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
| Export | Route | Covers |
|---|---|---|
| Audit log | GET /audit/export | Every recorded action in a date range, oldest first. |
| Usage | GET /usage/export | What 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>"
| Parameter | Meaning |
|---|---|
format | jsonl (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. |
since | Inclusive. A plain date like 2026-08-01 or a full ISO timestamp. A date alone means midnight UTC. |
until | Exclusive, 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.
| Column | Contents |
|---|---|
month | The month requested. |
metric | queries for the daily series. documents_now, storage_gb_now, gpu_seconds_now and n_corpora_now for the inventory. |
scope | day, corpus or workspace. |
scope_id | The date for a daily row, the document base id for a per-base row, empty for a workspace row. |
scope_name | The document base's name, where the row has one. |
value | The 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:
| Field | Value |
|---|---|
error | plan_feature, the one key every plan refusal in the API uses. |
feature | audit_export, which tells a client which row of the plan table to highlight. |
message | A 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.