> ## Documentation Index
> Fetch the complete documentation index at: https://developers.ttool.travel/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> Exchange credentials for a bearer token and keep it fresh.

Every call except `authenticate` needs this header:

```http theme={null}
Authorization: Bearer {accessToken}
```

Get the token from [`POST /api/v1/flights/authenticate`](https://developers.ttool.travel/api-reference/authenticate) with your API user's username and password. The response tells you how long it lasts:

```json theme={null}
{ "accessToken": "eyJhbGciOiJSUzI1NiIsImtpZCI6...", "expiresIn": 3600, "tokenType": "Bearer" }
```

## Rules

* Cache the token and reuse it. Refresh it shortly before `expiresIn` seconds have passed. Don't authenticate per request.
* `authenticate` allows **10 calls per minute per IP**. Beyond that you get HTTP 429.
* Any call can return HTTP 401 if the token expired or was revoked. Authenticate once more and retry that call.
* Token validity is checked with zero clock tolerance, so keep your servers on NTP.
* Store the password and tokens in a secret store. Never log them.

## Failures

| HTTP | `errorCode` | Meaning |
| - | - | - |
| 401 | `INVALID_CREDENTIALS` | Wrong username or password |
| 401 | `AUTHENTICATION_FAILED` | The identity provider rejected the login |
| 401 | `USER_TOKEN_NOT_FOUND` | Missing or unreadable bearer token |
| 403 | `ACCOUNT_DISABLED` | The API user is disabled |
| 403 | `UNAUTHORIZED_ACCESS` | The API user isn't linked to an agency, or lacks permission |

## Example: token cache

```javascript theme={null}
let cached = { token: null, expiresAt: 0 };

async function getToken() {
  if (cached.token && cached.expiresAt - Date.now() > 60_000) return cached.token;
  const res = await fetch(`${BASE_URL}/api/v1/flights/authenticate`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ username: process.env.HUB_USER, password: process.env.HUB_PASSWORD })
  });
  if (!res.ok) throw new Error(`Authentication failed: ${res.status}`);
  const body = await res.json();
  cached = { token: body.accessToken, expiresAt: Date.now() + body.expiresIn * 1000 };
  return cached.token;
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.