Skip to content

OAuth

These endpoints support partner apps that merchants install, the Flint CLI, and workload identities. Partner apps and the CLI use the shared token and revocation endpoints. Workload identities exchange an external issuer's JWT for a short-lived access token at the token endpoint. See Partner OAuth for the hosted install flow and CLI OAuth for device login.

Client kinds#

The grant and client authentication select the flow:

  • Partner apps are confidential clients. They authenticate with HTTP Basic or with client_id and client_secret form fields, never both in one request. They use the authorization code grant with S256 PKCE, and the refresh token grant.
  • flint-cli is a public client. It sends client_id=flint-cli with no client_secret and no Authorization header. It uses the device code grant and the refresh token grant.
  • Workload identities use grant_type=urn:ietf:params:oauth:grant-type:token-exchange. They send a JWT from their configured external issuer and a workload_identity_id, without client authentication or client_id.

A request that sends client_id=flint-cli together with a secret is treated as a partner app and fails client authentication.

Requests#

Token and revocation requests use application/x-www-form-urlencoded bodies only. JSON bodies and URL query strings return 400 invalid_request. A repeated field returns invalid_request. Token exchange rejects unrecognized fields; partner and CLI requests ignore them.

Responses#

OAuth responses use flat protocol JSON from RFC 6749, RFC 7009, and RFC 8693, without the Flint envelope. Access tokens are Bearer tokens for every client. Each client reads scope in the vocabulary it requested: permission IDs for partner apps, and API scope names for flint-cli and workload identities.

Errors carry error and a fixed error_description. The description is text for people, not for parsing; branch on error. A failed client authentication returns 401 invalid_client, with WWW-Authenticate: Basic realm="flint" when the client used HTTP Basic.

Workload identity token exchange#

First create a workload identity that trusts your issuer and workload. Then call POST /v1/oauth/token with a JWT from that external issuer. Flint verifies the JWT's signature and claims, then matches it against that identity's trust rules. You don't send a Flint API key.

FieldRequiredValue
grant_typeYesurn:ietf:params:oauth:grant-type:token-exchange
subject_tokenYesYour external issuer's JWT, up to 8 KiB.
subject_token_typeYesurn:ietf:params:oauth:token-type:jwt or urn:ietf:params:oauth:token-type:id_token.
workload_identity_idYesThe wid_ identity whose trust rules and scopes apply.
scopeNoSpace-separated subset of the identity's API scopes. Omit to request all its current scopes.
requested_token_typeNourn:ietf:params:oauth:token-type:access_token. No other value is supported.

Don't send an Authorization header, client_id, client secrets or assertions, actor_token, resource, or audience. These and any other unrecognized fields return invalid_request. The identity already fixes the accepted issuer and audience, merchant, mode, and sandbox.

cURL
curl https://api.withflintpay.com/v1/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
  --data-urlencode "subject_token=$OIDC_TOKEN" \
  --data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:jwt" \
  --data-urlencode "workload_identity_id=wid_1kmn0aExample" \
  --data-urlencode "scope=commerce.products.read"
JSON
{
  "access_token": "fwat_example",
  "issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "commerce.products.read",
  "workload_identity_id": "wid_1kmn0aExample",
  "merchant_id": "mer_1kmn0aExample",
  "mode": "test",
  "sandbox_id": "test_1kmn0aExample"
}

The opaque fwat_ access token expires after the shorter of 3,600 seconds and the JWT's remaining lifetime. The response never contains a refresh token. Live-mode responses omit sandbox_id. Exchange another valid issuer token when you need a new access token; the same unexpired issuer token can be exchanged more than once.

Send the returned token as Authorization: Bearer fwat_... on API requests. Don't combine it with another credential. The token stays bound to the identity's merchant, mode, and sandbox. If you send Flint-Merchant-Id, Flint-Mode, or Flint-Sandbox-Id, each must match that binding; a mismatch returns 403 FORBIDDEN with reason oauth_context_mismatch.

Deactivating or archiving an identity invalidates its access tokens on the next request. Narrowing its scopes also takes effect on the next request, including for tokens issued earlier. A token can never regain scopes excluded by its original exchange. GET /v1/developer/auth-context identifies the caller with auth_type: workload_identity, workload_identity_id, name, and expires_at.

Exchange errors and limits#

StatusOAuth errorAction
400invalid_requestCheck required fields, token types, content type, and forbidden or repeated fields.
400invalid_grantCheck that the identity is active and the issuer token is valid and matches its trust rules.
400invalid_scopeRequest only scopes currently granted to the identity.
400unsupported_grant_typeUse a grant advertised in discovery.
429temporarily_unavailableWait at least Retry-After seconds; Flint-Rate-Limited-Reason identifies the exhausted budget.
503temporarily_unavailableIssuer verification keys are unavailable. Retry later.
500server_errorRetry later and retain X-Request-Id when contacting Flint Help.

Token exchange allows 300 requests per minute per IP and 600 per minute per workload identity. The identity budget counts requests only after signature verification. Each invalid_grant failure has a fixed description; don't parse descriptions as error codes.

Discovery#

Flint publishes authorization server metadata (RFC 8414) at https://api.withflintpay.com/.well-known/oauth-authorization-server. It lists the endpoints, the supported grant types, and S256 as the only PKCE method. Both discovery paths and the public key set are documented in OAuth discovery and signing keys. These paths are outside the /v1 API.

Note:

The Partner app installs guide walks through PKCE, token exchange, refresh, and revocation for partner apps.

Revoke an OAuth token#

POST/v1/oauth/revoke

No API key required

RFC 7009 token revocation for partner apps and flint-cli. Partner apps authenticate with HTTP Basic or client_secret. Revoking a partner refresh token ends its token family, meaning the refresh token and the access tokens issued from it. Revoking a partner access token invalidates every outstanding access token for its environment grant; the refresh token keeps working. Unknown, expired, or other clients' tokens return 200. Revocation doesn't change the install or grant. For flint-cli, revoking an access or refresh token ends its CLI session and invalidates the session's tokens. The public client sends client_id=flint-cli without a secret or Authorization header. Send application/x-www-form-urlencoded; success has an empty body.

curl -X POST https://api.withflintpay.com/v1/oauth/revoke \
  -H "Flint-Version: 2026-09-07" \
  -H "Authorization: Bearer YOUR_API_KEY"

Exchange an OAuth token#

POST/v1/oauth/token

No API key required

Partner apps exchange authorization codes with S256 PKCE or rotate refresh tokens for installation-scoped Bearer access tokens. A lost refresh response can be retried for 60 seconds while the replacement remains unused; a replay revokes that environment grant's token family. Authorization codes last 5 minutes and are single use. Partner access tokens last 1 hour; refresh tokens expire 30 days after issuance and rotate on each use. This endpoint follows OAuth token endpoint conventions: send application/x-www-form-urlencoded only. Failures return OAuth token error objects instead of the normal Flint error envelope. The public client flint-cli supports the RFC 8628 device_code grant and rotating refresh tokens. Pending device requests return authorization_pending; early polling returns slow_down and increases the required interval by five seconds; denial returns access_denied; expired or consumed codes return expired_token. CLI access tokens last 15 minutes. CLI sessions expire after 30 days idle or 90 days total. Reusing a rotated CLI refresh token revokes its family, including when the previous response was lost. A fresh browser login is then required. With session_mode=contexts at CLI authorization, tokens include oauth_session_id and context_id. CLI refresh requires an explicitly authorized context_id; invalid_context and context_access_denied do not consume the refresh token. Each CLI access token is limited to one context. Ordinary CLI rotation preserves other unexpired access tokens; removing consent immediately invalidates affected tokens. CLI refresh scope may request a subset of the selected grant or context scopes. Only the issued access token is narrowed; consent and other tokens are unchanged. A later refresh without scope restores the full consented scopes. Legacy CLI sessions retain their original response shape. Request rate limits return HTTP 429 temporarily_unavailable with Retry-After and Flint-Rate-Limited-Reason. Device-code token polling returns HTTP 400 slow_down for both early polls and request rate limits. Workload identities use grant_type=urn:ietf:params:oauth:grant-type:token-exchange with subject_token, subject_token_type, and workload_identity_id. subject_token_type must be urn:ietf:params:oauth:token-type:jwt or urn:ietf:params:oauth:token-type:id_token. Optional scope narrows the identity scopes for this token; requested_token_type, if supplied, must be urn:ietf:params:oauth:token-type:access_token. Client authentication, client_id, actor_token, resource, audience, and unrecognized fields are forbidden and return invalid_request. The response includes an opaque fwat_ access token, issued_token_type, workload_identity_id, merchant_id, mode, and sandbox_id in test mode, without a refresh token. The token expires after the shorter of one hour and the remaining subject-token lifetime. Identity deactivation, archive, and scope narrowing take effect on the next API request. Exchange errors use invalid_request, invalid_grant, invalid_scope, unsupported_grant_type, temporarily_unavailable, and server_error. Limits are 300 requests per minute per IP and 600 per minute per identity after signature verification; exceeding either returns HTTP 429 temporarily_unavailable with Retry-After and Flint-Rate-Limited-Reason. No usable external issuer keys returns HTTP 503 temporarily_unavailable.

Response · 200

access_tokenstringRequired
context_idstring
environment_grant_idstring
expires_inintegerRequired
issued_token_typeenum

Present for workload identity token exchange. Identifies the issued credential as an access token.

  • urn:ietf:params:oauth:token-type:access_token
merchant_idstring
modeenum
  • test
  • live
oauth_session_idstring
partner_app_idstring
partner_app_install_idstring
refresh_tokenstring

Rotating refresh token for partner apps and flint-cli. Absent for workload access tokens.

sandbox_idstring

Present exactly when mode is test.

scopestringRequired

Space-separated granted scope, in the vocabulary the client requested: permission IDs for partner apps, API scope names for flint-cli and workload identities. Empty when a workload identity has no scopes.

token_typeenumRequired
  • Bearer
workload_identity_idstring

Workload identity that authorized the token exchange. Present only for workload access tokens.

curl -X POST https://api.withflintpay.com/v1/oauth/token \
  -H "Flint-Version: 2026-09-07" \
  -H "Authorization: Bearer YOUR_API_KEY"

Was this helpful?