Skip to content

Authentication

The Plirin API supports two authentication methods:

  • Dashboard sessions (JWT) — for interactive users (merchants and staff) who log in via the dashboard.
  • API keys — for server-to-server integrations using sk_live_… / sk_test_… keys on the /graphql endpoint.
mutation {
login(email: "you@business.com", password: "your-password") {
access_token
refresh_token
expires_in
user_type
}
}

The response includes:

  • access_token — use this in the Authorization header
  • refresh_token — use this to get a new access token when the current one expires
  • expires_in — token lifetime in seconds

The dashboard’s signup (createMerchant), sign-in (login, staffLogin, merchantLogin, adminLogin, and POST /api/v1/auth/login with turnstile_token in the body), requestPasswordReset and resendVerificationEmail take a turnstileToken argument: the token the Cloudflare Turnstile widget on the form produced. Without one the request is refused with details.code TURNSTILE_REQUIRED, and with one that does not pass, TURNSTILE_FAILED. Tokens are single use.

These are browser operations. API keys are not affected: an integration authenticates with its sk_live_ / sk_test_ key and never calls them.

  • Each sign-in operation is rate limited per IP address (a 429 with Retry-After when exceeded).
  • One sign-in or account operation per request: a request that names two, including through aliases, or a JSON array of operations, is refused with HTTP 400.
  • After five failed attempts for one email address from one network (an IPv4 /24 or IPv6 /64) within 15 minutes, further attempts for that address from that network are refused for 15 minutes, with the same error as a wrong password; after 50 from any networks, from everywhere. The count is shared by every sign-in mutation. Resetting the password lifts the lock.
  • A merchant account that Plirin has suspended cannot sign in, and its existing tokens, its staff’s tokens and its API keys stop working.

Include the access token in every authenticated request:

Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

When your access token expires, use the refresh token to get a new one:

mutation {
refreshToken(refreshToken: "your-refresh-token") {
access_token
expires_in
}
}

Verify your token and get account details:

query {
me {
email
businessName
kybStatus
roles
}
}

Server-to-server integrations can authenticate directly on the /graphql endpoint using an API key instead of a JWT. The key is resolved to its associated merchant automatically.

Authorization: Bearer sk_live_…
Authorization: Bearer sk_test_…

sk_live_ keys work with live data and sk_test_ keys with test data. (The REST API returns a livemode field on its responses. The GraphQL schema does not expose one, so use the key prefix to tell the environments apart.)

Creating an sk_live_ key requires an approved business verification, the same condition that unlocks live mode in the dashboard. Until then, sk_test_ keys are available and work against every test network.

API-key requests are subject to a default-deny operation allowlist. Only merchant-facing data operations are permitted:

Allowed resource groups
Payments and payment links
Invoices (all lifecycle operations: create, update, send, remind, sync, void)
Credit notes
Customers
Refunds
Products and prices
Discounts and promotion codes
Subscriptions (merchant-customer)
Inventory
Catalog and CSV import/export
Webhook endpoints
Merchant analytics reads

The following operations are denied with an API key and require a dashboard session (JWT):

Denied resource groups
Authentication (login, password reset, token refresh)
Staff management
All admin* operations
KYB lifecycle
Platform billing / merchant subscription to Plirin
AI chat
Account and profile management (updateMerchantProfile, logo upload)
Payout-wallet management (addOrUpdateWallet, removeWallet)
Test/live mode switching (setTestMode)
Bills inbox (receivedBills, pendingSenders, networkSenders, inboxNews, markInboxSeen, decideSender, saveBillToInbox)
Sidebar activity badges (navActivity, navNewItems, markPageSeen)
VAT rate configuration and reporting (vatRates, createVatRate, updateVatRate, deactivateVatRate, setDefaultVatRate, vatSummary, vatReport)

A denied operation returns a GraphQL error with HTTP 200 (standard GraphQL error envelope) — the resolver does not execute:

{
"errors": [
{
"message": "Operation not permitted with API key authentication",
"extensions": {
"code": "operation_not_permitted"
}
}
]
}

An invalid or unrecognised API key returns:

{
"errors": [
{
"message": "Invalid API key",
"extensions": {
"code": "authentication_failed"
}
}
]
}
Terminal window
curl -X POST https://your-domain.com/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk_live_…" \
-d '{
"query": "query { invoices(limit: 10) { invoices { id invoice_number status amount } totalCount } }"
}'

Keys are replaced without downtime, because a merchant can hold several valid keys at once:

  1. Create the new key in Settings > API Keys.
  2. Deploy it to your integration.
  3. Once nothing is using the old key, revoke it.

Both keys authenticate during step 2, so there is no window where requests fail. Revoking a key takes effect immediately and cannot be undone; create the replacement first.

A key is shown in full exactly once, when it is created. Plirin stores only a hash, so a lost key cannot be recovered and has to be replaced by rotating.