Flavours Partner API
Integrations

Token lifecycle

How long a token lives, how to cache and refresh it, and what happens on rotation and revocation.

Lifetime

An access token is valid for exactly one hour. The token response says so: "expires_in": 3600. There is no refresh token, because the client credentials grant does not issue one. To get a new token, repeat the token request with the same client ID and client secret.

The pattern

  1. Request a token once and keep it in memory with its expiry time.
  2. Reuse it until it is about 60 seconds from expiry. Then request a new one.
  3. If a call made with the token returns 401, discard the token, request a new one, and retry once. Do not retry in a loop.
  4. Never request a token for every call.

Token requests are throttled

The token endpoint allows 10 requests per minute for each client ID, and 30 requests per minute for each IP address. Failed attempts count too. Above the limit it returns 429. See Errors.

Cache and refresh

These samples keep one token per process and request a new one 60 seconds before it expires. They do not call any business endpoint, because none accepts the token yet. Call get_token() wherever your code needs a token.

import os
import threading
import time

import requests

TOKEN_URL = "https://api.flavours.sa/dev/oauth/token"
REFRESH_MARGIN = 60  # seconds

_lock = threading.Lock()
_token = None
_expires_at = 0.0


def _request_token():
    response = requests.post(
        TOKEN_URL,
        data={
            "grant_type": "client_credentials",
            "client_id": os.environ["FLAVOURS_CLIENT_ID"],
            "client_secret": os.environ["FLAVOURS_CLIENT_SECRET"],
        },
        timeout=10,
    )
    response.raise_for_status()
    return response.json()


def get_token(force=False):
    """Return a valid access token, requesting a new one when needed."""
    global _token, _expires_at
    with _lock:
        if force or _token is None or time.time() >= _expires_at - REFRESH_MARGIN:
            body = _request_token()
            _token = body["access_token"]
            _expires_at = time.time() + body["expires_in"]
        return _token

On a 401 from an endpoint that accepts the token, call get_token(force=True) (Python) or getToken({ force: true }) (JavaScript), then retry the call once.

Rotation

Rotating the secret issues a new client secret for the same client ID.

  • The old secret stops working immediately.
  • Every access token already issued to the integration is revoked immediately, even if it has not expired.
  • Tokens of other integrations are not affected.
  • The new secret is shown once. Rotating needs the account password, unless it was confirmed within the last 10 minutes.

Update the stored secret in your system, then request a new token.

Revocation

Revoking permanently disables the integration. The client ID and secret stop working. A revoked integration cannot be rotated or edited, and it cannot be reactivated. To reconnect, create a new integration.

What your client sees

After a rotation, the old secret is rejected. After a revocation, every secret is rejected. In both cases the token endpoint answers a token request with the same response as for any wrong credential:

{
  "error": "invalid_client",
  "error_description": "Client authentication failed"
}

This is HTTP 401. The response does not say whether the secret is wrong, was rotated, or the integration was revoked. Check in the Flavours web app: the integration list shows each integration as Active or Revoked.

If your system receives invalid_client after it has worked before, stop retrying, alert an operator, and check whether the secret was rotated or the integration revoked. Retrying only consumes the rate limit.

On this page