Authentication

Every request to the Flint API goes to https://api.withflintpay.com and carries a secret API key. You send the key one of two ways, and the key itself decides everything else: whether the request runs in test or live mode, and for test mode, which sandbox it uses. Keys are secret and belong on your server only. This page covers how to send a key, how test and live keys differ, and what each authentication error means. If you just want to make your first call, start with Accept Your First Payment.

Making an Authenticated Request#

Put your key in the Authorization header as a bearer token. This call reads your account's card-payments capability and confirms the key works:

Bash
curl "https://api.withflintpay.com/v1/capabilities?capability=accept_card_payments" \
  -H "Authorization: Bearer YOUR_API_KEY"
JSON
{
  "data": [
    { "capability": "accept_card_payments", "status": "ready" }
  ],
  "request_id": "0b8f2b26-9c1e-4f6a-8d35-7e4a2c9b1d60",
  "meta": { "api_version": "2026-02-01" }
}

A successful response wraps the result under data, alongside a request_id and a meta object. List endpoints add a next_page_token for pagination. Every response, success or failure, also returns an x-request-id header carrying the same request id. If the call fails, the body is the error envelope instead.

The base URL is the same for test and live traffic. There is no separate test host and no per-request mode flag: your key selects the mode.

The Two Header Schemes#

Flint accepts the key two ways. Use whichever fits your client; do not send both.

The bearer scheme is the copy-paste default and the one every example on this site uses.

HTTP
Authorization: Bearer YOUR_API_KEY

Flint does not accept HTTP Basic auth or a key passed as a query parameter. The key must travel in one of the two headers above.

Test and Live Keys#

A key's prefix decides its mode. Flint reads the prefix on every request; you never pass a mode flag.

flint_test_

Test keys

Run against an isolated sandbox. Card charges are simulated and no money moves. Every test key is bound to exactly one sandbox, so the key you send selects the sandbox with no extra header. Dashboard test keys come sandbox-bound automatically.

flint_live_

Live keys

Run against your real merchant data and move real money. Live keys are never bound to a sandbox. Guard them accordingly and issue them only when you are ready to charge.

For test cards, sandbox isolation, and resetting test data, see Sandboxes & Test Mode.

Test keys must be sandbox-bound

A test key that is not bound to a sandbox is rejected with 401 SANDBOX_SELECTION_REQUIRED. The reverse also holds: a live key bound to a sandbox is rejected with API_KEY_MODE_MISMATCH. Both appear in the error table below.

Where Keys Come From#

Every path below gives you a working key:

The full secret key is shown only once, in the response that creates it. Capture it then; Flint never returns it again.

What a Key Looks Like#

A key has two identifiers, and telling them apart avoids most key-handling confusion. The key_-prefixed api_key_id names the key record and is safe to log and store; the flint_..._<random> string is the secret you actually send.

api_key_idstring

The record id (for example key_01KWJ93G11C7MF8REX91MDS0CD). Use it to retrieve, update, or revoke the key. Not a secret.

key_prefixstring

The non-secret, displayable start of the key (for example flint_test_1a2b3c4d). Safe to show in a UI or logs so you can tell keys apart.

secret_keystring

The full plaintext key. Returned only once, in the create response. This is the value you put in the Authorization header.

scopesstring[]

The permissions the key grants. See Scopes and Permissions.

statusstring

active or revoked. A revoked key can no longer authenticate.

The rest of the key object is descriptive metadata:

FieldDescription
merchant_idThe merchant the key belongs to.
sandbox_idThe sandbox a test key is bound to. Absent on live keys.
nameA label you set to identify the key.
key_typeAlways external for keys you use against the public API.
last_used_atWhen the key last authenticated a request.
created_atWhen the key was created.
updated_atWhen the key was last modified.

Scopes and Permissions#

Each key carries a set of scopes such as commerce.orders.read, payments.payment_intents.write, or webhooks.webhooks.write. A request to an endpoint the key is not scoped for is rejected with 403, not 401.

A .write scope also satisfies an endpoint that only requires the matching .read. Holding commerce.orders.write lets you both read and write orders, so you do not need to grant both.

When a key lacks the required scope, the response names it:

JSON
{
  "error": {
    "type": "authorization_error",
    "code": "INSUFFICIENT_SCOPE",
    "message": "The API key does not have the required scope: commerce.orders.write.",
    "request_id": "4a1f0c9e-2b7d-4f60-8c25-9e0b3d6f2a81"
  }
}

Unlike a 401, a scope 403 carries no WWW-Authenticate header. The missing scope is in the message, so you know exactly what to grant.

API key scope catalog

Every scope and the operations it grants.

Managing Keys Over the API#

Alongside the dashboard, you can manage keys entirely over the API:

GET/v1/api-keys

List your keys. Requires accounts.api_keys.read.

POST/v1/api-keys

Create a key. Requires accounts.api_keys.write.

GET/v1/api-keys/{api_key_id}

Retrieve one key.

PATCH/v1/api-keys/{api_key_id}

Update a key's metadata, such as its name.

POST/v1/api-keys/{api_key_id}/revoke

Revoke a key so it can no longer authenticate.

Listing your keys is a plain read:

Bash
curl "https://api.withflintpay.com/v1/api-keys?page_size=2" \
  -H "Authorization: Bearer YOUR_API_KEY"
JSON
{
  "data": [
    {
      "api_key_id": "key_01KWJ93G11C7MF8REX91MDS0CD",
      "name": "Production server",
      "key_prefix": "flint_live_1a2b3c4d",
      "sandbox_id": null,
      "scopes": ["commerce.orders.write", "payments.payment_intents.write"],
      "key_type": "external",
      "status": "active",
      "last_used_at": "2026-07-02T18:04:11Z",
      "created_at": "2026-06-01T09:12:00Z"
    }
  ],
  "next_page_token": "eyJjIjoia2V5XzAxS1cifQ",
  "request_id": "1b9d7c2a-4e8f-4a31-b7f0-5c9a2e8d4b36"
}

Creating a key returns the secret_key once. Two rules apply to the request:

  • A test (sandbox-bound) caller must pass a sandbox_id; the new key is bound to that sandbox. A live caller must not pass one.
  • You can only grant scopes the calling key already holds, so a key cannot escalate its own permissions.
Bash
curl -X POST https://api.withflintpay.com/v1/api-keys \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer flint_test_YOUR_KEY" \
  -d '{
    "name": "CI runner",
    "scopes": ["commerce.orders.read"],
    "sandbox_id": "test_01KNB061AGD0CYYF16M5QAQE3N"
  }'
JSON
{
  "data": {
    "api_key_id": "key_01KWK4Q8Z1MB7F0T2S9R3V6HAX",
    "name": "CI runner",
    "key_prefix": "flint_test_9f8e7d6c",
    "secret_key": "flint_test_9f8e7d6c...",
    "sandbox_id": "test_01KNB061AGD0CYYF16M5QAQE3N",
    "scopes": ["commerce.orders.read"],
    "key_type": "external",
    "status": "active"
  }
}

There is no rotate endpoint. To rotate a key, create a new one and revoke the old:

1

Create a replacement#

Create a new key with the same scopes as the one you are replacing.

2

Deploy it#

Roll the new secret_key out to your servers and confirm traffic is using it.

3

Revoke the old key#

Call POST /v1/api-keys/{api_key_id}/revoke on the previous key. Once revoked, it authenticates nothing (API_KEY_REVOKED).

Authentication and Authorization Errors#

Authentication failures return 401 with type: authentication_error and a WWW-Authenticate: Bearer realm="Flint API" header. Authorization failures (a valid key that lacks permission) return 403 with type: authorization_error and no WWW-Authenticate header. Every error uses the same envelope:

JSON
{
  "error": {
    "type": "authentication_error",
    "code": "API_KEY_NOT_FOUND",
    "message": "No API key matches the provided credential.",
    "request_id": "6f2c9a71-b0d3-4e58-9c16-7a4e0b2d8f53"
  }
}
CodeHTTPMeaning and fix
API_KEY_REQUIRED401No credential sent. Add the Authorization: Bearer or X-API-Key header.
INVALID_AUTHORIZATION_HEADER401The Authorization header is malformed. Use Bearer with a flint_ key.
INVALID_API_KEY401The value is not a valid Flint key. Check for truncation or stray whitespace.
API_KEY_NOT_FOUND401No key matches the credential. It may belong to another environment.
API_KEY_REVOKED401The key was revoked. Issue a new one.
API_KEY_EXPIRED401The key has expired. Issue a new one.
SANDBOX_SELECTION_REQUIRED401A test key that is not sandbox-bound. Use a sandbox-bound key (the dashboard's are).
API_KEY_MODE_MISMATCH401A live key bound to a sandbox. Use an unbound live key.
INSUFFICIENT_SCOPE403The key lacks the scope the endpoint needs (named in the message). See Scopes.
EXTERNAL_API_KEY_REQUIRED403The endpoint accepts only external API keys.

SANDBOX_SELECTION_REQUIRED is the most common first-run failure, and it carries a remediation block telling you exactly what to do:

JSON
{
  "error": {
    "type": "authentication_error",
    "code": "SANDBOX_SELECTION_REQUIRED",
    "message": "Test mode API keys must be sandbox-bound before they can authenticate public API requests.",
    "remediation": {
      "retryable": false,
      "next_actions": [
        {
          "action_type": "issue_developer_sandbox_test_key",
          "reason_code": "SANDBOX_BOUND_TEST_KEY_REQUIRED",
          "reason_message": "Create or use a sandbox-bound test key for the sandbox this request should target.",
          "required_fields": ["sandbox_id"]
        }
      ]
    }
  }
}

Every error carries a request_id (also in the x-request-id response header). Quote it in bug reports and support requests. To trace a failing call end to end, see Debugging; for the full error model, see Error Handling.

Keep Your Keys Safe#

Never expose secret keys in client code

Every Flint key is a secret that grants full API access. Keep keys on your server only. Never ship them in browser or mobile app code, and never commit them to source control.

  • Load keys from environment variables or a secrets manager, not from code.
  • If a key leaks, revoke it immediately (via the API or the dashboard) and roll out a replacement.
  • Grant each key the fewest scopes it needs, so a leak is contained.

For rotation policy, secret hygiene, and incident response in depth, see Key Security.

Next Steps#

Rate this doc