Exports and audit

Exports

Your audit log and your usage, as files you can hand to a reviewer or load into a pipeline. A compliance question is answered with a file, not a screenshot of a table, so both exports stream straight to a download with a filename a person can find again later.

What you can download

ExportRouteCovers
Audit logGET /audit/exportEvery recorded action in a date range, oldest first.
UsageGET /usage/exportWhat the workspace used in a month, per document base and in total.

Both are in the app under Settings, Exports, both take csv or jsonl, and both are workspace-admin routes on the plans that carry export. A finance or compliance team that needs one almost always needs the other, so they share a single plan flag rather than adding a row nobody asked for. Which plans carry it is in the plan table.

Exporting the audit log

curl -s -OJ "https://api.engramdynamics.org/audit/export?format=jsonl&since=2026-08-01&until=2026-09-01" \
  -H "Authorization: Bearer <your key>"
ParameterMeaning
formatjsonl (the default) keeps each event's detail as the JSON it is, one event per line, which is what a pipeline wants. csv is what a spreadsheet wants.
sinceInclusive. A plain date like 2026-08-01 or a full ISO timestamp. A date alone means midnight UTC.
untilExclusive, same formats. It has to be after since.

Rows are oldest first, because a log reads forward. The columns are fixed and spelled out rather than derived from the database, so a column cannot move underneath tooling that parses the file: id, created_at, event, user_id, corpus_id, detail.

{"id":"9f1c...","created_at":"2026-08-14T11:02:56","event":"apikey.create","user_id":"7a2e...","corpus_id":"","detail":"{\"name\": \"nightly sync\", \"prefix\": \"ek_7Fq2pXk9\", \"scopes\": [\"ingest\"]}"}

Leave since off and the export reaches back two years, which is the guard against a mistyped date asking for an unbounded scan rather than a retention policy. The file arrives as engram-audit-<start date>.jsonl or .csv, with the real media type so a spreadsheet or a jq pipeline does the right thing, and it streams rather than being built in memory, so a long history does not become a timeout.

The workspace comes from the credential, never from a parameter. There is no id in this route for anyone to change.

Exporting usage

curl -s -OJ "https://api.engramdynamics.org/usage/export?format=csv&month=2026-08" \
  -H "Authorization: Bearer <your key>"

month is YYYY-MM and defaults to the current month, which is what clicking Export without touching anything means. The file arrives as engram-usage-<month>.csv.

The shape is long rather than wide: one row per observation, with the metric named in its own column. A workspace's usage is really two different things, a daily query series and a per-document-base inventory, and a wide table would have to lose one or pad it with blanks. Long carries both honestly and pivots in one step in any spreadsheet.

ColumnContents
monthThe month requested.
metricqueries for the daily series. documents_now, storage_gb_now, gpu_seconds_now and n_corpora_now for the inventory.
scopeday, corpus or workspace.
scope_idThe date for a daily row, the document base id for a per-base row, empty for a workspace row.
scope_nameThe document base's name, where the row has one.
valueThe number.
month,metric,scope,scope_id,scope_name,value
2026-08,queries,day,2026-08-01,,412
2026-08,queries,day,2026-08-02,,377
2026-08,documents_now,corpus,c_9f1c,Handbook,128
2026-08,storage_gb_now,corpus,c_9f1c,Handbook,51.2
2026-08,documents_now,workspace,,,904

Two honest details in that shape. The _now suffix means exactly what it says: the per-base rows are a snapshot as of the moment you exported, not a month total, because the inventory has no month dimension, and nothing here should read as a monthly figure that it is not. And there are no per-base query counts, because queries are attributed per workspace rather than per base, so the only honest value would be a column of zeros.

The numbers come from the same rollup the usage page in the app reads, so a downloaded figure and an on-screen figure can never disagree.

If the download is refused

A plan without export answers 403 with the shape every plan gate uses, so one client branch handles all of them:

FieldValue
errorplan_feature, the one key every plan refusal in the API uses.
featureaudit_export, which tells a client which row of the plan table to highlight.
messageA sentence naming the plan the feature comes with and saying that moving up turns it on right away.
upgrade_url/billing, which a client turns into a button.

Treat the plan table as authoritative for which tier carries export. A member rather than an admin gets a plain "tenant admin required" instead, because an upgrade prompt for something they could not configure anyway would be the wrong answer. Both exports are also mirrored under /v1, where refusals arrive in the versioned error envelope with the same codes.

Exports are never blocked by a billing state. Leaving with your data stays open on a past-due or cancelled workspace, which is what the Data Processing Agreement promises and what self-serve has to mean.

Next

Audit log is the event catalogue behind the first file, and Plans and allowances is where the numbers in the second one come from.