Authentication
How Tokeflow API authentication works — secret vs public keys, the Bearer scheme, key anatomy, Organization vs Merchant scoping, scopes, and IP allowlisting.
Every request to the Tokeflow API is authenticated with an API key. The key you present determines two things at once: who you are (the Organization or Merchant the key belongs to) and what you can do (the scopes granted to that key).
Tokeflow uses two kinds of keys:
- Secret keys (
sk_…) — full server-side access. Used from your backend with theAuthorization: Bearerheader. - Public keys (
pk_…) — restricted, client-safe access. Used by the Bridge SDK in the browser to collect payment details. Safe to ship to the client.
Never expose a secret key in client-side code. Secret keys grant server-side access to your account. They belong only on your servers — never in browser JavaScript, mobile apps, public repositories, or anything a user can inspect. If a secret key leaks, revoke it immediately in the Tokeflow Dashboard. Use a public key for anything that runs in a browser.
The Bearer scheme
Server-side requests authenticate by sending your secret key in the Authorization header using the Bearer scheme:
Authorization: Bearer <secret_key>curl https://api.tokeflow.com/api/v1/transactions \
-H "Authorization: Bearer sk_live_mer_4f9a2c7e8b1d6035" \
-H "Accept: application/json"Load secret keys from environment variables or a secrets manager — never hardcode them. See Environments & API keys for how live keys are issued and rotated.
All endpoints live under https://api.tokeflow.com/api/v1. For the full request/response model and conventions, see the API overview.
Key anatomy
Every key encodes its type, environment, and scope in the prefix. The format is:
{sk|pk}_{environment}_{org|mer}_{random}Worked example:
sk_live_mer_4f9a2c7e8b1d6035
│ │ │ └─ random — the unique, secret portion
│ │ └───── scope — org (Organization) or mer (Merchant)
│ └────────── environment — e.g. live
└───────────── key type — sk (secret) or pk (public)| Segment | Values | Meaning |
|---|---|---|
| Key type | sk, pk | sk = secret (server-side). pk = public (client-safe). |
| Environment | e.g. live | The environment the key operates in. |
| Scope | org, mer | org = Organization-scoped. mer = Merchant-scoped. |
| Random | opaque string | The unique secret portion. Treat the whole key as sensitive. |
This means you can recognize at a glance what a key is:
pk_live_org_…— public, Organization keypk_live_mer_…— public, Merchant keysk_live_org_…— secret, Organization keysk_live_mer_…— secret, Merchant key
Only the prefix is ever shown back to you in the Dashboard and in API responses (for example sk_live_mer_4f9a2c7e…). The full key is displayed exactly once, at creation time. Store it securely then — Tokeflow cannot show it again.
Organization keys vs Merchant keys
Tokeflow is multi-tenant: an Organization governs one or more Merchants (see Organizations & Merchants). Every key is scoped to exactly one entity, and that scope changes how merchant-specific resources are addressed.
Merchant key (…_mer_…) | Organization key (…_org_…) | |
|---|---|---|
| Scope | A single merchant | Every merchant in the organization |
| Merchant targeting | Inferred automatically — no merchant_id needed | Must specify the target merchant via merchant_id |
merchant_id on list/get | Not used (ignored) | Query parameter, e.g. ?merchant_id=mrc_… |
merchant_id on create | Not used | Body field |
| Best for | A single merchant's own backend | Platform / multi-merchant orchestration |
Merchant key — merchant is inferred
A Merchant key is already bound to one merchant, so you never pass a merchant_id. The example below lists that merchant's transactions:
curl "https://api.tokeflow.com/api/v1/transactions?limit=20" \
-H "Authorization: Bearer sk_live_mer_4f9a2c7e8b1d6035" \
-H "Accept: application/json"Organization key — identify the merchant
An Organization key spans every merchant in the org, so for merchant-scoped resources you must say which merchant you mean. On reads, pass merchant_id as a query parameter:
curl "https://api.tokeflow.com/api/v1/transactions?merchant_id=mrc_8a3f12d9&limit=20" \
-H "Authorization: Bearer sk_live_org_2b7e91c4a0f53d68" \
-H "Accept: application/json"On writes, pass merchant_id in the request body:
await fetch("https://api.tokeflow.com/api/v1/transactions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKEFLOW_ORG_SECRET_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
merchant_id: "mrc_8a3f12d9",
amount: 15000, // minor units → R$150.00
currency: "BRL",
payment_method: "credit_card",
// …
}),
});With an Organization key, omitting merchant_id on a merchant-scoped request is a client error and returns 400 validation_error. With a Merchant key, any merchant_id you send is ignored — the key's own merchant always wins.
Scopes
Permissions are expressed as scopes in the form resource:action. A key is granted an explicit list of scopes when it is created in the Tokeflow Dashboard, and a request is authorized only if the key carries the scope the endpoint requires.
Common scopes:
| Scope | Grants |
|---|---|
transactions:read | List and view transactions |
transactions:write | Capture and refund transactions |
customers:read / customers:write | Read / manage customers |
orders:read / orders:write | Read / manage orders |
products:read / products:write | Read / manage catalog products |
offers:read / offers:write | Read / manage offers and prices |
checkout:read / checkout:write | Read / manage checkout sessions |
subscriptions:read / subscriptions:write | Read / manage subscriptions |
webhooks:read / webhooks:write | Read / manage webhook configuration |
tokens:read | Read SDK tokens |
reports:read | View reports and analytics |
audit_logs:read | List and view audit logs |
The :read / :write split lets you apply least privilege: issue read-only keys for reporting or analytics workloads, and reserve write scopes for the services that actually create or mutate resources.
Create one key per workload, with only the scopes that workload needs. Narrow, single-purpose keys limit blast radius if a key is ever compromised, and make audit logs easier to reason about.
Public keys: what they can do
Public keys (pk_…) are designed to be embedded in browser code. They are restricted to a small set of client-safe scopes — the operations the Bridge SDK needs to collect payment details and create tokens — and cannot read transactions, issue refunds, or touch any server-only resource.
Because of those restrictions, public keys are safe to expose in client-side code. The Bridge SDK presents the public key over SDK-specific headers rather than the Authorization header:
X-Public-Key: pk_live_mer_…
X-Session-Id: <uuid>
X-SDK-Version: <semver>You normally never set these headers yourself — the SDK manages them. See the Bridge SDK overview for details.
Card data collected in the browser is captured by Tokeflow's secure fields, which run inside isolated, cross-origin iframes. Card data is encrypted in the browser and never touches your servers — so even a fully exposed public key cannot be used to read raw card details.
IP allowlisting (optional hardening)
For an extra layer of defense, you can restrict a key to a set of source IPs by configuring an allowed_ips allowlist on the key in the Tokeflow Dashboard. When set, requests from any other IP are rejected with 403 authorization_error, even if the key and its scopes are otherwise valid.
The allowlist accepts:
- Exact IPv4 / IPv6 addresses —
203.0.113.10,2001:db8::1 - CIDR ranges —
203.0.113.0/24,2001:db8::/32 - Wildcards to allow all IPs —
*,0.0.0.0/0, or::/0
IP allowlisting is most effective for secret keys used by backend services with stable egress IPs. Avoid pinning IPs on public keys — browser clients connect from many, ever-changing addresses.
401 vs 403
Tokeflow distinguishes authentication failures from authorization failures:
401 authentication_error— Tokeflow could not establish who you are. The key is missing, malformed, revoked, or expired.403 authorization_error— Tokeflow knows who you are, but you are not allowed to do this. The key lacks the required scope, or the request came from an IP outside the key'sallowed_ips.
A missing or invalid key returns 401:
{
"error": {
"type": "authentication_error",
"code": "INVALID_API_KEY",
"message": "Invalid or expired API key",
"details": {},
"request_id": "req_8f3c9a1e2d40",
"timestamp": "2026-01-15T12:30:00.000Z"
}
}A valid key that lacks the required scope (or is blocked by IP allowlisting) returns 403:
{
"error": {
"type": "authorization_error",
"code": "INSUFFICIENT_SCOPE",
"message": "This API key is not permitted to perform this action",
"details": { "required_scope": "transactions:write" },
"request_id": "req_3a7d10b9c2e8",
"timestamp": "2026-01-15T12:30:00.000Z"
}
}Treat 401 and 403 differently in your client. A 401 means fix the credentials (rotate or re-load the key). A 403 means fix the permissions — request the missing scope, or check the calling host against the key's IP allowlist. Never retry either blindly.
Next steps
- Environments & API keys — how keys are issued, rotated, and revoked.
- API overview — base URL, versioning, envelopes, and conventions.
- Bridge SDK overview — using public keys to collect payment details in the browser.