Reference
API reference
A catalogue of the public LIME operations: the method, path, required header, and purpose of every call, plus the response shape and the error code a client branches on. For AI agents: /docs/all.en.md. Version: 2026-10-15
Conventions#
Every operation lives under https://lime.pics/api/v1. Requests and responses are JSON.
- Credentials travel only in headers — never in the URL or the query string.
- There are three credentials: the Agent Token (
X-Agent-Token), the Site Token (X-Site-Token), and the MCP passport (Authorization: Bearer). - Expensive operations are rate-limited with a Redis sliding window (default 60 s); a denied request does not consume quota. On
429, back off exponentially with jitter and honorRetry-Afterwhen present — it is not universal (the OAuth429omits it) — and cap retries. - Application errors carry a stable machine-readable
code. The OAuth token endpoint is the exception for its RFC 6749 outcomes: a successful body is raw{access_token, token_type, expires_in}, and protocol errors (invalid_request,invalid_client,invalid_grant,unsupported_grant_type) are raw{error, error_description}, with no envelope. Its rate-limit and unavailability responses (429,503) still use the standard{ok:false,error:{code,message,detail}}envelope. - The default mint request carries no
grant_type; the field appears only on the separate authorization-code form path. - A machine-readable OpenAPI description of the public operations is published at
https://lime.pics/.well-known/openapi/v1.json. - Datetime fields (
expires_at,created_at,approved_at,delivered_at) serialize as ISO-8601 UTC with a+00:00offset (notZ), or as JSONnullwhen unset. - Every API response carries
X-Request-IdandX-Trace-IDheaders (static assets do not). - Only
/api/v1is published; there is no deprecation or sunset mechanism, and no other API version exists.
Credentials#
| Credential | Header | Used by these calls |
|---|---|---|
| Agent Token | X-Agent-Token (LIME_AGENT_TOKEN) | Mint an MCP passport, approve a sign-in, read its own profile |
| Site Token | X-Site-Token (LIME_SITE_TOKEN) | Create sign-in and binding requests, read events, exchange a code |
| MCP passport | Authorization: Bearer | Proving identity to a service |
| none | — | JWKS, OAuth metadata, the public profile and login request |
The Agent Token and Site Token authenticate to LIME only: never send either to a service. The only credential a service ever sees is the MCP passport, and it verifies it locally via JWKS. The full comparison lives in Credentials.
Operations#
| Flow | Operation | Credential | Purpose |
|---|---|---|---|
| MCP | POST POST /api/v1/modules/oauth/token | X-Agent-Token | Mint an aud=mcp passport for 300 seconds |
| MCP | GET GET /api/v1/modules/oauth/.well-known/oauth-authorization-server | none | Read token_endpoint and jwks_uri |
| Sign-in | POST POST /api/v1/modules/agent-login/requests | X-Site-Token | Create a PENDING login request |
| Sign-in | GET GET /api/v1/modules/agent-login/requests/{request_id} | X-Site-Token | Poll a login request's status |
| Sign-in | GET GET /api/v1/modules/agent-login/events | X-Site-Token | SSE stream of login events |
| Agent | GET GET /api/v1/auth/requests/{request_id} | none | Read a login request and its PoW challenge |
| Agent | POST POST /api/v1/modules/agent-login/requests/{request_id}/approve | X-Agent-Token | Approve a sign-in with a pow_nonce |
| Agent | GET GET /api/v1/core/agents/me/profile | X-Agent-Token | Read the authenticated agent's profile |
| Binding | POST POST /api/v1/modules/bindings/requests | X-Site-Token | Create a binding request and get a connect_url |
| Binding | POST POST /api/v1/modules/bindings/exchange | X-Site-Token | Exchange a binding_code for a binding passport |
| Core | GET GET /api/v1/core/.well-known/jwks.json | none | Public keys for local passport verification |
| Core | GET GET /api/v1/agents/{agent_id} | none | Public agent profile after verification |
Sequences#
The operations form three flows, and the order differs between them.
- An MCP call, no browser. The agent mints a passport with POST
POST /api/v1/modules/oauth/token, then calls the MCP server withAuthorization: Bearer. The server verifies the signature itself, fetching GETGET /api/v1/core/.well-known/jwks.json; it never calls LIME. - Agent site login. The backend creates a request (POST
POST /api/v1/modules/agent-login/requests); the agent reads it (GETGET /api/v1/auth/requests/{request_id}) and approves it (POSTPOST /api/v1/modules/agent-login/requests/{request_id}/approve); the backend waits for the passport on GETGET /api/v1/modules/agent-login/events. Polling with GETGET /api/v1/modules/agent-login/requests/{request_id}is the fallback. - Binding. The backend creates a request (POST
POST /api/v1/modules/bindings/requests) and sends the human toconnect_url; after the return with?binding_code=, the backend exchanges the code (POSTPOST /api/v1/modules/bindings/exchange) and verifies the passport.
An MCP server never receives an Agent Token or a Site Token: it sees only the passport in the Authorization header.
Response shapes#
Application responses arrive in a LIME envelope, while discovery and OAuth documents are raw RFC JSON. A successful call, for example POST POST /api/v1/modules/agent-login/requests:
{
"ok": true,
"data": {
"login_request_id": "<LOGIN_REQUEST_ID>",
"status": "PENDING",
"expires_at": "<ISO-8601>"
}
}The mint answers with RFC 6749, with no envelope and with Cache-Control: no-store; expires_in is 300 seconds:
{
"access_token": "<PASSPORT>",
"token_type": "Bearer",
"expires_in": 300
}An application error always carries a code:
{
"ok": false,
"error": {
"code": "REQUEST_VALIDATION_ERROR",
"message": "request validation failed",
"detail": { "errors": [ { "loc": ["body", "domain"], "msg": "<REASON>" } ] }
}
}Branch on error.code, not on the message text. Common statuses: 401 for a missing or wrong credential (MISSING_AGENT_TOKEN when X-Agent-Token is absent, INVALID_AGENT_TOKEN when it is wrong or carried in Authorization: Bearer, SITE_TOKEN_MISSING for a site call with no X-Site-Token), 422 for a body that failed validation (REQUEST_VALIDATION_ERROR), 403 for an inactive agent (AGENT_INACTIVE), and 429 when a limit is exceeded (RATE_LIMIT_EXCEEDED). The mint is the exception: a bad credential or body answers RFC 6749 (invalid_client 401, invalid_request 400) with no envelope.
Every application error is {"ok": false, "error": {"code", "message", "detail"}}: REQUEST_VALIDATION_ERROR (422) puts the per-field messages in detail.errors, and HTTP_500 (500) carries an empty detail: {}. Response headers are X-Request-Id and X-Trace-ID. correlation_id and invariant_id are nested inside detail and appear only on CAPTCHA errors — a 400 CAPTCHA rejection carries detail.correlation_id, and 503 CAPTCHA_UNAVAILABLE carries detail.invariant_id plus detail.correlation_id; those CAPTCHA codes are portal-facing and not part of the public integrator catalogue.
The data payload of each operation:
| Operation | Success data keys |
|---|---|
POST POST /api/v1/modules/agent-login/requests | login_request_id, status, expires_at |
GET GET /api/v1/modules/agent-login/requests/{request_id} | status |
GET GET /api/v1/auth/requests/{request_id} | request_id, status, pow_challenge, pow_difficulty, expires_at |
POST POST /api/v1/modules/agent-login/requests/{request_id}/approve | request_id, site_id, webhook_url, status, created_at, expires_at, approved_agent_id, approved_at, delivered_at, create_agent_id, create_idempotency_key |
POST POST /api/v1/modules/oauth/token | RFC 6749 body: access_token, token_type, expires_in |
GET GET /api/v1/core/agents/me/profile | agent_id, user_id, display_name, avatar_url, description |
GET GET /api/v1/agents/{agent_id} | agent_id, display_name, avatar_url, description, status |
POST POST /api/v1/modules/bindings/requests | binding_id, connect_url, status (PENDING), expires_at |
POST POST /api/v1/modules/bindings/exchange | passport, binding_id, redirect_uri |
Two identifiers name the same login request: login_request_id in the create response equals request_id in the approve and status responses — one UUID. In the approve data, create_agent_id and create_idempotency_key are always null today (reserved fields, not the login UUID). The Idempotency-Key header is ignored on the site-login endpoints (not required, not rejected). GET GET /api/v1/core/agents/me/profile returns user_id, which is the SDK AgentProfile.owner_id.
The site status read (GET GET /api/v1/modules/agent-login/requests/{request_id}) returns only status — one of PENDING, APPROVED, DELIVERING, DELIVERED, EXPIRED, CANCELLED — whereas the public read (GET GET /api/v1/auth/requests/{request_id}) returns request_id, status, pow_challenge, pow_difficulty and expires_at. In the approve data, webhook_url is a reserved field: LIME stores and echoes it back, and delivers over SSE rather than an HTTP webhook. The public agent profile (GET GET /api/v1/agents/{agent_id}) carries status as a string with the values active or inactive; there is no suspended agent status.
Edge cases#
- A secret in the wrong header.
X-Agent-Tokenon a site call returnsSITE_TOKEN_MISSING, andX-Site-Tokenon an agent call returnsINVALID_AGENT_TOKEN. An agent call that omitsX-Agent-TokenreturnsMISSING_AGENT_TOKEN; a wrong token, or one carried inAuthorization: Bearer, returnsINVALID_AGENT_TOKEN. Match the header to the credentials table. - An Agent Token sent to a service. The mint is the only call that needs it; an MCP server must never receive it. If a token leaked, rotate it in the portal.
- A body that failed validation. The default mint body is exactly
{"domain":"<BARE_HOST>"}withContent-Type: application/json, and it carries nogrant_type;unsupported_grant_typeapplies only to the separate authorization-code form-urlencoded path, not to this body. A scheme or path indomainis stripped before matching, so a bareexample.comandhttps://example.com/pathresolve the same;invalid_request(400) is returned for an IP-literal, a reserved host, or an emptydomain; for a missingContent-Type: application/json; for malformed or non-object JSON; for extra JSON keys besidedomain; for a non-stringdomain; and for a leftover?domain=query parameter. - An unknown `kid` at the server. The server rejects the passport even with a valid signature until it refreshes GET
GET /api/v1/core/.well-known/jwks.json; refresh the cache and retry. - Retrying too often. The mint and public reads are rate-limited; the OAuth
429from POSTPOST /api/v1/modules/oauth/tokencarries noRetry-After, so back off with jitter and do not retry back to back, or theRATE_LIMIT_EXCEEDED(429) only deepens. - Creating an agent or a site, minting or rotating a token. All portal-only (UI) — no public API mints an Agent Token or a Site Token, and no public sandbox or staging host exists for the LIME API (
https://lime.picsis its only public origin); the public demo MCP resource server athttps://demo.lime.pics/mcpis a separate host. See the value reference.
Codes and errors#
Branch on error.code, never on the message text. The most common public codes:
| Code | HTTP | Cause | Action |
|---|---|---|---|
MISSING_AGENT_TOKEN | 401 | An agent call with no X-Agent-Token | Send the Agent Token header |
INVALID_AGENT_TOKEN | 401 | The X-Agent-Token is wrong, or sent in Authorization: Bearer | Mint an Agent Token, then send it in X-Agent-Token |
SITE_TOKEN_MISSING | 401 | A site call without X-Site-Token | Send the Site Token header |
REQUEST_VALIDATION_ERROR | 422 | The body failed validation | Fix the payload to match the schema |
AGENT_INACTIVE | 403 | The agent is inactive | Reactivate it in the portal |
BINDING_REQUEST_EXPIRED | 410 | The binding request expired | Create a new request |
RATE_LIMIT_EXCEEDED | 429 | Too many calls | Back off with jitter; honor Retry-After when present (the OAuth 429 omits it) |
JWKS_UNAVAILABLE | 503 | JWKS is unreachable and no cached key verifies the passport | Retry after a short delay |
This is the complete public integrator catalogue (59 codes); CAPTCHA, owner-session, registration and credential-rotation codes are portal-facing and intentionally excluded. The full catalogue is in the value reference.