Authentication
Accounts, personal API tokens, CLI sign-in, public endpoints and rate limits.
Accounts
Anyone can create an account with GitHub, Google or an email address. Signing in gives you a dashboard where you manage personal API tokens, approve CLI sign-ins and see your recent jobs.
Personal API tokens
Every authenticated route takes a personal token. Tokens look like lta_Ab12Cd34_… and act as you: jobs they create are yours, and rate limits count against your account.
Send the token as a bearer token or in X-API-Key; both are equivalent.
Authorization: Bearer lta_Ab12Cd34_...A token works until you revoke it, or until the expiry you chose when you created it (7, 30, 90 or 365 days). Only a SHA-256 hash is stored; the secret is shown once, right after creation. Revoking takes effect immediately.
There are three ways to get one:
| How | Best for |
|---|---|
| Dashboard → API tokens | CI, servers, scripts |
latex-api auth login | Your own machine (see below) |
POST /v1/tokens with an existing token | Automation that rotates its own tokens |
Managing tokens over the API
# Who am I?
curl https://latex-api.crafter.run/v1/me -H "Authorization: Bearer $LATEX_API_TOKEN"
# List, create, revoke
curl https://latex-api.crafter.run/v1/tokens -H "Authorization: Bearer $LATEX_API_TOKEN"
curl -X POST https://latex-api.crafter.run/v1/tokens \
-H "Authorization: Bearer $LATEX_API_TOKEN" -H "Content-Type: application/json" \
-d '{"name":"nightly build","expiresInDays":30}'
curl -X DELETE https://latex-api.crafter.run/v1/tokens/tok_... -H "Authorization: Bearer $LATEX_API_TOKEN"POST /v1/tokens returns { "token": "lta_…", "apiToken": { … } }. Store token right away; it is never returned again. A user can hold up to 25 active tokens.
Signing the CLI in
latex-api auth login uses a device flow, the same pattern as gh auth login:
- The CLI calls
POST /v1/auth/deviceand prints a code such asWDJB-MJHT. - Your browser opens
https://latex-api.crafter.run/cli/authorize?code=WDJB-MJHT. Sign in if needed, check that the code and machine match, pick a lifetime and click Authorize. - The CLI polls
POST /v1/auth/device/tokenand receives a personal token, which it stores in your OS keychain.
Codes expire after 10 minutes and can be used once. Approval only works from a signed-in browser session; a token cannot approve another device. CLI sign-ins show up under API tokens as "CLI on <hostname>", where you can revoke them.
For headless machines, create a token in the dashboard and pipe it in:
echo "$LATEX_API_TOKEN" | latex-api auth login --with-tokenDevice flow for your own clients
| Call | Response |
|---|---|
POST /v1/auth/device {clientName?, hostname?, platform?} | {deviceCode, userCode, verificationUri, verificationUriComplete, expiresIn, interval} |
POST /v1/auth/device/token {deviceCode} | 200 {token, tokenId, name, expiresAt?, user} once approved |
While waiting, the token call answers 428 with code authorization_pending (or slow_down, then wait interval + 2 seconds). It answers 400 access_denied if the user clicked Deny and 400 expired_token when the code expired or was already used.
Operator keys
Self-hosted instances can also define static keys in the API_KEYS environment variable (name:secret,name2:secret2). They are meant for the operator: they compile, but they cannot create personal tokens. Instances without Clerk configured run on operator keys only. See self-hosting.
Errors
A missing, unknown, expired or revoked credential gets 401 with code unauthorized:
{
"type": "https://latex-api.crafter.run/docs/errors#unauthorized",
"title": "Unauthorized",
"status": 401,
"detail": "Missing or invalid API key",
"instance": "/v1/compile",
"code": "unauthorized"
}Using an operator key on a personal-token endpoint returns 403 forbidden.
Public endpoints
These need no credential:
| Route | Purpose |
|---|---|
GET /health | Health check |
GET /openapi.json | OpenAPI 3.1 document |
GET /v1/limits | Effective limits of this instance |
GET /v1/templates and sub-routes | Catalog, files, archive, preview |
POST /v1/demo/compile | Playground compile, rate limited by IP |
POST /v1/auth/device, POST /v1/auth/device/token | CLI sign-in |
Rate limits
Per account, by default:
- 60 compiles per rolling hour (
RATE_LIMIT_PER_KEY_PER_HOUR) - 5 compiles running at the same time (
RATE_LIMIT_CONCURRENT_PER_KEY)
All your tokens share these limits. Over the limit you receive 429 rate_limited with a Retry-After header in seconds. The demo endpoint is limited per IP address instead: 5 compiles a minute and 30 a day. Starting CLI sign-ins is limited to 20 per hour per IP.
CORS
CORS is enabled for every origin on /v1. Calling the API from a browser works, but a token in browser code is public. Prefer a small server-side proxy, or the demo endpoint for anything anonymous.