Authentication
Every Clone endpoint requires IsAuthenticated. Two credential shapes are accepted, both detected automatically by the server.
Credential shapes
| Shape | Header | Lifetime | Used by |
|---|---|---|---|
| JWT | Authorization: Bearer <access> | 60 minutes (refreshable) | Web, Desktop, browser dashboards. |
| API key | X-Clone-API-Key: clone_<token> | Long-lived (rotate manually) | CLI, MCP server (stdio), CI, scripts. |
The MCP server (apps/mcp/src/index.ts) chooses the right header automatically based on the token shape — values starting with clone_ go in X-Clone-API-Key, anything else is sent as Authorization: Bearer ….
Endpoints (/api/auth/)
| Method | Path | Purpose |
|---|---|---|
| POST | /api/auth/signup/ | Create a new account. |
| POST | /api/auth/login/ | Log in with email + password. Returns { access, refresh, user }. |
| POST | /api/auth/logout/ | Invalidate the current refresh token. |
| POST | /api/auth/token/refresh/ | Exchange a refresh token for a fresh access token. |
| GET | /api/auth/me/ | Return the authenticated user. |
| POST | /api/auth/invite/validate/ | Validate an invite code before signup. |
| POST | /api/auth/waitlist/ | Add an email to the public waitlist. |
| GET / POST | /api/auth/keys/ | List the user's API keys / issue a new one. |
| GET / DELETE | /api/auth/keys/<uuid>/ | Inspect or revoke a specific API key. |
JWT flow
# 1. Login.
curl -sS -X POST https://api.clone.is/api/auth/login/ \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","password":"…"}'
# → { "access": "<jwt>", "refresh": "<jwt>", "user": {...} }
# 2. Use the access token. (60-min lifetime.)
curl -sS https://api.clone.is/api/auth/me/ \
-H "Authorization: Bearer <access>"
# 3. When access expires, exchange the refresh.
curl -sS -X POST https://api.clone.is/api/auth/token/refresh/ \
-H "Content-Type: application/json" \
-d '{"refresh":"<refresh>"}'
# → { "access": "<new jwt>" }
Browser surfaces (Web, Desktop) refresh access tokens on a 50-minute timer so the user never sees a forced re-login. Servers and headless scripts should use API keys instead.
API-key flow
# 1. Issue a key.
curl -sS -X POST https://api.clone.is/api/auth/keys/ \
-H "Authorization: Bearer <access>" \
-H "Content-Type: application/json" \
-d '{"label":"my-laptop"}'
# → { "id": "<uuid>", "key": "clone_xxxxxxxxxxxxxx", "label": "my-laptop", "created_at": "..." }
#
# IMPORTANT: the raw key is only returned once. Copy it now or revoke and re-issue.
# 2. Use it.
curl -sS https://api.clone.is/api/auth/me/ \
-H "X-Clone-API-Key: clone_xxxxxxxxxxxxxx"
# 3. Revoke it later.
curl -sS -X DELETE https://api.clone.is/api/auth/keys/<uuid>/ \
-H "Authorization: Bearer <access>"
Multi-tenant MCP
When the MCP server runs in MCP_TRANSPORT=http (production at clone.is/mcp), each MCP session is bound to whatever credential the client provided on its initialize request. One MCP instance therefore serves many users — the server forwards the per-session token upstream rather than holding a shared one. See Apps → MCP.
What goes wrong
| Symptom | Cause |
|---|---|
401 Unauthorized | Missing header, expired access token, revoked API key. |
401 immediately after token issue | Clock skew on the producing host (JWT iat/exp rely on accurate time). |
Endpoint works in browser but 401 from MCP | Browser session uses cookies; MCP needs an explicit X-Clone-API-Key or Authorization: Bearer. |