Authentication & Tokens
How to obtain, use, and rotate seller bearer tokens for the Fynex Payments API.
Every request to /payments-api/v1 authenticates with a seller bearer token. Tokens are scoped to a single seller account and grant full API access — treat them like passwords.
The auth header
Include the token in every request as an HTTP Bearer token:
Authorization: Bearer <your_token>
Anything else — missing header, malformed value, or an unrecognised token — returns 401 Unauthorized.
How to get a token
You obtain your token yourself — there is no need to wait on the Fynex team.
From the dashboard (easiest):
- Log in to the dashboard (
https://dashboard.fynex.ai) with your username and password. - Select your seller account.
- Open the Integration page. It shows the API token masked, with a reveal (eye) toggle and a copy button.
- Click the reveal icon, then copy the token. This value is exactly what you pass as
Authorization: Bearer <token>.
If you don't have a dashboard login yet, sign up self-service (see Programmatically below).
Programmatically: POST /api/v1/onboarding/start (sign-up; sets the session cookie itself) — or, for an existing account, POST /api/v1/login/dashboard (which sets the
session cookie) → the createSellerAccount GraphQL mutation, which returns the new seller
account's authorizationToken. The first user of a new organization is automatically granted the
selleraccounts:create / selleraccounts:update permissions this requires. See the
Account setup & onboarding guide for the full walkthrough.
Important
A new seller account is created in
Demomode and activated automatically, so your token works immediately for sandbox testing — you can take test payments straight away (see the Quickstart). Going live (real-money processing) is separate: it requires KYB approval and a Fynex-assigned live acquirer terminal. If a sandbox call ever returns403 seller account is not active, the token is still valid — the account just isn't active yet; contact Fynex with your seller account ID.
Note
A separate token is issued per seller account, and each environment's dashboard issues its own — a token only works against the API host of the dashboard that issued it.
Caution
The Integration page also has a Regenerate button. Regenerating issues a new token and immediately invalidates the old one — there is no overlap window. Only use it when you intend to rotate (see Rotating a token).
What a token looks like
There are two credential shapes on this API, and which one you get depends on how you got it:
- Dashboard-issued (Integration page, above). An opaque long-lived string with no prefix — treat the entire value as opaque, don't parse it.
- Secret key (Integration → API keys, see /keys). Prefixed
sk_test_…while the account is in demo mode andsk_live_…once it has gone live.
Both authenticate identically as a Bearer token on the Payments API. Don't
assume every token on this API carries an sk_ prefix — the dashboard-issued
shape doesn't.
The Billing API (/billing-api/v1) is stricter: it accepts only
sk_test_… / sk_live_… secret keys and answers 401 to the prefix-less
dashboard token. If you need both APIs, use an sk_ key for both — one key
authenticates both surfaces, the legacy token authenticates only this one.
Verifying a token
The quickest "is this token alive, and which account is it?" check is GET /account. It requires only a valid seller token and returns the account the token acts for — including operationalMode, which tells you whether you are in Demo (test cards, no real money) or Live.
curl -sS "$FYNEX_API/account" \
-H "Authorization: Bearer $FYNEX_TOKEN"
{
"sellerAccountUuid": "3f2b1c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"name": "Acme Marketplace",
"operationalMode": "Demo",
"status": "Active",
"currency": "EUR",
"selfServeSandbox": false,
"features": { "walletCreation": true, "payeePayoutAutoFunding": true, "refunds": true }
}
A 200 OK JSON response confirms the token works and the account is active. A 401 means the token is missing, malformed, or invalid. A 403 seller account is not active means the token is valid and recognised but the account isn't active — contact Fynex with your seller account ID (see How to get a token).
Storing tokens securely
- Never commit tokens to source control. Use a secret manager (HashiCorp Vault, AWS Secrets Manager, GitHub Encrypted Secrets) or environment variables loaded at runtime.
- One token per environment. Keep staging and production tokens separate.
- One token per seller account. There is no cross-account access; operate multiple sellers with one token each.
# .env — never commit this file
FYNEX_TOKEN=<your_token_here>
FYNEX_API=https://api.fynex.ai/payments-api/v1
Rotating a token
The simplest way to rotate is the Regenerate button on the dashboard Integration page (the same page you got the token from). It issues a new token and reveals it for copying.
Caution
Rotation atomically replaces the existing token. The old token becomes invalid immediately — there is no two-token overlap window. Plan a brief service restart or deploy when rotating.
If you prefer to automate it, the same operation is exposed as the GraphQL mutation regenerateSellerAccountToken on the /dashboard/graphql endpoint (cookie-session authenticated).
Rotation steps (GraphQL)
- Authenticate with the dashboard to obtain a
dashboard_sessioncookie:
curl -c cookies.txt -X POST https://api.fynex.ai/api/v1/login/dashboard \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com", "password": "..."}'
- Call
regenerateSellerAccountTokenwith your seller account ID asmerchantId:
curl -b cookies.txt -X POST https://api.fynex.ai/dashboard/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "mutation Rotate($merchantId: ID!) { regenerateSellerAccountToken(merchantId: $merchantId) { authorizationToken } }",
"variables": { "merchantId": "42" }
}'
The response contains the new authorizationToken value.
- Update your services — replace the token in your secret manager / environment and restart affected services before the old token is invalidated (which happened in step 2).
When to rotate
- A developer with access leaves the team.
- You suspect or confirm a token leak.
- As a precautionary measure on a regular schedule (quarterly is a common default).
Rotating an API key without an outage
sk_ API keys, unlike the dashboard token, rotate with an overlap: regenerating a key can leave the
old one working for up to 7 days beside its replacement, so you deploy the new key first and let the
old one lapse. Regenerating answers with the replacement's plaintext, once; the replacement keeps the
old key's name, permissions and lifetime.
# $FYNEX_TOKEN is the dashboard token from the Integration page, not an sk_ key.
curl -sS -X POST https://api.fynex.ai/api/v1/developer/api-keys/<id>/regenerate \
-H "Authorization: Bearer $FYNEX_TOKEN" \
-H "Content-Type: application/json" \
-d '{"overlapHours": 24}'
overlapHours—0to168.0, or no body at all, stops the old key at once, as before.expiresAt— optional RFC 3339 time at which the replacement stops working. Without it the replacement lives as long as the old key was issued to, counted from now: a 30-day key regenerated today expires in 30 days, and a key that never expired is replaced by one that never expires.
Key management needs the dashboard token or a dashboard session: an sk_ key cannot create,
regenerate or revoke keys.
Naming and expiring keys
Give each integration its own key — production, CI, one per agent — so one can be revoked without
touching the others. POST /api/v1/developer/api-keys takes:
| Field | Required | Meaning |
|---|---|---|
type |
yes | secret or publishable |
name |
no | What the key is for, up to 64 characters. Shown in the key list |
expiresAt |
no | RFC 3339 time at which the key stops working, at least a minute ahead. Omit it for a key that never expires |
A key used after its expiresAt is refused with 401 api_key_expired. The key list reports it
with status: "expired"; create or regenerate a key to replace it.
Restricted keys
A secret key can be restricted to what one integration needs: a reporting agent that reads
payments and events, a metering job that only submits usage. Pass permissions when you create it:
{ "type": "secret", "name": "metering", "permissions": ["billing_usage:write"] }
Each permission is a resource at read or write; write includes read. Without permissions
the key has full access, as every key had before.
| Resource | Covers |
|---|---|
payments |
Payments, checkout sessions, capture, payment methods, settling a test payment |
refunds |
Refunds and their cancellation, cancelling a promotion-paid order |
payees |
Payees |
payout_methods |
Payout methods, the seller's own and its payees' |
payouts |
Payouts |
wallets |
Wallets, their funding and transactions |
topups |
Top-up invoices |
split_rules |
Split rules and a payment's split decisions |
webhooks |
Webhook endpoints and the allowlist |
events |
The events feed |
vendors |
Marketplace vendors |
billing_contracts |
Billing customers, contracts, credits, prices and the test clock |
billing_subscriptions |
Subscriptions |
billing_invoices |
Billing invoices |
billing_usage |
Usage events, metrics, dead letters and usage webhook endpoints |
Every operation in the OpenAPI documents states the permissions it needs in
x-fynex-permissions. A restricted key calling an operation it lacks a permission for is refused
with 403 insufficient_scope, and the message names what is missing:
{ "error": "this API key is restricted: this route needs payouts:write", "code": "insufficient_scope" }
A restricted key reaches only the documented Payments and Billing operations, under
/payments-api/v1 and /billing-api/v1, and the MCP endpoint. GET /account works with any key
and reports the calling key's access and permissions under apiKey, so an integration can check
what it may do. Regenerating a restricted key keeps its permissions; only a secret key can be
restricted.
OAuth for AI clients (MCP)
Not yet available on production.
Token properties
| Property | Dashboard token | sk_ API key |
|---|---|---|
| Scope | Single seller account | Single seller account |
| Expiry | None | Optional, per key (expiresAt) |
| Revoke | None — rotate to invalidate | Any key, at any time |
| Rotation | Replaces the token at once | At once, or with up to 7 days of overlap |
| Several per account | No — one per seller account | Yes, each with its own name |
| Permissions | Full access | Full access, or restricted to listed permissions |
| Rate limiting | Per-seller token-bucket rate limit; the budget differs by environment — read X-RateLimit-Limit / X-RateLimit-Remaining rather than assuming a rate. Returns 429 with a Retry-After header when exceeded |
Same |
Common errors
| Status | Body | Cause |
|---|---|---|
401 |
{"error": "authorization token is required", "code": "missing_api_key"} |
Header absent or malformed at the middleware level |
401 |
{"error": "invalid authorization token", "code": "invalid_api_key"} |
Token value is not recognised |
401 |
{"error": "invalid authorization token: this API key has expired …", "code": "api_key_expired"} |
An sk_ key used after its expiresAt |
401 |
{"error": "invalid authorization token: invalid access token: …", "code": "invalid_token"} |
An OAuth access token that expired, whose connection was revoked, or presented outside the MCP endpoint |
401 |
{"error": "seller auth is required"} |
Missing seller context inside a handler |
403 |
{"error": "seller account is not active", "code": "account_not_active"} |
Token is valid but the seller account isn't active — new demo accounts activate automatically; if you see this, contact Fynex with your seller account ID |
403 |
{"error": "this API key is restricted: …", "code": "insufficient_scope"} |
A restricted sk_ key without the permission this route needs — see Restricted keys |
403 |
{"error": "resource does not belong to this seller"} |
Token is valid but the resource belongs to a different seller |
Note
Every error body is JSON. Branch on
code, not on theerrortext — see Errors.
See also
- Quickstart — Take your first test payment in 5 minutes.
- Errors — Full status code and error body reference.
- Request headers — All request headers reference — required, conditional, and optional.