Mint an access token
const url = 'https://api.aerandil.com/oauth/token';const options = { method: 'POST', headers: {'Content-Type': 'application/json'}, body: '{"client_id":"example","client_secret":"example","grant_type":"client_credentials","scope":"example"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://api.aerandil.com/oauth/token \ --header 'Content-Type: application/json' \ --data '{ "client_id": "example", "client_secret": "example", "grant_type": "client_credentials", "scope": "example" }'OAuth 2.1 client_credentials grant — exchange a client_id + client_secret for a short-lived bearer token whose scopes claim narrows the issued token to a subset of the client’s pre-provisioned scopes.
Deviation from RFC 6749 §4.4.2. Aerandil accepts the grant parameters as a JSON body (application/json), not the application/x-www-form-urlencoded body the RFC mandates. This is an explicit operational choice — every other Aerandil endpoint takes JSON, and SDK generators emit cleaner code for one wire format than two. Modern SaaS APIs (Stripe, Auth0 management API) have made the same call. RFC-conformant integrations generally fail open against a JSON token endpoint; if they don’t, the integrator wraps a per-PMS shim.
Public endpoint — no bearer required. The HTTPS transport is the credential carrier; the request body is intentionally not authenticated by a prior token (that would be a chicken-and-egg).
Request Bodyrequired
Section titled “Request Bodyrequired”OAuth 2.1 client_credentials grant body. JSON-encoded, not form-urlencoded — see /oauth/token’s endpoint description for the rationale.
object
Verified against the client’s argon2id-hashed secret.
Aerandil supports only client_credentials in v1. Any other value yields 422 unsupported_grant_type.
Optional space-separated scope filter. When present, the issued token’s scope is the intersection of the filter and the client’s pre-provisioned scopes — a way to narrow per-request without rotating the client’s permanent grant.
Responses
Section titled “Responses”Token minted. scope is the granted set — intersection of the client’s pre-provisioned scopes with the scope request parameter (when present).
object
Short-lived HS256 JWT. Send as Authorization: Bearer <token> on subsequent calls.
Token lifetime in seconds. Default 3600 (one hour); deployment-configurable via AERANDIL_TOKEN_TTL_SECS.
Space-separated list of granted scopes. The set the token actually carries — already narrowed by any scope request filter and the client’s pre-provisioned grants.
Always Bearer in v1.
Example
{ "token_type": "Bearer"}Authentication failed. Returned for unknown client_id and for client_secret mismatch alike — the two failure modes are deliberately indistinguishable to prevent client_id enumeration.
Structured error wrapper. Every non-2xx response from any Aerandil endpoint serialises to this shape; the HTTP status carries the failure class (4xx/5xx), the body adds a stable code for programmatic dispatch plus a human-readable message.
object
object
Stable, programmatic error code (e.g. unauthorized, forbidden, not_found, invalid_body, invalid_url, precondition_failed, no_secondary_key). Clients dispatch on code, not on the message (which may be localised or contextualised in future versions).
Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.
Human-readable explanation of the failure.
Per-error structured detail. The shape varies by code (e.g. forbidden_action carries {"action": "..."}; precondition_failed carries {"current_version": N}).
Examplegenerated
{ "error": { "code": "example", "instance_id": "example", "message": "example", "parameters": "example" }}Request body invalid — unsupported grant_type (only client_credentials is accepted), malformed JSON, or unknown body fields.
Structured error wrapper. Every non-2xx response from any Aerandil endpoint serialises to this shape; the HTTP status carries the failure class (4xx/5xx), the body adds a stable code for programmatic dispatch plus a human-readable message.
object
object
Stable, programmatic error code (e.g. unauthorized, forbidden, not_found, invalid_body, invalid_url, precondition_failed, no_secondary_key). Clients dispatch on code, not on the message (which may be localised or contextualised in future versions).
Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.
Human-readable explanation of the failure.
Per-error structured detail. The shape varies by code (e.g. forbidden_action carries {"action": "..."}; precondition_failed carries {"current_version": N}).
Examplegenerated
{ "error": { "code": "example", "instance_id": "example", "message": "example", "parameters": "example" }}