> For the complete documentation index, see [llms.txt](https://adrasis.gitbook.io/developer/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://adrasis.gitbook.io/developer/authentication.md).

# 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:

```bash
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. |

### 2. Read the response

A successful exchange returns `200 OK`:

```json
{
  "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:

```json
{
  "error": "invalid_client",
  "error_description": "Client authentication failed."
}
```

| `error`                  | HTTP | Cause                                                                                                                                                                                               |
| ------------------------ | ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `invalid_request`        | 400  | A required form field (`grant_type`, `client_id`, `client_secret`) was missing, or the body was not form-encoded. `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. The body carries an extra `requested_scope` array holding what you asked for, so you can diff it against your granted set.                |
| `invalid_client`         | 401  | Unknown `client_id`, wrong secret, or a disabled/revoked credential. 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:

```bash
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:

```http
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:

```http
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:

```json
{
  "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](/developer/errors.md). 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/place-types`, `GET /places`, `GET /properties`, `GET /properties/{public_id}`, `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 Distribution API Credentials screen can also issue **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

* [Getting started](/developer/getting-started.md): Make the first authenticated call.
* [The booking flow](/developer/booking-flow.md): Review the endpoints these scopes gate.
* [Booking webhooks](/developer/booking-webhooks.md): Configure signed push notifications.
* [Errors](/developer/errors.md): See the full status-code reference.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://adrasis.gitbook.io/developer/authentication.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
