Skip to main content

Obtaining tokens

This page is the wire-level contract for the OAuth token endpoint.

Placeholder URL

The token endpoint URL in the samples below — https://auth.<environment>/oauth/token — is a placeholder. Use the real URL delivered to you at onboarding.

Token request

POST /oauth/token HTTP/1.1
Host: auth.<environment>
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&scope=screenings.read
ParameterRequiredNotes
grant_typeyesMust be client_credentials.
client_idyesIssued at onboarding.
client_secretyesIssued at onboarding. Bearer-equivalent — protect it.
scopeyesThe space-separated scopes you need. The only scope offered in v1 is screenings.read.

The endpoint conforms to RFC 6749 §4.4.

Token response

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "screenings.read"
}
FieldTypeNotes
access_tokenstring (JWT)The bearer token. Send it on every call to the REST API.
token_typestringAlways Bearer.
expires_inintegerSeconds until the token expires. Currently 3600 (1 hour).
scopestringThe scopes actually granted. May be a subset of what you requested.

JWT claims

Although the token is opaque from the API's point of view, it is deliberately a JWT so that you can decode it for diagnostics. The following claims are present and stable:

ClaimTypeMeaning
issstringIssuer. The token endpoint's base URL.
audstringAudience. The Listo REST API's identifier.
substringSubject. Typically equal to your client_id.
iatinteger (epoch s)Issued-at timestamp.
expinteger (epoch s)Expiry timestamp.
scopestringThe granted scopes, space-separated.
tenant_idstring (UUID)The single TenantId this token is scoped to. Each (client_id, client_secret) pair is provisioned for exactly one tenant; the minted token inherits that scope. The Sessions API requires the {tenantId} in the request path to equal this claim — a different value returns 403 forbidden. To query a different tenant, mint a token using that tenant's credentials.

Refresh strategy

Tokens are short-lived (1 hour). Recommended pattern:

  1. Cache the token in process memory. A single in-process variable is enough for most consumers; a small module-level singleton is fine.
  2. Reuse it until 60 seconds before exp. Compute the deadline once when you receive the token (expiresAt = now + (expires_in - 60) s).
  3. Re-mint, don't refresh. There is no refresh token. When your cached token is close to expiring, request a fresh one with the same client_credentials request.
  4. Don't mint a token per API call. It works, but it's a free round-trip you didn't have to pay for, and it creates avoidable load on the token endpoint.

Using the token

Send the token in the standard Authorization header on every REST call:

GET /v1/tenants/0d3e2b51-4e7a-44b6-8d5e-1c3a9b2f7c40/screenings?from=2026-05-08T00:00:00Z&to=2026-05-08T01:00:00Z HTTP/1.1
Host: api.<environment>
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
Accept: application/json

The {tenantId} segment must equal the token's single tenant_id claim. A different value returns 403 forbidden. To query a different tenant, mint a token using that tenant's (client_id, client_secret) pair.

If the token has expired, you'll get a 401 Unauthorized with the unauthenticated error code. Treat 401 as a signal to re-mint and retry exactly once.

Token-endpoint errors

The token endpoint follows RFC 6749 §5.2 for error responses. Most errors return 400 Bad Request; the exception is invalid_client, which returns 401 Unauthorized (with a WWW-Authenticate header) per the same section:

HTTP/1.1 401 Unauthorized
Content-Type: application/json

{
"error": "invalid_client",
"error_description": "Client authentication failed."
}
errorHTTPWhen
invalid_request400Required parameter missing, malformed, or duplicated.
invalid_client401client_id unknown or client_secret wrong.
unauthorized_client400The client is recognized but not allowed to use this grant.
invalid_grant400The supplied grant cannot be used.
unsupported_grant_type400grant_type is not client_credentials.
invalid_scope400The requested scope is not granted to your client.

Where to next