Skip to content

Authentication and permissions

integration secret key ──> POST /v1/auth/login ──> access_token + refresh_token
│
POST /v2/auth/refresh <─────────┘ when expired

Overview: the secret key is exchanged for a token pair via POST /v1/auth/login, requests are sent with access_token, when it expires the pair is refreshed via POST /v2/auth/refresh, and if the refresh fails a new sign-in with the secret key is performed

Of the methods in this documentation, only these two work without a token. All other requests are sent with the header:

Authorization: Bearer <access_token>

Request with a token: GET with the Authorization Bearer access_token header, a valid token gets 200, an expired one gets 401 Token is expired

For sign-in with the secret key the lifetimes are set in code and do not depend on company settings:

Token Lifetime In the login response
access_token 15 days expires_in: 1296000 (seconds)
refresh_token 30 days returned in the refresh_token field; it has no expires_in of its own
Окно терминала
curl -X POST 'https://api-admin.billz.ai/v2/auth/refresh' \
-H 'Content-Type: application/json' \
-H 'platform-id: 7d4a4c38-dd84-4902-b744-0488b80a4c01' \
-d '{ "refresh_token": "..." }'

The response contains a new pair of tokens with the expires_in and refresh_expires_in fields. The old refresh token can no longer be used.

Refreshing the token pair: POST /v2/auth/refresh with refresh_token, the response contains a new access_token and refresh_token, the old pair is replaced

Refresh errors come with code 400 (RFC 6749 §5.2): 401 from a regular method means an expired access token, 400 from the refresh means an unusable refresh token. The error field carries the standard code: invalid_request, invalid_grant, unauthorized_client; an internal failure is 500 with server_error.

Key access is built from two independent levels.

Level 1 — route. The pair “HTTP method + route template” must be present in the fixed list of routes, which is the same for all companies. The full list: what an integration key can do.

Level 2 — role permission. The key has its own role. Its access switches are checked while an allowed request is executed and decide what the response contains and whether the operation goes through: key role permissions.

The route template (/v2/transfer/:id/add) and the HTTP verb are compared: PUT where PATCH is registered does not pass; ID values do not affect the check.

Permissions are cached for 20 minutes; changing the role resets the cache.

The error body is usually {"code": ..., "message": ..., "error": ..., "data": null}. The cause is distinguished by the message + error pair:

Code message / error What happened
401 token error the token cannot be parsed: truncated, corrupted, foreign signature
401 token expired / token has been expired expired — a refresh is required
401 unauthorized / refresh token used as access a refresh token was put in Authorization
401 unauthorized / session not found, session revoked the session is closed or revoked — a new login is required
401 unauthorized / mismatch platform-id with token info platform-id differs from the one the token was issued with
401 unauthorized / user is not active, user has been expired the key is disabled or has expired
403 forbidden / access denied the route is not in the list available to the key
403 blocked / company is blocked the whole company is blocked

These cases are covered in 403 and 404.

Client procedure on 401: refresh the pair while the refresh token is valid, otherwise sign in again with the secret key, then repeat the original request.

Handling 401: if refresh_token is valid, send POST /v2/auth/refresh, otherwise sign in again with the secret key, then repeat the original request

  • Store the secret key in secure storage.
  • Refresh access_token before it expires.
  • Perform the refresh from a single place.
  • Create a separate key for each integration.