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:
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
- Open the LeonAI dashboard.
- Go to Settings → API Keys.
- Click Create API key, name it, and select its scopes.
- 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):
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:
{
"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:
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:
{
"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 (
expiresInis seconds). - Refresh token lifetime defaults to 30 days. Exchange it at
POST /api/v1/auth/refreshfor a fresh pair (the old refresh token is rotated out). POST /api/v1/auth/logoutrevokes the current session;POST /api/v1/auth/logout-allrevokes every session.
Two-factor challenge
If the account has 2FA 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.
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.
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 (not published
yet) will surface these as a rateLimit object on every response and retry idempotent
requests automatically, honoring Retry-After.
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 for per-tier request quotas.