Skip to main content
A service account is an identity for software, not for a person: a CI job, a trading bot or a long-running integration. It belongs to one workspace, holds its own role and owns its own API keys. Your backend can send a service-account key as X-API-Key, like any key (Authentication), or exchange it for a short-lived bearer token with the standard OAuth 2.0 client-credentials grant, so an OAuth client library can authenticate without TetraFi-specific code.
The API may change until launch. Every change is listed in the Changelog.

Create a service account

  1. In the app, open Identity & access, then Service accounts.
  2. Select Create service account, give it a Name and choose its Role. Description and Expires are optional.
  3. Copy the account’s user ID: it is the client_id of the token request.
  4. Open the account, then API keys, and select New key. In Create service-account key, give it a Key name and select Create key.
  5. Copy the key. It is shown once and is the client_secret of the token request. Store it in your secret manager, never in source code or a tracked file.
You must be a workspace owner or admin with permission to manage credentials. Service accounts and their keys are managed only from a signed-in session: no API key or token can create another credential.

Its sponsor

Every service account has a sponsor: the member who is accountable for it, by default the one who created it. The account can never do more than its sponsor can do now: its permissions are its role intersected with the sponsor’s current permissions, checked on every request. If the sponsor’s own permissions shrink, the account’s shrink with them. When the sponsor stops being a current, unsuspended member of the workspace, the account’s keys can no longer trade from organisation wallets: those orders, and later steps of a trade already placed, are refused with 409 SERVICE_ACCOUNT_SPONSOR_NOT_CURRENT. Give the account a current member as sponsor to resume.

Exchange the key for a token

Send a form-encoded POST /api/v1/oauth/token (RFC 6749, section 4.4): You can send client_id and client_secret in an HTTP Basic Authorization header instead of the form. The answer is the standard token response: There is no refresh token: when a token is about to expire, exchange the key again. Keep tokens in memory only; the key is the credential to protect. The script samples/http/oauth.sh runs every step on this page:
Output on test networks

Check what the token can do

GET /api/v1/me with the token answers for the key it was exchanged from, the same way it answers that key sent as X-API-Key (Send the key): organisationWallets is the key’s organisation-wallet trading. Its blockedBy, when present, is the code an organisation-wallet order would be refused with now (Check what a key can trade from).
Output on test networks
The scopes can shrink while a token is valid: /me reads them live, so call it again instead of trusting the token answer’s scope for the token’s whole life.

Send the token

Send the token as Authorization: Bearer <access_token> on every request, in place of X-API-Key. It works in the service account’s workspace only.
Output on test networks

When the exchange is refused

A refused exchange answers the RFC 6749 error body, {"error", "error_description", "error_uri"}, not the JSON error body of other API refusals:
  • invalid_client, 401. The client_id or client_secret is wrong, or the key is not an active key of an active service account. The answer carries a WWW-Authenticate: Basic challenge. Check both values, and create a new key if this one was revoked or has expired.
  • invalid_grant, 400. grant_type is missing, or the form cannot be read. Send the three fields, form-encoded.
  • unsupported_grant_type, 400. grant_type is not client_credentials.
  • unauthorized_client, 400. The key may not be exchanged: it requires request signing or holds venue trading, and error_uri ends with #SIGNED_KEY_NOT_EXCHANGEABLE. Use another key of the account, or send this one as a signed X-API-Key (Sign requests).
  • temporarily_unavailable, 503. TetraFi could not read the account’s current authority. Retry shortly.
A key with request signing on is never exchanged, because a bearer token cannot carry a request signature:
Output on test networks
A token used after expires_in has passed is refused with 401 and the challenge WWW-Authenticate: Bearer error="invalid_token" (RFC 6750): exchange the key for a new one. A refused API key carries no challenge.