Admin

Export and verify a tenant

Export portable tenant records and verify their signature before using them.

For: cluster and tenant administrators.

oz0 tenant export writes everything the cluster holds for one tenant into one folder: its configuration (plugins, agents' policies, models, teams, routines, channels, settings), its records (the audit trail, usage, the inventory, erasures, approvals), and, with --content, every job as oz0 job export --content writes it. Each file is JSON or JSON lines, the manifest says what every file holds, and the cluster's audit key signs it, so anyone can check the export with one binary and no network.

Use it to take a tenant to another cluster or another product, to keep a copy outside the cluster, or to show that you could. It is not a backup: see what a backup is for. A public authority can answer a request for records and archive them from an export with ai.oz0/public-access.

Choose a task

TaskGuide
Tenant export formatOpen guide

Export a tenant

Terminal
oz0 tenant export acme --out ./acme-export
oz0 tenant export acme --out ./acme-export --content                   # jobs' content too
oz0 tenant export acme --out ./acme-2026q3 --since 2026-07-01T00:00:00Z
oz0 tenant export acme --tar acme-export.tar --content
  • The tenant's admins and owners may export it (permission tenant.export), and so may its privacy officers. --content also needs the content permission: what jobs read and wrote, content columns such as a board note's body, and audit entries' private parts are in the export only then.
    Without it, a decision record's digests of what its job read and of its calls' arguments are keyed for that one export (as they are wherever a reader without content sees them): steps with the same input still show the same digest, but none can be checked against a guess of the input or compared with another export's.

So are the digests of job files in file_labels and file_scopes, which still agree with each other, a held history's in held_histories, and an envelope block's digest of its job's input in authority_envelopes, whose MAC is left out as well, since it would confirm the same guess.

The same holds for a row whose content a wall or someone else's private chat withholds.

  • --since exports configuration whole and the records that have a time (jobs, the audit trail, usage, approvals, errors) from that time on. Export a long history in pieces with successive --since values.
  • Someone else's private chat (one that has an owner who is not you, and is not shared with you or a team of yours) is exported without what was said in it: its jobs' histories carry no content, its turns' rows in tables/ keep their facts with the content columns null (counted in each table's private_rows), and its audit entries keep their private parts out.
    With --chats-reason "..." (and the content permission) it is exported with its content, and each such chat is audited as conversation.view.private with your reason, which its owner sees, as when you read one chat: once by the edge for the jobs' histories and once by the server for the tables and the audit trail ("part": "tables"). The manifest's private_chats says how many chats were withheld or read with the reason.
  • The walls of the tenant's label policies hold for the person who exports. A job a wall closes to them is exported without its content, with a note in the content manifest saying a wall withheld it; a reason for private chats never opens a wall.

In tables/, a row that stores labels (a board note, a plugin record) keeps its content columns only where its labels are open to the exporter, and a job's row that stores none (a routine output, a reported result, a chat request, or a note kept before labels were) only where the exporter's own content part exported that job with its content and the row was made by the time that part was; otherwise they are null, counted in the table's walled_rows.

A content part made before the tenant's tool policy (which holds the label policies) last changed vouches for nothing: the export is refused, and you export the jobs' content again.

An audit entry's private part follows the labels that decide it: the job's labels when the edge recorded the entry, besides the entry's own; a job's entry that kept none (one recorded before the edge kept them) is withheld.

The manifest's walls says how many rows and entries were withheld.

To export everything, an exporter the walls do not apply to makes the export.

  • Every export is in the audit trail before it reads anything: tenant.export (and tenant.export.content and one job.export per job with --content).
  • The command checks the export when it is written, as --verify does, and fails if a check fails. --timeout bounds the whole export (no limit by default).
  • The server writes its part of the export to its temporary folder (TMPDIR, readable by the server's user only) while it reads the database, and sends it once it has read everything, so a slow download never holds the database up for other tenants.
    Give the server's temporary folder room for the tenant's records and plugins; the folder is removed when the export ends. One export of a tenant runs at a time, and at most four on one server: another is refused as busy (resource_exhausted) at once, so try again when the running one has finished.
    The client must read each message within a minute and the whole export within the time its size takes at 1 MiB/s (five minutes at least); a client that reads more slowly gets deadline_exceeded and its place is free for others, so export again from a faster connection.

--content reads each job's history from Temporal through an edge, decrypted with the tenant's keys, so it needs the edge's address as other job commands do. A job's content is capped at 16 MiB as in job export; the steps past it are listed in the job's file. A content part holds at most 100,000 jobs; the content manifest says when it stopped, and a later --since exports the rest.

Verify an export

Terminal
oz0 audit key                                                   # on the cluster: the audit key's fingerprint
oz0 tenant export --verify ./acme-export --key 3f9a…            # anywhere: no connection is opened

--verify checks, in this order, and stops at the first check a later one would rely on:

  1. the manifest's signature, with the audit key whose fingerprint you give (without --key it trusts the key the export carries and says so);
  2. every file's size and SHA-256, and that no file is missing or was added;
  3. each table file's row count;
  4. the content manifest's signature, with a gateway key the signed manifest lists, and its files;
  5. the audit part's hash chain, as oz0 audit verify --file does; an export made without the content permission holds blinded entries, which only the signature covers, so it fails without --key unless --unpinned says that showing the export is whole is enough;
  6. the evidence part, as oz0 evidence verify does; it holds keyed digests, which only its signature covers, so it too fails without --key unless --unpinned says that showing the export is whole is enough.

It exits 0 when every check passed, 1 when one failed, and 2 when the export could not be read. --json prints the result as JSON. The evidence part's two-way check (whether the gateway's record of each model call matches what the job's history says) is about what the cluster did, not about the export: its findings are listed under findings and in a note, and oz0 evidence verify <export>/evidence explains them; they do not fail --verify.

What is never in it

  • A secret's value, as the cluster stores it. Secrets are listed by name, who set them and when (tables/tenant_secrets.jsonl without its sealed column). In jobs' histories (content/jobs/), every secret the edge knows of six characters or more is replaced by [redacted:<name>], as in job export.
    That redaction covers jobs' histories only: with --content, the content columns of the tables (a board note's body, a routine output's arguments and answer, a chat request) and audit entries' private parts are exported as stored. Management, which writes them, cannot read secrets' values, so it cannot look for them. If an agent may have written a secret's value into a note or an output, export without --content, or check those columns before you pass the export on.
  • A sealed or wrapped key: payload keys, data subjects' keys, job keys.
  • A credential's hash: API keys', join tokens', passwords' (accounts are the cluster's), and short-lived tokens such as a chat message's reply token.
  • An erased data subject's data. Erasure deletes their jobs' rows and destroys their keys; the export has their erasure record (a hash, never a name) and nothing of the jobs: every row that names one of their jobs (as job, tree, root or workflow_id) is left out, for every erasure however old, pending or done.
  • Per-person figures. Usage, outside spend included, is exported as the usage export writes it: no column names a person or a provider's account, and a team only where works-council mode shows it to you; the teams it hides are summed into one row. The per-subject daily spend (quota_usage) and the per-budget counters (budget_holds) are left out, and a model call's reservation is exported without the budget keys that name whose quota it charged.
  • Another tenant's data, and the cluster's own: accounts, operators, the CA, servers.

Exports and backups

A backup and a tenant export answer different questions.

server backuptenant export
ForRestoring this cluster after a lossTaking a tenant elsewhere, or keeping its records outside the cluster
HoldsEvery tenant's records in the management database, in the schema of the dayOne tenant, in documented formats
Secrets and keysSealed; using them requires the master key and any separate secrets keyNever: names and metadata only
JobsTemporal's database in dev mode; not in a clusterWith --content, each job's history decrypted and redacted
Checked byserver restore, with the master keytenant export --verify, with the audit key's fingerprint, offline
WhoOperatorsThe tenant's admins and privacy officers

Retained-history chunk bytes are included in database backups and downloaded with oz0 retention held --out. A retry preserves earlier staging attempts until hold release or keep-for expiry allows the archive to be removed; repeated source changes can therefore increase storage while the hold or retention period continues. Source checkpoints and storage identities stay private even when a tenant export includes content.

Importing

There is no import yet. What one needs is known: plugins installed again from their artifacts (so each passes the target cluster's gates and signature checks), configuration applied through the target's own commands (so each change is checked and audited), secrets set again by their owners (no value leaves), members mapped to the target's accounts, and jobs as records only: a running job cannot move between Temporal clusters.

The format: open guide.

Tables: open guide.

Copyright © 2026