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/graphqlendpoint.
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 theAuthorizationheaderrefresh_token— use this to get a new access token when the current one expiresexpires_in— token lifetime in seconds
Security check (Cloudflare Turnstile)
Section titled “Security check (Cloudflare Turnstile)”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.
Sign-in limits
Section titled “Sign-in limits”- Each sign-in operation is rate limited per IP address (a
429withRetry-Afterwhen 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.
Using the token
Section titled “Using the token”Include the access token in every authenticated request:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...Refreshing tokens
Section titled “Refreshing tokens”When your access token expires, use the refresh token to get a new one:
mutation { refreshToken(refreshToken: "your-refresh-token") { access_token expires_in }}Current user
Section titled “Current user”Verify your token and get account details:
query { me { email businessName kybStatus roles }}GraphQL with API keys
Section titled “GraphQL with API keys”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.
Header format
Section titled “Header format”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.
Operation allowlist
Section titled “Operation allowlist”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) |
Error responses
Section titled “Error responses”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" } } ]}Example request
Section titled “Example request”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 } }" }'Rotating a key
Section titled “Rotating a key”Keys are replaced without downtime, because a merchant can hold several valid keys at once:
- Create the new key in Settings > API Keys.
- Deploy it to your integration.
- 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.