API referenceOAuthExchange a workload token

Exchange a workload token

⚠️
Preview — this feature is being built; the design below is the spec.
POST https://app.syntic.ai/v1/oauth/token

Trade a short-lived OIDC token from your platform (GitHub Actions, a cloud provider, Kubernetes) for a Syntic access token that acts as a service account. No API key is sent: the incoming JWT is the credential. The body may be JSON, as below, or application/x-www-form-urlencoded.

For how this fits into a full workflow, see Workload identity.

Headers

HeaderRequiredValue
syntic-betaYes, while in previewtoken-exchange-2026-11-01. Without it the call fails with 400 beta_required. See Versioning.
Content-TypeYesapplication/json

Body parameters

ParameterTypeRequiredNotes
grant_typestringYesurn:ietf:params:oauth:grant-type:token-exchange
subject_tokenstringYesThe signed OIDC token (a JWT) your platform gave the workload.
subject_token_typestringYesurn:ietf:params:oauth:token-type:jwt
service_accountstringYesThe service account id, starting with svcacct_.
requested_lifetime_secondsintegerNo60 to 3600, capped by the trust rule. Default 900.

Response

200 OK, Content-Type: application/json

{
  "access_token": "sat_eyJhbGciOi...",
  "token_type": "Bearer",
  "expires_in": 900,
  "issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
  "service_account": "svcacct_01JCB3D5F7H9K1M3P5R7",
  "workspace_id": "wrkspc_01JC0A2C4E6G8J0L2N4Q"
}
FieldTypeDescription
access_tokenstringStarts with sat_. Send it as Authorization: Bearer ... until it expires.
token_typestringAlways Bearer.
expires_inintegerSeconds until the token stops working.
service_accountstringThe service account the token acts as.
workspace_idstringThe workspace billed for its requests.

Examples

curl -sS https://app.syntic.ai/v1/oauth/token \
  -H "syntic-beta: token-exchange-2026-11-01" \
  -H "Content-Type: application/json" \
  -d '{
      "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
      "subject_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ii4uLiJ9...",
      "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
      "service_account": "svcacct_01JCB3D5F7H9K1M3P5R7"
    }'

Errors

This endpoint follows the OAuth 2.0 error format so standard libraries understand it: {"error": "invalid_grant", "error_description": "...", "request_id": "req_..."}. The type column below is empty because the OAuth error string is the code.

StatustypecodeWhen
400invalid_request_errorbeta_requiredThe syntic-beta header is missing.
400—invalid_requestA required field is missing or malformed.
400—unsupported_grant_typegrant_type is not token exchange.
400—invalid_grantSignature, expiry, issuer, audience or claims match no trust rule. The description names the first mismatch and never echoes the token.
401—invalid_clientThe service account does not exist or is disabled.
429—slow_downMore than 60 exchanges per minute for one service account.

Using the access token

sat_ tokens cannot be refreshed or revoked one by one. Exchange a fresh JWT when one expires; to cut off access immediately, disable the service account or its trust rule. Calls made with the token are billed to the service account’s workspace and recorded in the audit log under its name.

Was this page helpful?