Skip to main content
The REST API is the surface the CLI, the SDK runtime objects, and the admin dashboard call. Use it directly when a service of your own needs to talk to an agent, send on a channel, read or write user data and collections, run commerce, manage versions, or drive workflow runs without going through lua-cli. Every family on this tab is a developer-facing route: /chat/* for conversation turns, /developer/* for agent-scoped data, channels, commerce, persona, and versions, /marketplace/* for agent templates and marketplace skills, and /workflows/* for the workflow runtime. Verified against lua-cli 3.33.0.

Base URL

All routes are served from one host, chat routes included:
Paths carry no version segment. The CLI reads the same host from LUA_API_URL when you set it, so a self-hosted or staging deployment is addressed by changing that one variable.

Authentication

Every request carries an API key as a bearer token. A user session token from the CLI or admin dashboard works too, but for scripts and servers use a key.
A missing or malformed header answers 401 with No token provided; a key that is invalid, expired, suspended, or revoked answers 401 with Invalid or expired token. A revoked user session adds a code to that body so a client can sign in again instead of retrying. The key identifies the caller: on chat routes the key’s owner is the end user the conversation belongs to.

Scopes

A scoped key must hold the scope each route declares on the agent, or on the organization for organization-wide routes. Legacy keys act as their owner and pass every check the owner would. The families on this tab need: A wildcard grant such as agents:* or *:read covers every scope in its area except the sensitive ones, which a key holds only when they were granted by exact name: secrets:read, secrets:write, credits:grant, credits:deduct, billing:manage, negotiated-plans:write, telephony:provision, members:assign-role, api-keys:issue, tokens:mint, rights:assign, marketplace:review, promo-codes:manage, and platform:*. No route on this tab needs one. A key that can see the agent but lacks the scope answers 403 with code: "INSUFFICIENT_SCOPE" and the requiredScope. A key that cannot see the agent at all, because the agent is private and the key has no grant on it, or because the id belongs to another organization or does not exist, answers 403 with code: "RESOURCE_NOT_PROJECTED"; the two cases are deliberately indistinguishable. A scoped key reaches a private agent only through an explicit agent grant, and the message says so: This credential is not bound to this agent. Issue a credential bound to the agent (or add an agent grant).

Errors

Most routes answer errors in the default Nest shape. Validation failures list every violated constraint:
A permission denial names the resource you asked for, never the one the server resolved behind it:
Workflow routes use a typed envelope on every error, { statusCode, message, code, ...detail }, where code is one of the workflow error codes and validation failures carry details[] with path, constraint, and message. The statuses you handle across families: A 424 is built by POST /ai/generate/:agentId only; the chat routes report a provider refusal in-band. It is deterministic: retrying the same request fails identically. Its body tells you whose key was refused, so you know whether the fix is yours or Lua’s:
reason is one of invalid_api_key, forbidden, model_not_found, content_refused, quota_exhausted, or bad_request; keyOwner is byok for a key your organization configured or platform for Lua’s. The same fields ride the chat stream’s error chunk. The CLI maps these statuses to its exit codes: 401 to 9, 403 to 10, 5xx to 11, and 424 to 12. A 503 whose body is exactly { "error": { "code": "CORE_DRAINING", "message": "Lua is restarting. Please retry your request.", "retryable": true } } means the request never started; retry once after the Retry-After header. Every other 503 carries retryAfterSeconds only when a blind retry is safe, so honor it when present and treat its absence on a write as “the write may have applied”.

Pagination

Two conventions exist, and each family says which it uses. Page-numbered lists (custom data, products, user data, logs) take page (from 1) and limit and answer a pagination object beside data:
Workflow lists are cursor-paged: pass limit (default 50, at most 100) and hand nextCursor back as cursor until it is null. A cursor that does not decode answers 400 with BAD_CURSOR. Delivery lists take limit from 1 to 200 (default 50) and return the newest records first.

Idempotency

Two headers make retries safe, on the routes that honor them:
  • X-Idempotency-Key on the four channel send routes. Replaying a key returns the first send’s stored outcome instead of sending again, for as long as the delivery record lives; a key whose first attempt failed replays the failure; the same key from another agent is a different key. The CLI adds a UUID key to every POST it makes, so a lua command that sends never double-sends.
  • Idempotency-Key, or the body field idempotencyKey, on POST /workflows/:agentId/runs and on every item of a batch start. The same key with the same definition and input answers 200 with the existing run and idempotentReplay: true; the same key with anything else answers 409 with IDEMPOTENCY_KEY_REUSED naming the run that holds it.
Every other write is applied once per request.

Rate limits

Workflow routes are throttled per calling user per minute, in named buckets. A route names exactly one bucket: Past the limit the route answers 429 with RATE_LIMITED, retryAfterSeconds, bucket, limit, and windowSeconds, plus a Retry-After header and a per-bucket Retry-After-wf-<name>. The template catalog is throttled per client address: GET /marketplace/templates at 60 per minute, and the template detail and manifest reads at 20 per minute, answered with the plain 429 body { "statusCode": 429, "message": "ThrottlerException: Too Many Requests" }. No other route on this tab declares a limit in the API; whether the chat, channel, data, and commerce routes sit behind an edge rate limit, and at what value, is not documented here.

Versioning

Routes have no version prefix and no version header. Changes are additive: new optional fields and new codes appear without notice, and a client must ignore fields it does not know. A removed or renamed route is announced in the changelog before it goes.

See also