Merchant API keys

List, create, and revoke a merchant's API keys from the Tokeflow Dashboard, and discover which scopes each key type can carry.

These endpoints power the API keys screen of the Tokeflow Dashboard for a merchant: list the keys that exist, mint a new one, look up which scopes it may carry, and revoke a key that should no longer work. The keys they manage are the same sk_… (secret) and pk_… (public) credentials your integration presents to the public API — the Dashboard is simply the human-friendly way to manage them.

Secret keys grant server-side access and belong in a secrets manager; public keys are client-safe and restricted to what the Bridge SDK needs in the browser. See Authentication for the full model and Environments & API keys for how keys encode environment and entity in their prefix.

These endpoints power the Tokeflow Dashboard UI. They are session-authenticated (JWT bearer) — the token comes from signing in to the Dashboard, not from an sk_/pk_ API key. Every request must also carry the x-merchant-id header identifying the merchant whose keys are being managed; the signed-in user must be a member with access to that merchant. To manage keys programmatically from your backend instead, use the public API keys endpoints.

How the key screen flows

The scopes endpoint feeds the key-creation form: it returns only the scopes valid for this merchant and the chosen key type, so the user can never select a scope the key could not carry. After creation, the full key is never shown again — the list identifies each key by its key_prefix alone.

The API key object

Returned on creation; the list endpoint returns the same object without the creation-only api_key secret.

FieldTypeDescription
idstringUnique identifier for the API key, prefixed ak_.
entity_typestringType of entity the key belongs to — organization or merchant. Keys created on this surface belong to a merchant.
entity_idstringID of the owning entity (mrc_… for merchant keys).
namestringHuman-readable name for the key.
key_prefixstringNon-secret key prefix used for identification (e.g. sk_live_mer_7d21). The only part of the key shown after creation.
scopesarrayScopes/permissions granted to the key.
allowed_ipsarray | nullIP addresses and CIDR ranges allowed to use the key. null when the key is unrestricted.
is_activebooleanWhether the key is active. Requests with an inactive key fail authentication.
expires_atstring | nullExpiration date (ISO 8601 UTC), or null if the key never expires.
last_used_atstring | nullLast time the key was used, or null if it has never been used.
created_atstringCreation timestamp (ISO 8601 UTC).
api_keystringThe actual API key. Only returned on creation.
key_hashstringHash of the key material, used server-side for verification. Not a credential — it cannot be used to authenticate.

last_used_at is the Dashboard's best signal for whether a key is really in use. Before revoking a key that looks idle, check that its last_used_at has not moved recently — a stale name does not guarantee a stale key.


Endpoints

GET/api/v1/merchant/api-keys

Merchant

Auth: Dashboard session (JWT bearer). Merchant membership required.

Returns a paginated list of the merchant's API keys — the merchant is taken from the x-merchant-id header.

Query parameters

FieldTypeRequiredDescription
pagenumberNoPage number, 1-indexed (default 1, min 1).
limitnumberNoItems per page (default 20, min 1, max 100).

Example request

curl -G https://api.tokeflow.com/api/v1/merchant/api-keys \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…" \
  -H "x-merchant-id: mrc_8a3f12d9" \
  --data-urlencode "page=1" \
  --data-urlencode "limit=20"

Example response200 OK

{
  "data": [
    {
      "id": "ak_9f2e6c81",
      "entity_type": "merchant",
      "entity_id": "mrc_8a3f12d9",
      "name": "Checkout service (production)",
      "key_prefix": "sk_live_mer_7d21",
      "scopes": ["transactions:read", "transactions:write"],
      "allowed_ips": ["203.0.113.10", "198.51.100.0/24"],
      "is_active": true,
      "expires_at": null,
      "last_used_at": "2026-08-11T18:22:04.000Z",
      "created_at": "2026-03-02T09:15:00.000Z"
    },
    {
      "id": "ak_5b0d3a47",
      "entity_type": "merchant",
      "entity_id": "mrc_8a3f12d9",
      "name": "Storefront widget",
      "key_prefix": "pk_live_mer_0c1d",
      "scopes": ["tokens:read"],
      "allowed_ips": null,
      "is_active": false,
      "expires_at": "2026-12-31T23:59:59.000Z",
      "last_used_at": "2026-07-30T10:41:52.000Z",
      "created_at": "2026-01-20T16:00:00.000Z"
    }
  ],
  "meta": {
    "page": 1,
    "limit": 20,
    "total": 2,
    "total_pages": 1,
    "has_next": false,
    "has_prev": false
  }
}

data holds the keys for the current page and meta the pagination metadata — advance page while has_next is true. The full key is never included here; each key is identified by its non-secret key_prefix.


POST/api/v1/merchant/api-keys

Merchant

Auth: Dashboard session (JWT bearer). Merchant membership required.

Creates an API key for the merchant identified by x-merchant-id. The response is the only place the full key (api_key) is ever returned.

Request fields

FieldTypeRequiredDescription
namestringYesHuman-readable name for the key (max 255 characters). Name it after the workload, e.g. Checkout service (production).
scopesarray of stringsYesScopes/permissions for the key — at least one. Must be valid for this merchant and key type; load the assignable values with GET /merchant/api-keys/scopes.
allowed_ipsarray of stringsNoIP addresses allowed to use the key — up to 50 entries, each up to 45 characters. Omit the field entirely for no restriction — an empty array is rejected, since it reads as a restriction but enforces none. See the note below for accepted formats.
key_typestringNoType of key to create — secret (server-side, sk_…) or public (client-safe, pk_…).
expires_atstringNoExpiration date for the key (ISO 8601). Omit for a key that never expires.
entity_typestringNoType of entity the key belongs to — organization or merchant. On this surface, omit it or send merchant.
entity_idstringNoID of the entity the key belongs to (e.g. mrc_8a3f12d9). Omit to use the merchant from x-merchant-id.

allowed_ips formats. IPv4 addresses must be dotted-quad (203.0.113.10); CIDR entries must be in network form (192.168.1.0/24, not 192.168.1.5/24); IPv6 addresses and ranges are accepted (2001:db8::/32). A wildcard entry — *, 0.0.0.0/0, or ::/0 — allows every IP and cannot be combined with specific addresses.

The full key is returned exactly once. The api_key field appears only in this creation response — Tokeflow stores a hash, not the key itself, and cannot show it again. Copy it to a secrets manager immediately; every later response identifies the key by its non-secret key_prefix only.

Example request

curl -X POST https://api.tokeflow.com/api/v1/merchant/api-keys \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…" \
  -H "x-merchant-id: mrc_8a3f12d9" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Checkout service (production)",
    "scopes": ["transactions:read", "transactions:write"],
    "allowed_ips": ["203.0.113.10", "198.51.100.0/24"],
    "key_type": "secret"
  }'

Example response201 Created

{
  "id": "ak_9f2e6c81",
  "entity_type": "merchant",
  "entity_id": "mrc_8a3f12d9",
  "name": "Checkout service (production)",
  "key_prefix": "sk_live_mer_7d21",
  "scopes": ["transactions:read", "transactions:write"],
  "allowed_ips": ["203.0.113.10", "198.51.100.0/24"],
  "is_active": true,
  "expires_at": null,
  "last_used_at": null,
  "created_at": "2026-08-12T14:03:00.000Z",
  "api_key": "sk_live_mer_7d21c39ab8e4f560",
  "key_hash": "6dcd4ce23d88e2ee9568ba546c007c63d9131c1b"
}

Handle this response like the credential it contains: surface api_key to the user exactly once and keep the body out of logs, error trackers, and analytics. From here on, identify the key by id and recognize it by key_prefix.


GET/api/v1/merchant/api-keys/scopes

Merchant

Auth: Dashboard session (JWT bearer). Merchant membership required.

Returns the scopes that can be assigned to this merchant's API keys. Org-only scopes are excluded. Filter by key_type to get the scopes valid for public or secret keys — this is what the Dashboard's key-creation form uses to populate its scope picker before calling POST /merchant/api-keys.

Query parameters

FieldTypeRequiredDescription
key_typestringYesKey type to list scopes for — secret or public. Public keys are restricted to client-safe scopes.

Example request

curl -G https://api.tokeflow.com/api/v1/merchant/api-keys/scopes \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…" \
  -H "x-merchant-id: mrc_8a3f12d9" \
  --data-urlencode "key_type=secret"

Example response200 OK (truncated)

{
  "scopes": [
    {
      "scope": "transactions:read",
      "description": "List and view transactions"
    },
    {
      "scope": "transactions:write",
      "description": "Create, capture, and refund transactions"
    },
    {
      "scope": "customers:read",
      "description": "List and view customers"
    }
  ],
  "wildcards": [
    "transactions:*",
    "customers:*"
  ]
}

Each item in scopes pairs the scope string with a human-readable description. wildcards lists the wildcard scope strings valid for this entity and key type — a wildcard grants every scope it covers, so reserve them for trusted, tightly-controlled services and prefer explicit scope lists everywhere else.


DELETE/api/v1/merchant/api-keys/:id

Merchant

Auth: Dashboard session (JWT bearer). Merchant membership required.

Revokes an API key permanently. Revocation takes effect immediately: any request presenting the revoked key fails authentication from that moment on. There is no undo — if the workload still needs access, create a replacement key first.

Path parameters

FieldTypeRequiredDescription
idstringYesID of the API key to revoke (e.g. ak_9f2e6c81).

Example request

curl -X DELETE https://api.tokeflow.com/api/v1/merchant/api-keys/ak_9f2e6c81 \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…" \
  -H "x-merchant-id: mrc_8a3f12d9"

Example response204 No Content

The response body is empty.

If a secret key leaks, revoke it now — before creating the replacement. A brief outage of one workload is a better outcome than an attacker holding a live server-side credential. For planned rotation (no exposure), do the reverse: create the new key, deploy it, confirm it works, then revoke the old one.


Errors

The most common cases for these endpoints:

HTTPTypical cause
400Invalid input — missing name or scopes, an unknown scope, an empty or malformed allowed_ips list, or a missing key_type on the scopes endpoint.
401Missing, invalid, or expired Dashboard session token.
403Signed in, but not a member with access to the merchant in x-merchant-id.
404API key not found, or it does not belong to this merchant.

See Errors for the general error format used across the API.

On this page