Authentication & Access

OAuth 2.0 client credentials, credential-bound tenancy, and the auth failures you can hit.

The WestIQ Platform API uses the OAuth 2.0 client credentials grant for server-to-server access — there is no user/password or browser-redirect flow. Each approved partner receives a client_id and client_secret during onboarding (delivered through your onboarding pack, never over email or in these docs).

Treat your client_secret like a password. Store it in a secrets manager, never in source control, and rotate it if you suspect exposure.

1. Get a token

Exchange your client credentials for a short-lived bearer access token at the staging token endpoint:

POST https://auth.staging.westiq.ai/oauth2/token

Authenticate the token request itself with HTTP Basic (client_id:client_secret), and request the westiq/search scope in the form body:

$curl -X POST "https://auth.staging.westiq.ai/oauth2/token" \
> -u "$WESTIQ_CLIENT_ID:$WESTIQ_CLIENT_SECRET" \
> -H "Content-Type: application/x-www-form-urlencoded" \
> -d "grant_type=client_credentials&scope=westiq/search"
1{
2 "access_token": "eyJraWQiOi…",
3 "expires_in": 3600,
4 "token_type": "Bearer"
5}

If your HTTP client can’t send Basic auth, client_id and client_secret may instead go in the form body alongside grant_type and scope — both styles are accepted.

Send the token on every request as Authorization: Bearer <token>, and cache it until expires_in (about an hour) elapses rather than requesting one per call.

Only access tokens are accepted. Sending an ID token — or any token whose token_use is not access — is rejected with 401 auth.invalid_token.

To use the API Reference playground, fetch a token with the curl above and paste the access_token value into the playground’s auth field — every “Try it” request then sends it as the bearer. The token endpoint itself is documented in the reference too, under Authentication.

2. Your credential IS your airline

Every partner credential is bound to its airline on the WestIQ side: the token’s client_id is resolved through a server-side mapping to your airline_id. You never send an airline identifier — not as a header, not as a body field, not as a token claim. There is nothing to configure and nothing to get wrong.

This is a deliberate isolation property, not a convenience:

  • A request can only ever act as the airline its credential is bound to. Tenancy cannot be forged, because the request carries no tenancy input.
  • A credential with no airline mapping is rejected with 403 auth.unknown_client — never resolved to a default tenant.
  • Your searches see your airline’s catalog namespace plus the shared global namespace, and nothing else.

You can observe the resolution in every search response: parsed.filters.airlineId is the airline your credential resolved to.

3. One scope: westiq/search

Every /v1 endpoint — search, events, and all of the personalization endpoints alike — requires the single westiq/search scope on the access token. There are no per-endpoint scopes. Request it in the token call (as in the example above). A token without it is rejected with 403 auth.insufficient_scope.

4. Some endpoints also need a session locator

Authentication gets you in; it does not by itself identify a passenger session. The session-scoped personalization endpoints additionally require an X-Session-Id header (or, for the SSE routes, a session id in the path), and the per-member calls take seatId / memberSlot locators. Those mechanics — which endpoints, the exact value rules, and the 400 session.unresolved you get without them — are covered in Sessions & Member Locators.

5. Auth failures you will see

All auth failures return the standard error envelope:

HTTPCodeCause
401auth.invalid_tokenMissing, malformed, expired, bad-signature, or wrong-issuer token — or an ID token instead of an access token
403auth.insufficient_scopeToken valid, but the westiq/search scope is absent
403auth.unknown_clientToken valid, but the client has no airline mapping

Unmatched routes also return 401, by design (the API denies by default). If a request 401s unexpectedly, check the path before debugging your token.

Rate limits

Two independent layers, tied to different identities:

  • Per client credential — AI-backed /v1 endpoints enforce a per-client, per-endpoint-class request-rate limit. Over it you get 429 rate.limited (standard error envelope) with a Retry-After header. Back off for that many seconds.
  • Per IP, at the edge — the platform edge applies a limit of 2000 requests per 5-minute window. Over it, requests are blocked at the edge with a plain 403 — without the API’s error envelope — and the block clears on its own once your request rate drops.

Both layers, how to tell the responses apart, and the endpoint classes are covered in Errors & Limits.