--- name: Trip Ninja — rotate the 90-day access token description: >- Exchange a refresh token for a fresh access token before the 90-day expiry, and read the environment flags on the response so you never point sandbox credentials at production (or DataStream credentials at SmartFlights). api: openapi/webjet-tripninja-adminpanel-refresh-token-openapi.yml operations: - 'POST /adminpanel/refresh-token/' generated: '2026-07-28' method: generated source: >- openapi/webjet-tripninja-adminpanel-refresh-token-openapi.yml, https://devhub.tripninja.io/smartflights/authentication/, https://github.com/trip-ninja-inc/tn-sdk-python --- # Trip Ninja — rotate the 90-day access token > `operationId` is `""` in the published document. Anchor on METHOD + PATH. This is a **credential-rotation** operation. Treat it as safety-critical: it mints new long-lived secrets and invalidates the refresh token you used. Do not run it autonomously on a schedule without a human-approved secret store to write into. ## Step 1 — POST /adminpanel/refresh-token/ Body is `RefreshTokenRequest` — both members required: - `access_token` — the current (possibly expired) access token. - `refresh_token` — the current refresh token. ## Step 2 — read `RefreshTokenResponse` and store it - `access_token` — the new token. Present it as `Authorization: Token ` on every call. - `refresh_token` — **a NEW refresh token is issued every time.** The old one is spent. Persist the new one atomically or you lock yourself out and have to go back to the Admin Panel. - `expiry_date` — when the new access token stops working, `YYYY-MM-DDThh:mm:ss.SSSSSSZ` in UTC. - `created_date` — issue time. The access token expires 90 days after this. - `is_production_token` — `false` means these credentials are **sandbox only**. - `is_datastream_token` — `false` means these credentials are for **SmartFlights**, not DataStream. Check both booleans before using the token. Pointing a sandbox token at production, or a DataStream token at SmartFlights, fails as an authorization error, not as a helpful message. ## Step 3 — handle failure `401 Unauthorized` returns `{"status": "401", "message": "Invalid token"}`. There is no self-service recovery: a burned or lost refresh token has to be regenerated by a human in the Admin Panel at `https://app.tripninja.io` under Developer Resources, and Trip Ninja issues the Admin Panel account itself — there is no signup form. ## Alternative: the SDK credential exchange The first-party SDKs do not use this endpoint. They `POST /sdk/auth/` with `X-Client-ID` and `X-Client-Secret` headers (from env `TN_SDK_CLIENT_ID` / `TN_SDK_CLIENT_SECRET`), receive a credentials document keyed `sandbox_token` / `prod_token` / `data_stream_token`, cache it in `credentials.json`, and re-fetch automatically on a `401` before replaying the request once. `/sdk/auth/` appears in **no** published OpenAPI document — it is only visible in the SDK source at `https://github.com/trip-ninja-inc/tn-sdk-python`. ## Rules an agent must respect - Never log, echo or emit any token value. The examples in the published spec are illustrative strings, not live credentials. - Rotate **before** `expiry_date`, not after — an expired access token cannot be presented alongside the refresh token in some client flows without a manual reset. - IP allow-listing applies to this endpoint too: a non-allow-listed source IP gets `403` regardless of how valid the tokens are.