Authentication
HAPI v3 uses the OAuth2 client-credentials grant. You exchange a client_id and client_secret for a short-lived bearer access token, then send that token on every request.
There is no long-lived API key on the wire. The access token is the only credential a request carries, and it expires after an hour, which keeps a leaked token from being useful for long.
Your credentials
| Value | Example | Description |
|---|---|---|
client_id | cid_live_… / cid_test_… | Public client identifier. The prefix tells you the environment. |
client_secret | sk_live_… / sk_test_… | The secret. Never send it from a browser or mobile app; keep it on your server. |
Get a token
Body parameters
| Name | Type | Required | Description |
|---|---|---|---|
grant_type | string | Yes | Must be client_credentials. |
client_id | string | Yes | Your client id. |
client_secret | string | Yes | Your client secret. |
scope | string | No | Space-separated scopes to narrow the token. Defaults to every scope the client holds. |
Credentials may go in the JSON body, or as HTTP Basic auth (client_id as the user, client_secret as the password):
curl -X POST https://api.hermeseus.com/v3/HAPI/auth/tokens \
-u "cid_live_…:sk_live_…" \
-H "Content-Type: application/json" \
-d '{ "grant_type": "client_credentials" }'Response
{
"access_token": "hat_live_…",
"token_type": "Bearer",
"expires_in": 3600,
"expires_at": "2026-08-27T17:36:36+00:00",
"scope": "flights:read flights:write wallet:read",
"environment": "live"
}| Field | Description |
|---|---|
access_token | The bearer token to send on every request. |
token_type | Always Bearer. |
expires_in | Seconds until the token expires (3600). |
expires_at | Absolute expiry, RFC 3339 UTC. |
scope | Space-separated scopes granted to this token. |
environment | test or live, from the credential. |
Use the token
Send it in the Authorization header:
Authorization: Bearer hat_live_…
Introspect a token
Confirm the office, environment, and scopes a token maps to.
{
"active": true,
"client_id": "cid_live_…",
"environment": "live",
"office": { "id": "off_2", "name": "Your Office" },
"scopes": ["flights:read", "flights:write", "wallet:read"],
"token_type": "Bearer",
"expires_at": "2026-08-27T17:36:36+00:00"
}Scopes
A token is limited to a set of scopes. Read endpoints need the :read scope for their product; endpoints that move money or create bookings need :write. A request missing a scope returns 403 insufficient_scope.
| Scope | Grants |
|---|---|
flights:read | Search flights, read offers, read orders. |
flights:write | Create orders, issue tickets, cancel, refund. |
hotels:read / hotels:write | The same split for hotels. |
activities:read / activities:write | The same split for activities. |
wallet:read | Read your office balance. |
Environments
A test credential operates against a sandbox wallet: searches are real, but bookings never move real funds. A live credential operates against your real office balance. Because the environment is part of the credential, you never point at a different host, you just authenticate with the matching client.
Authentication errors
| Status | Code | Meaning |
|---|---|---|
| 400 | missing_credentials | No client_id / client_secret supplied. |
| 400 | unsupported_grant_type | grant_type was not client_credentials. |
| 401 | invalid_client | The client id or secret is wrong. |
| 401 | missing_authorization | No bearer token on the request. |
| 401 | invalid_token | The token is unknown. |
| 401 | expired_token | The token has expired; get a new one. |
| 403 | insufficient_scope | The token lacks a scope the endpoint requires. |
See the errors reference for the full envelope.