> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.loreos.app/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.loreos.app/_mcp/server.

# Authentication

LoreOS has three credential types you will see during setup:

* **Runtime API key (`ck_...`)** — the server-side app key for public runtime
  `/v1/*` calls such as characters, sessions, messages, imports, delivery, images,
  and usage.
* **Account access token (`lat_...`)** — a scoped, revocable, expiring setup credential for
  `/v1/account/*` only (the device-flow response returns its `expires_at`; default lifetime is
  30 days). Use it to create apps, issue runtime keys, manage caps, and inspect account usage.
* **Demo sandbox key** — no signup, short-lived, and capped. Use it for local
  copy-paste testing and coding-agent quickstarts.

Clerk's own **Account API keys (`ak_...`) are not LoreOS keys**. Do not use
Clerk's Account / API Keys / Add new key screen for LoreOS runtime calls; those
keys are rejected by `api.loreos.app`.

## No-key testing (demo sandbox key)

The **demo sandbox key is one no-key path when availability says it is usable**. Check the
auth-free read endpoint first; it creates no app, key, quota reservation, or metered work:

```bash
export LOREOS_BASE="https://api.loreos.app"
curl -sS "$LOREOS_BASE/v1/demo/sandbox-key/availability" | jq '.data'

# Continue only when issuance_available=true. If challenge_required=true, a browser
# must supply turnstile_token; terminal agents should use the persistent-key fallback.
export LOREOS_KEY="$(curl -sS -X POST "$LOREOS_BASE/v1/demo/sandbox-key" \
  | jq -r '.data.api_key')"
```

The availability response reports `status`, `issuance_available`, `challenge_required`, current
demo limits, and `persistent_key_docs`. It does not reserve rate-limit quota, so the subsequent
POST may still return `429` for that caller.

Demo keys are short-lived and capped. The exact caps (defaults shown; an operator can tune
them, but these are the deployed values):

* **one sandbox app** — the key is bound to a fresh isolated sandbox tenant + app;
* **five characters** (`POST /v1/characters` past the fifth → `403 demo_quota_exceeded`);
* **fifteen sessions** (past the fifteenth → `403 demo_quota_exceeded`);
* **150 user messages** total (the 151st → `403 demo_quota_exceeded`);
* **2,000 characters per message** (longer → `413 demo_input_too_large`);
* **no image generation** — image probes and inventory generation are blocked
  (`403 demo_key_surface_disabled`), and the key's hard credit cap blocks metered work
  before any provider call regardless;
* **no managed Telegram** and **no webhook delivery** (`403 demo_key_surface_disabled`);
* **no budget changes**, **no app creation**, and **no extra key issuance**
  (`403 demo_key_surface_disabled`);
* **auto-expires** (\~24 hours) — every demo key carries an expiry and stops working after
  it (`403 api_key_expired`).

Demo keys are designed for local testing only. Do not deploy a demo key as production
auth because it expires automatically and intentionally blocks product surfaces such
as images, Telegram, webhooks, and additional key issuance.

**Issuance is runtime-controlled and rate-limited.** This route is gated behind abuse controls
and may be paused. Re-read availability before retrying. Handle these responses:

* `429 demo_sandbox_rate_limited` — issuance rate limit reached. Issuance is capped per
  client (by default **a few keys per IP and per fingerprint within a rolling \~1-hour
  window**); the `fix` field tells you roughly how long to wait. Back off and retry after the
  window — do **not** loop-mint demo keys.
* `503 demo_sandbox_disabled` — public issuance is **paused** (maintenance or abuse
  controls). This is not transient from your side; use a persistent runtime key from
  self-serve account access, the console, or preview invite provisioning.

We do not publish a shared public `LOREOS_KEY` because LoreOS keys can create state and
trigger metered model work.

> **The demo fits a real multi-turn try** — five characters, 150 messages, and a
> day-long TTL are enough to feel reply latency, the async event log (via polling), and reply
> quality across a few conversations. **But for image generation, webhook/Telegram delivery, or
> a repeated eval loop, use a persistent staging key.** A demo key can't generate images or use
> managed delivery, and re-minting one per run trips the issuance `429`. Staging keys are
> issued through self-serve account access or preview provisioning; reuse one server-side
> instead of minting demo keys in a loop. See
> [Staging and evaluation](./staging-and-evaluation).

## Self-serve account access

For persistent apps, use **[Self-serve access](./self-serve-access)** when it is enabled
for your workspace. The high-level contract is:

1. A coding agent calls `POST /v1/account/device/start`.
2. A human opens the returned `verification_uri`, signs in with Clerk/Google, reviews the
   requested account scopes, and approves the code.
3. The agent exchanges `device_code` with `POST /v1/account/device/token` and receives a
   short-lived `lat_...` account access token.
4. The agent uses that account token only on `/v1/account/*` routes to create an app,
   issue a runtime API key, manage end-user caps, and inspect usage.
5. Your backend uses the issued `ck_...` runtime API key on public runtime `/v1/*`
   routes.

Do not give an agent Google credentials, Clerk credentials, browser cookies, or a human
session token. The agent only needs the LoreOS account access token after a human approval.
Device-code issuance is rate-limited; handle `429 account_device_rate_limited` by waiting
or reusing an existing account token, not by looping.

## Use your key

Send the key as a Bearer token on **every** request, against the base URL
`https://api.loreos.app`:

```bash
curl https://api.loreos.app/v1/me \
  -H "Authorization: Bearer $LOREOS_KEY"
```

`GET /v1/me` returns the app your runtime key resolves to — the quickest way to confirm
that a demo, invite, or self-serve-issued app key works before you build anything else.

If `GET /v1/me` returns `clerk_account_api_key_not_loreos_key`, you pasted a
Clerk `ak_...` key. Go back to the LoreOS console and issue a Runtime API Key.
If it returns `account_token_used_as_runtime_key`, you pasted a `lat_...`
account token; use it only for `/v1/account/*`, then issue a `ck_...` runtime
key.

## Keep keys safe

* Treat keys like passwords — keep them **server-side**. Never ship a key in a browser or
  mobile app.
* Each key is scoped to a single app; everything you create is isolated to that app.
* Need to rotate a compromised key? Use the console Runtime API Keys panel or call
  `POST /v1/account/apps/{app_id}/api-keys/{prefix}/rotate` with an account token.
  Use `POST /v1/account/apps/{app_id}/api-keys/{prefix}/revoke` to immediately
  disable an old prefix.