---
title: "Export and verify a tenant"
description: "Export portable tenant records and verify their signature before using them."
canonical_url: "https://docs.oz0.ai/admin/tenant-export"
---
# 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](https://docs.oz0.ai/admin/tenant-export#exports-and-backups).
A public authority can answer a request for records and archive them from an export with
[ai.oz0/public-access](https://docs.oz0.ai/build/plugins/verified/public-access).

## Choose a task

<table>
<thead>
  <tr>
    <th>
      Task
    </th>
    
    <th>
      Guide
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Tenant export format
    </td>
    
    <td>
      <a href="https://docs.oz0.ai/admin/tenant-export/format">
        Open guide
      </a>
    </td>
  </tr>
</tbody>
</table>

## Export a tenant

```bash [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](https://docs.oz0.ai/admin/tenants): 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.<br />

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.<br />

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](https://docs.oz0.ai/agents/conversations): 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](https://docs.oz0.ai/govern/taint-and-labels/label-policies#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.<br />

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.<br />

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

```bash [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](https://docs.oz0.ai/govern/audit-trail/records#what-a-job-read-or-wrote), 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`.<br />

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](https://docs.oz0.ai/spend/cost-and-usage)
writes it: no column names a person or a provider's account, and a team only where
[works-council mode](https://docs.oz0.ai/govern/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](https://docs.oz0.ai/fleet/backups#back-up) and a tenant export answer different questions.

<table>
<thead>
  <tr>
    <th>
      
    </th>
    
    <th>
      <code>
        server backup
      </code>
    </th>
    
    <th>
      <code>
        tenant export
      </code>
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      For
    </td>
    
    <td>
      Restoring this cluster after a loss
    </td>
    
    <td>
      Taking a tenant elsewhere, or keeping its records outside the cluster
    </td>
  </tr>
  
  <tr>
    <td>
      Holds
    </td>
    
    <td>
      Every tenant's records in the management database, in the schema of the day
    </td>
    
    <td>
      One tenant, in documented formats
    </td>
  </tr>
  
  <tr>
    <td>
      Secrets and keys
    </td>
    
    <td>
      Sealed; using them requires the master key and any separate secrets key
    </td>
    
    <td>
      Never: names and metadata only
    </td>
  </tr>
  
  <tr>
    <td>
      Jobs
    </td>
    
    <td>
      Temporal's database in dev mode; not in a cluster
    </td>
    
    <td>
      With <code>
        --content
      </code>
      
      , each job's history decrypted and redacted
    </td>
  </tr>
  
  <tr>
    <td>
      Checked by
    </td>
    
    <td>
      <code>
        server restore
      </code>
      
      , with the master key
    </td>
    
    <td>
      <code>
        tenant export --verify
      </code>
      
      , with the audit key's fingerprint, offline
    </td>
  </tr>
  
  <tr>
    <td>
      Who
    </td>
    
    <td>
      Operators
    </td>
    
    <td>
      The tenant's admins and privacy officers
    </td>
  </tr>
</tbody>
</table>

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.

## Previous section links

<span id="the-format">

The format

</span>

: [open guide](https://docs.oz0.ai/admin/tenant-export/format#the-format).

<span id="tables">

Tables

</span>

: [open guide](https://docs.oz0.ai/admin/tenant-export/format#tables).


## Sitemap

See the full [sitemap](https://docs.oz0.ai/sitemap.md) for all pages.
