Export your organization's data

An organization admin can export everything Ciresk dev holds for the organization into one signed archive. That covers sessions, their journals, every policy version, templates, the connection and egress logs, usage and the audit trail. This page explains how to start an export and download it, what the archive contains and how to check its signature. It also covers how long dev keeps each kind of record, and how to erase the organization's dev data.

Who can export

Starting an export and downloading it need the dev:org.export permission. Organization owners and admins hold it. A custom role can be given it too.

Only a person signed in as themselves can export:

  • An API key cannot be given this permission.
  • An agent is refused with principal_not_allowed.
  • There is no ciresk dev command and no MCP tool for it.

Export from the dev console, or call the API as yourself, with your own sign-in token, not an API key.

In the console

Open Data in the dev console at dev.ciresk.com. The page shows to anyone who holds dev:org.export or dev:org.erase. Each half of the page shows only to the holder of its own permission.

  • Export lists the organization's exports, newest first. For each one it shows the status, who requested it, when, the size and how long it is kept.
  • Request export starts one. It is built in the background; select Refresh to see it change.
  • Download on a ready export makes a download link at that moment, valid for 15 minutes. Each click is recorded in the audit log.

The erasure half of the page is described in Erase your organization's dev data.

Start an export through the API

Terminal
curl -X POST https://api.eu.ciresk.com/v1/dev/exports \
  -H "Authorization: Bearer $TOKEN"

The request answers 202 Accepted with the export:

JSON
{
  "id": "eu1_devexp_0192f3c4a5b67d8e9f0a1b2c3d4e5f60",
  "state": "pending",
  "requested_by": "user:01H...",
  "requested_at": "2026-10-25T09:12:03Z",
  "completed_at": null,
  "expires_at": null,
  "bytes": null,
  "sha256": null,
  "rows": null,
  "error": null,
  "download": null
}

An organization has at most one open export at a time. A request while one is pending or running answers that export instead of starting a second one.

List the organization's exports, newest first, to find an id again:

Terminal
curl "https://api.eu.ciresk.com/v1/dev/exports?limit=20" \
  -H "Authorization: Bearer $TOKEN"

The answer is a page, {"data": [...], "next_cursor": null, "has_more": false}. limit is 20 by default and at most 50, and there is no cursor: the list holds only the newest exports. A listed export never carries download; read the export itself for that.

The archive is built in the background. A build starts within about 15 seconds. If a build fails, it is tried again, up to three times in all.

StateMeaning
pendingwaiting to be built
runningbeing built
readybuilt; bytes, sha256 (of the zip), rows (row count per file) and expires_at are set
failednot built; error says why
expiredthe archive was deleted 7 days after it was built

Download it through the API

Read the export:

Terminal
curl https://api.eu.ciresk.com/v1/dev/exports/eu1_devexp_0192f3c4a5b67d8e9f0a1b2c3d4e5f60 \
  -H "Authorization: Bearer $TOKEN"

Once it is ready, the answer carries download, a URL valid for 15 minutes:

JSON
"download": {
  "url": "https://...",
  "expires_at": "2026-10-25T09:30:41Z"
}

Fetch the zip from that URL with a plain GET. It needs no Authorization header. Each read of a ready export creates a new URL and adds a dev.org.export.downloaded record to the audit log. If the URL has expired, read the export again.

What the archive contains

A zip file in the format ciresk-dev-export/1. The archive holds every record the organization still has, meaning everything retention has not deleted yet (see Retention). Each data file is JSON Lines: one JSON object per record, with that record's fields as stored.

FileWhat it holds
orgs.jsonlthe organization's dev record
sessions.jsonlevery session, live and ended
session_events.jsonlevery session's journal
writers.jsonl, device_grants.jsonl, members.jsonlwho wrote to each session, the devices attached to it, the people it was shared with
needs.jsonl, ci_records.jsonlwhat sessions asked their people, and the CI runs recorded on them
web_handoffs.jsonlsessions opened in the browser
org_policies.jsonlevery version of the dev policy
template_versions.jsonl, template_promotions.jsonl, template_files.jsonlpublished templates, what each repository runs, and their files
egress_decisions.jsonlthe egress decision log
connections.jsonlthe connection log
usage_ledger.jsonlusage, in vCPU-seconds
workers.jsonlthe organization's workers
demand_audits.jsonlwhen each service last polled for waiting sessions (bookkeeping for the audit log)
secrets.jsonleach session secret's name, hosts and bindings, never its value
dotfiles.jsonlthe dotfiles repository each person set, never the value of a private repository's credential
schedules.jsonlautostart schedules people and their agents set
idempotency_keys.jsonlthe answers kept for 24 hours to stop, pause, start and kill requests sent with an Idempotency-Key
org_erasures.jsonl, org_exports.jsonlpast erasure requests, cancelled ones included, and exports
audit.jsonlevery dev audit record of the organization, as the audit log shows it
manifest.jsonthe organization, region, export id, creation time, and each file's name, row count and SHA-256
manifest.sigthe signature over manifest.json

No secret is ever exported:

  • Session secret values and dotfiles credentials stay in the secret store, which an export never reads.
  • Worker and enrolment token hashes are left out.
  • The single-use credential a session starts with is left out.
  • The hash that redeems a browser link is left out.

Verify the archive

manifest.json lists each file with its row count and the SHA-256 of its contents. Check each file against it.

manifest.sig is a single line dvx_<payload>.<signature>:

  • <payload> is base64url (no padding) of a JSON object holding kid, exp, org_id, export_id and manifest_sha256. The last one is the SHA-256 of manifest.json, in hex.
  • <signature> is base64url (no padding) of an Ed25519 signature over the text dvx_<payload>.

To check it:

  1. Decode the payload. Confirm that manifest_sha256 matches your own hash of manifest.json and that org_id is your organization.
  2. Fetch the signing keys from GET /v1/dev/keys. This route needs an API key with dev:keys.read; members, admins and owners hold that permission. Each key is a JWK (kty: OKP, crv: Ed25519, x) with a kid and a verifies_until.
  3. Verify the signature with the key whose kid matches.

The signing keys rotate. A downloaded archive can be verified only while its key is still in that list, about seven weeks. If you need proof for longer, keep a copy of the key along with the archive.

Limits and errors

AnswerWhen
503 export_unavailablethe installation has no object store for exports (only on a self-hosted installation set up without one)
failed, error: "export_too_large: ..."the archive passed 512 MiB compressed
failed, error: "export_failed: three builds did not finish"three builds did not finish; start a new export
404 export_not_foundno export of your organization has that id
403 insufficient_scope, 403 principal_not_allowedsee Who can export

A problem answer's type links to the code's entry: https://docs.ciresk.com/docs/dev/troubleshooting#<code> for Ciresk dev's own codes, such as export_unavailable, and https://docs.ciresk.com/errors/<code> for codes every Ciresk API shares, such as insufficient_scope.

Retention

Ciresk dev deletes old records on its own. The windows are part of the organization's dev policy, under retention (see admin.md):

SettingDefaultWhat is deleted
retention.ended_sessions_days90a stopped or failed session this many days after it ended, together with its journal, device grants, members, needs, CI records, usage and browser links
retention.connections_days30connection log windows this many days after they ended
retention.egress_decisions_days30egress decision windows this many days after they ended
JSON
"retention": { "ended_sessions_days": 365, "connections_days": 90, "egress_decisions_days": 90 }
  • Each value is a whole number of days from 1 to 3650.
  • A change is a new policy version, set with ciresk dev policy set like any other field.
  • Deletion runs in the background, about twice an hour, and each run records dev.retention.swept in the audit log.
  • A paused session is not deleted.
  • A session that a later session resumed from is kept as long as that later session is.
  • Connection and egress windows go on their own windows, whatever their session's state.

Two kinds of data follow their own rules:

  • Audit records are kept 12 months by default. An admin with dev:policy.write can choose 7 or 10 years instead, on the Audit log page of the dev console at dev.ciresk.com, or through PUT /v1/dev/audit/retention with {"months": 84} (12, 84 or 120). GET /v1/dev/audit/retention reads the setting, with dev:audit.read.
  • Checkpoints in Ciresk's cloud are deleted 30 days after they were taken. That includes the checkpoint a paused session resumes from. A self-hosted installation sets its own rule.

Erase your organization's dev data

An admin with dev:org.erase can have every record Ciresk dev holds for the organization deleted. Only a person can do this; API keys and agents cannot, and neither the CLI nor the MCP tools offer it. Export first if you want a copy.

In the console, open Data and select Erase organisation data under Danger zone, then type the organization's id to confirm. While the erasure waits out its grace period, the page shows it as scheduled with a Cancel erasure action. Afterwards it shows the erasure under way, then done with its counts, or cancelled.

Through the API, type the organization's id back to confirm:

Terminal
curl -X POST https://api.eu.ciresk.com/v1/dev/erasure \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"confirm": "org_01H..."}'

At once:

  • Every live session is killed, with the reason org_erasure.
  • The answer is 202 with the erasure: state: "erasing", requested_at, erase_after (24 hours later) and killed, the number of sessions stopped.
  • Until the erasure finishes or is cancelled, ciresk dev up is refused with 409 org_erasing.
  • A second request while one is pending answers the pending one.
  • A confirm that is not your organization's id is refused with 422 validation_failed.

Cancel within the 24 hours

Terminal
curl -X DELETE https://api.eu.ciresk.com/v1/dev/erasure \
  -H "Authorization: Bearer $TOKEN"
  • Within the grace period this answers 200 with the erasure in state: "cancelled" and completed_at set. Nothing is erased, and new sessions can start again.
  • The sessions the request killed stay ended.
  • The audit log records dev.org.erasure.cancelled.
  • Cancelling an erasure that is already cancelled answers it as it is.
  • Once erase_after has passed, the cancel is refused with 409 erasure_started: the erasure may already be deleting.
  • With no pending or cancelled erasure, it answers 404 erasure_not_found.

After the 24 hours

  • Every dev record of the organization is deleted: sessions, journals, policies, templates, logs, usage, workers, schedules and exports.
  • Its checkpoints and export archives are deleted from storage.
  • Its session secret values and its people's dotfiles credentials are deleted from the secret store.
  • Each person and agent named in its audit records is erased. The audit records themselves stay, with the names behind them removed.
  • The erasure becomes erased and its counts are filled in: rows, subjects and checkpoints.

GET /v1/dev/erasure reads the latest erasure (erasing, erased or cancelled), with the same permission. After an erasure finishes, the organization can use Ciresk dev again, starting from the default policy.