# Authentication

Source: https://developers.leonai.io/docs/authentication/

The LeonAI API supports two authentication methods:

- **API keys** (`X-API-Key: <key>`) — for scripts and server-to-server integrations.
- **JWT bearer tokens** (`Authorization: Bearer <token>`) — for the dashboard and
  account-management endpoints.

Most developer endpoints accept **either** method. Account endpoints (auth, users,
billing, teams, and API-key management) require a **JWT**, and
`POST /api/v1/track/conversions` requires an **API key** with the `analytics:write`
scope.

## API keys

API keys are the recommended way to call the API programmatically. Send the key in
the `X-API-Key` header:

```bash
curl https://api.short.leonai.io/api/v1/links \
  -H "X-API-Key: lk_live_your_key_here"
```

Keys are prefixed (`lk_live_...` / `lk_test_...`), stored only as a hash, and shown
in full **exactly once** — at creation time.

### Scopes

Each key carries one or more scopes. An endpoint's required scope is noted in its
description; a `403` is returned if the key lacks it.

| Scope             | Grants                                                               |
| ----------------- | -------------------------------------------------------------------- |
| `links:read`      | List, read, and export links                                         |
| `links:write`     | Create, update, delete, and bulk-import links                        |
| `analytics:read`  | Read link/account analytics, real-time stats, and conversion reports |
| `analytics:write` | Record conversions (`POST /api/v1/track/conversions`)                |
| `domains:read`    | List and read custom domains                                         |
| `domains:write`   | Create, verify, and delete custom domains                            |
| `webhooks:read`   | List and read webhook endpoints and delivery logs                    |
| `webhooks:write`  | Create, update, and delete webhook endpoints                         |

Grant the narrowest set of scopes a key needs. A key used only to create links needs
just `links:write` (and `links:read` if it also reads them back).

### Creating a key

1. Open the LeonAI dashboard.
2. Go to **Settings → API Keys**.
3. Click **Create API key**, name it, and select its scopes.
4. Copy the key immediately — it is shown only once. Store it in a secret manager or
   environment variable, never in source control.

Programmatically, keys are created with a JWT via `POST /api/v1/api-keys` (an API key
can never mint, re-scope, or revoke API keys — that always requires a JWT):

```bash
curl -X POST https://api.short.leonai.io/api/v1/api-keys \
  -H "Authorization: Bearer <jwt>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "CI deploy key",
    "scopes": ["links:read", "links:write"],
    "environment": "live"
  }'
```

The response includes the raw `key` **once**:

```json
{
  "success": true,
  "data": {
    "id": "key_...",
    "name": "CI deploy key",
    "key": "lk_live_...",
    "prefix": "lk_live",
    "scopes": ["links:read", "links:write"],
    "createdAt": "2026-07-10T12:00:00.000Z"
  },
  "message": "API key created successfully. Save it now - you won't be able to see it again!"
}
```

## JWT bearer tokens

JWTs are issued by `POST /api/v1/auth/signup` and `POST /api/v1/auth/login` and refreshed
via `POST /api/v1/auth/refresh`. Send the access token as a bearer token:

```bash
curl https://api.short.leonai.io/api/v1/auth/me \
  -H "Authorization: Bearer <access_token>"
```

Use JWTs for account operations (managing API keys, billing, teams, 2FA, GDPR data
export/deletion). For integrations and automation, prefer API keys.

### Token flow

A successful login returns an access token and a refresh token:

```json
{
  "success": true,
  "data": {
    "user": { "id": "…", "email": "you@example.com", "tier": "pro", "emailVerified": true },
    "tokens": { "accessToken": "…", "refreshToken": "…", "expiresIn": 604800 }
  }
}
```

- **Access token** lifetime defaults to **7 days** (`expiresIn` is seconds).
- **Refresh token** lifetime defaults to **30 days**. Exchange it at
  `POST /api/v1/auth/refresh` for a fresh pair (the old refresh token is rotated out).
- `POST /api/v1/auth/logout` revokes the current session; `POST /api/v1/auth/logout-all`
  revokes every session.

### Two-factor challenge

If the account has [2FA](https://developers.leonai.io/docs/guides/two-factor/) enabled, `login` returns no tokens —
instead it returns `{ requiresTwoFactor: true, pendingToken }`. Complete the login by
posting the `pendingToken` plus a TOTP code (or recovery code) to
`POST /api/v1/auth/2fa/verify`, which then returns the normal token pair.

:::note[Conversion tracking is API-key only]
`POST /api/v1/track/conversions` does **not** accept a JWT — it requires an API key with
the `analytics:write` scope (and the Business+ `conversionTracking` feature). See the
[Analytics guide](https://developers.leonai.io/docs/guides/analytics/#conversion-tracking-business).
:::

## Rate limiting

All `/api/v1/*` endpoints are rate limited **per subscription tier**. Every response
includes these headers:

| Header                  | Meaning                                |
| ----------------------- | -------------------------------------- |
| `X-RateLimit-Limit`     | Requests allowed in the current window |
| `X-RateLimit-Remaining` | Requests remaining in the window       |
| `X-RateLimit-Reset`     | When the window resets                 |

When you exceed the limit, the API responds with `429 Too Many Requests` and a
`Retry-After` header (in seconds). The planned [TypeScript SDK](https://developers.leonai.io/docs/sdks/) (not published
yet) will surface these as a `rateLimit` object on every response and retry idempotent
requests automatically, honoring `Retry-After`.

```ts
const page = await leonai.links.list();
page.rateLimit; // { limit, remaining, reset } | undefined
leonai.rateLimit; // most recent rate-limit state seen by this client
```

See the [tier table](https://developers.leonai.io/docs/intro/#subscription-tiers) for per-tier request quotas.
