Adrasis™

Authentication

Authenticate with the OAuth 2.0 client-credentials grant: request a token, send the bearer, manage expiry, and apply scopes. Basic and API-key alternatives included.

The recommended mechanism is OAuth 2.0 with the client_credentials grant. You exchange a client_id + client_secret for a short-lived bearer token and send that token on every API call. HTTP Basic and API-key credentials are also offered for callers that cannot run a token exchange.

OAuth 2.0 client-credentials flow

1. Request a token

Send a form-encoded POST to the token endpoint, on the same host and /api/v1 prefix as every other call:

curl -s -X POST \
  https://ari.console.adrasis.com/api/v1/oauth/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials' \
  -d 'client_id=YOUR_CLIENT_ID' \
  -d 'client_secret=YOUR_CLIENT_SECRET'

Request fields (all form-encoded, application/x-www-form-urlencoded):

Field Required Notes
grant_type yes Must be client_credentials. Any other value is rejected with unsupported_grant_type.
client_id yes Issued at credential creation.
client_secret yes Issued at credential creation; shown once.
scope no Space-delimited subset of your granted scopes to down-scope this token. Omit to receive your full granted set.

Your client library may instead send client_id and client_secret as HTTP Basic credentials, as RFC 6749 section 2.3.1 describes: Authorization: Basic followed by the base64 of client_id:client_secret, each form-encoded first. The body then carries only grant_type and, if you want it, scope. Use one of the two ways on a request, not both: a request with both answers invalid_request.

curl -s -X POST \
  https://ari.console.adrasis.com/api/v1/oauth/token \
  -u 'YOUR_CLIENT_ID:YOUR_CLIENT_SECRET' \
  -d 'grant_type=client_credentials'

2. Read the response

A successful exchange returns 200 OK:

{
  "access_token": "<ACCESS_TOKEN>",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "distribution:read distribution:booking",
  "issued_at": 1748851200
}
Field Meaning
access_token The signed bearer token. Treat it as opaque.
token_type Always Bearer.
expires_in Token lifetime in seconds (3600 = one hour).
scope The space-delimited scopes actually granted to this token.
issued_at Epoch seconds at which the token was issued.

A failed exchange returns a standard OAuth error envelope:

{
  "error": "invalid_client",
  "error_description": "Client authentication failed.",
  "requested_scope": null
}
error HTTP Cause
invalid_request 400 A required form field (grant_type, client_id, client_secret) was missing, the body was not form-encoded, or the client credentials came both in the Authorization header and in the body. error_description names the missing fields.
unsupported_grant_type 400 grant_type was not client_credentials.
invalid_scope 400 Requested scope is not a subset of your granted scopes. requested_scope, present on every token error and null on the others, is here an array holding what you asked for, so you can diff it against your granted set.
invalid_client 401 Unknown client_id, wrong secret, a disabled/revoked credential, or a Basic header that cannot be read. All map to the same status, the same error, and the same error_description, so the body cannot be used to tell them apart.

3. Send the bearer token

Attach the token to the Authorization header on every Distribution call:

curl -s \
  https://ari.console.adrasis.com/api/v1/properties \
  -H 'Authorization: Bearer <ACCESS_TOKEN>'

The token carries your partner-account identity and granted scopes. The service verifies both the token and its live credential/account binding on every request. There is no session or tenant header to send.

Partner account and profile selection

A credential always authenticates one partner account. Credentials are owned by the account, not by an individual profile, so you do not need a different credential for each catalogue audience.

Profile-scoped REST operations accept this optional header:

X-Adrasis-Partner-Profile-Id: <16-character-profile-public-id>

Omit the header on the first request to select the account's stable Default profile. Send it to select another profile owned by the same account. Every successful profile-scoped response returns the exact identity used:

X-Adrasis-Resolved-Partner-Profile-Id: <16-character-profile-public-id>

Persist opaque cursors, selection proofs, and prebook tokens unchanged. Each one seals the profile that created it, so a continuation can restore that profile without the header. If you repeat the header, it must match the sealed identity. Unknown or cross-account profiles return 404; inactive explicit profiles return 403; a temporarily unavailable profile resolver returns retryable 503.

GET /bookings is the reporting exception. It is account-wide and accepts profile_public_id as an optional query filter for the immutable originating profile, including a profile disabled after booking. It does not use the profile-selector header.

4. Handle expiry

Tokens are valid for expires_in seconds (3600). No refresh token is issued for the client-credentials grant. When a token expires, request a new one from the token endpoint with the same client_id / client_secret.

A missing, malformed, expired, or signature-invalid token returns 401 with a WWW-Authenticate: Bearer challenge header and one of these codes. The same three codes cover the Basic and API-key mechanisms below, so a client that handles them once handles every mechanism:

{
  "error": {
    "code": "CREDENTIAL_UNAUTHORIZED",
    "message": "Credential is missing, invalid or revoked",
    "trace_id": "01J8ZC4M2P6QK5R7T9VXA3B1DN",
    "retryable": false
  }
}
code Meaning
CREDENTIAL_MISSING No Authorization header was sent.
CREDENTIAL_MALFORMED The header was present but its scheme or shape was not recognised.
CREDENTIAL_UNAUTHORIZED The credential was well-formed but rejected because it was unknown, revoked, disabled, expired, or signature-invalid.
INVALID_TOKEN Not a credential failure, and not a 401. The book endpoints answer 400 INVALID_TOKEN when your credential is accepted but the request body carries a prebook_token that is malformed, tampered with, or minted for a different caller. Refreshing the access token does not help; prebook again.

Every Distribution endpoint uses UPPER_SNAKE codes inside the standard error envelope described in Errors. The token exchange in step 2 is the one exception: it returns the OAuth error envelope above, as its own specification requires. Branch on code; the message is human-readable and may change between releases.

CREDENTIAL_UNAUTHORIZED is deliberately one code rather than several. Which verification step failed is not disclosed, so an attacker cannot use the error body to distinguish "this client exists but the secret is wrong" from "this client does not exist". Treat all three as "obtain a fresh credential or token and retry"; none of them is worth an automatic retry with the same input.

A practical strategy is to cache one token in memory and refresh it slightly before expires_in elapses (for example, at ~90% of the window) rather than requesting a token per call.

Scopes

Two scopes gate the surface. A token must carry the scope an endpoint requires, or the request returns 403 with code: INSUFFICIENT_SCOPE.

Scope Gates
distribution:read GET /countries, GET /reference/categories, GET /reference/attributes, GET /reference/place-types, GET /reference/currencies, GET /places, GET /places/{place_id}, GET /properties, GET /properties/{public_id}, the transfer, tour and flight catalogue reads (GET /service-zones, GET /transfer-vehicle-types, GET /tours, GET /flights and their /{public_id} details), GET /catalog/changes, POST /properties/availability, POST /places/availability, POST /properties/{property_public_id}/offers/search, POST /availability/check
distribution:booking POST /prebook, POST /book, GET /bookings, GET /bookings/{public_id}, POST /bookings/{public_id}/cancellation-quote, POST /bookings/{public_id}/cancel, every /webhook-endpoints* operation, and every product-vertical endpoint (/transfer/*, /tour/*, /flight/*, /package/*, /trip), including the vertical search steps

A read-only integration only needs distribution:read. That covers both the bulk and single-property accommodation searches, availability check, and the content feed. The vertical search endpoints (/transfer/search, /tour/search, /flight/search, /package/search) sit under distribution:booking. A full booking integration needs both scopes. Use the scope request parameter to down-scope a token when a worker only needs a subset (for example, mint a read-only token for a search service even though the credential is granted both).

Alternative mechanisms

The seller can also issue, from Connectivity > Sales channels > API partners in its Console, HTTP Basic and API-key credentials for callers that cannot run a token exchange. Unlike OAuth, these do not use the token endpoint. The credential material is sent directly on each call (Basic on the Authorization: Basic header; an API key with the adr_ prefix as Authorization: Bearer adr_...). They are gated by the same distribution:read and distribution:booking scopes. OAuth 2.0 is recommended for new integrations because tokens are short-lived and the long-term secret never travels on data requests.

Next steps