Skip to main content
A log export is the drain’s offline twin. The same rows go through the same event builder and the same scrubber as a log drain; instead of being pushed at a destination you own, they are written as gzipped NDJSON and handed back as signed, expiring links. Use it when you have no collector, or when you need a window that predates the drain — a drain copies what happens next, and an export reads what already happened. These routes are organization-scoped and need logs:read. The base URL, bearer authentication, and the error envelope are on the REST API overview; lua logs export is the same surface from a terminal.
Exports are switched on for a deployment as a whole. While they are off, both routes answer 404 — the resource does not exist rather than being forbidden. It may not be enabled on your deployment yet.

Scopes

logs:manage is sensitive: a wildcard such as logs:* does not satisfy it. It is checked when the job is started, not when it is read — the decision is frozen onto the job at that moment, because the worker picks the job up minutes later with no request and no principal behind it. A job started without content cannot be upgraded; start another. Both routes share the drain rate-limit buckets: log-drains-mutate on the start, log-drains-read on the poll.

POST /admin/orgs/:orgId/logs/export

Starts an export. Returns immediately; the work happens on the platform.
string
required
Inclusive lower bound: an ISO 8601 instant, or a relative form such as 15m, 2h, or 7d. A relative form is resolved by the server’s clock.
string
required
Inclusive upper bound; the same formats.
string[]
Which sources to export. Defaults to every exportable source.
string[]
Up to 200 agent ids. Defaults to every agent in the organization.
string[]
Any of production, sandbox. Defaults to both.
boolean
default:"false"
Include user_message and agent_response bodies. Requires logs:manage.
Response 202 with { "jobId": "lex_…", "state": "queued", "createdAt": "…" }. Poll the status route. Errors
Only the eighteen execution-log sources and execution are exportable. The six adjacent-record sourcesdelivery, trigger-execution, job-execution, workflow-run, voice-call, and workflow-audit — are built for a drain out of the platform’s own ledgers and are not execution-log rows, so there is nothing for an export to read. Naming one answers 422 EXPORT_SOURCE_UNSUPPORTED. To capture them, point a drain at a destination you can read.

GET /admin/orgs/:orgId/logs/export/:jobId

Reads one job. A job id belonging to another organization answers 404, never 403 — the two cases are deliberately indistinguishable. Response
Errors

Caps

One export runs per organization at a time. That is a guarantee, not a race — a second start answers 409 EXPORT_ALREADY_RUNNING. lua logs export turns that into an attach: a second invocation in a terminal follows the running job to its files rather than refusing. A truncated export is a success. The files are valid and complete as far as they go; they simply do not cover the whole window. Narrow the window or the sources and run it again.

What the files contain

One complete batch per line. Not one record per line. A record has no resource of its own — service.name, lua.org.id, deployment.environment.name, and the agent version all live on the batch’s resource half. A file of bare records would validate against nothing you were given and would drop the tenancy every record depends on. Parts are named org-<orgId>-logs-<NNNN>.ndjson.gz, zero-padded so lexical order is emission order:

Validate it against the published schema

Each line is one batch and validates against the same JSON Schema a drain delivery does — byte-identical shape, same encoder.
Build your ingest so an unknown attributes key is carried through rather than rejected — additions are additive within a major version, and a schema addition should never fail a load.

What is not in them

  • Rows written before organization attribution existed. A row that never recorded an organization cannot be attributed to one, so it is in no org-scoped export. These are the oldest rows only, and the boundary is fixed: nothing written since carries the gap.
  • log-type rows with no source. A row the platform never classified is counted in counts.scanned and not in counts.rows.
  • Message bodies, unless the job was started with logs:manage and includeContent: true. The record still arrives, carrying lua.content.withheld: true.
  • Anything the scrubber caught. Scrubbing is unconditional: there is no flag that skips it, and includeContent: true does not weaken it. An export applies the built-in rule classes (builtin, lua-token, vendor-key); organization scrub rules live on a drain, so they do not apply here.

Retention bounds what you can export

The platform keeps execution logs for 90 days by default. An export can only read what is still there, so a window whose lower bound is older than the retention horizon returns the part of it that survives, not an error. Export before you need to, not after.

The CSV download is deprecated

GET /admin/orgs/:orgId/logs/download still works and still caps at 10,000 rows. It is deprecated in favour of this job: Nothing about the CSV route has changed. Its removal will be its own change, with its own release note.

See also