Skip to content

Retention: API reference

All endpoints are under /api/v1 and need a Bearer API key or JWT. Admin endpoints need the admin:tenants scope. Tenant endpoints need events:read and only return the caller's own tenant.

Policies

Verb Path Scope Description
GET /admin/tenants/{tenant_id}/retention admin:tenants Policy and staged change
PUT /admin/tenants/{tenant_id}/retention admin:tenants Set the policy. 200 applied, 202 staged
DELETE /admin/tenants/{tenant_id}/retention admin:tenants Remove the policy (keep forever)
POST /admin/tenants/{tenant_id}/retention/confirm admin:tenants Apply the staged change now
DELETE /admin/tenants/{tenant_id}/retention/pending admin:tenants Cancel the staged change
GET /retention events:read The caller's own policy

PUT body:

{
  "hot_retention": "30d",
  "max_events": 5000000,
  "max_bytes": 10737418240,
  "action": "archive_then_delete",
  "lake": {
    "kind": "s3",
    "bucket": "acme-telemetry",
    "prefix": "phycloud",
    "credentials": { "access_key_id": "…", "secret_access_key": "…" }
  }
}

PUT response:

{
  "status": "pending",
  "effective_at": "2026-10-31T09:00:00Z",
  "retention": {
    "tenant_id": "acme",
    "policy": null,
    "pending": {
      "policy": { "hot_retention": "30d", "action": "archive_then_delete",
                  "lake": { "kind": "s3", "bucket": "acme-telemetry", "credentials_configured": true } },
      "effective_at": "2026-10-31T09:00:00Z",
      "requested_by": "key:1",
      "requested_at": "2026-10-28T09:00:00Z"
    },
    "updated_at": "2026-10-28T09:00:00Z",
    "updated_by": "key:1"
  }
}

Invalid policies return 422.

Runs

Verb Path Scope Description
POST /admin/tenants/{tenant_id}/retention/run?dry_run=true admin:tenants Run now, or plan with dry_run=true. 409 if a run is in progress
GET /admin/tenants/{tenant_id}/retention/runs?limit=20 admin:tenants Runs, newest first

A run record:

{
  "run_id": "1b6f…",
  "tenant_id": "acme",
  "trigger_kind": "manual",
  "dry_run": false,
  "status": "completed",
  "action": "archive_then_delete",
  "started_at": "2026-10-28T09:00:00Z",
  "finished_at": "2026-10-28T09:00:04Z",
  "from_sequence": 1,
  "to_sequence": 431977,
  "event_count": 431977,
  "byte_count": 612004112,
  "retained_by_hold": 0,
  "swept_count": 0,
  "manifest_ids": "[\"3f1c…\", \"a90e…\"]",
  "error": null
}
status Meaning
completed Finished. It may have had nothing to do.
dry_run A plan. Nothing changed.
paused Archiving failed; nothing more was deleted. See error.
held A tenant-level legal hold stopped the run.
policy_applied A due staged change was applied. The next run enforces it.
no_policy Manual run for a tenant without a policy.
failed An unexpected error. See error.
interrupted The process stopped during the run. The next run resumes.
Verb Path Scope Description
POST /admin/tenants/{tenant_id}/legal-holds admin:tenants Place a hold. Body: {"reason": "…", "source_id": "…"} (source_id optional)
GET /admin/tenants/{tenant_id}/legal-holds?include_released=true admin:tenants List holds
DELETE /admin/tenants/{tenant_id}/legal-holds/{hold_id} admin:tenants Release a hold

Verification

Verb Path Scope Description
GET /verify events:read Hot chain from the retention checkpoint
GET /verify/archive events:read Full chain across the lake and the hot store
GET /admin/tenants/{tenant_id}/verify/archive admin:tenants Same, for any tenant

The response fields are described in Verifying the full chain.

Storage usage

Verb Path Scope Description
GET /storage events:read The caller's tenant
GET /admin/tenants/{tenant_id}/storage admin:tenants One tenant
GET /admin/storage admin:tenants Every active tenant, with totals
{
  "tenant_id": "acme",
  "events": 41022,
  "payload_bytes": 58127311,
  "oldest_ingested_at": "2026-09-28T09:00:01Z",
  "newest_ingested_at": "2026-10-28T08:59:58Z",
  "checkpoint": { "sequence_number": 2588100, "content_hash": "9f2c…" },
  "archive": { "segments": 812, "events": 2588100, "bytes": 401226031 },
  "active_legal_holds": 0,
  "policy_action": "archive_then_delete",
  "hot_retention": "30d",
  "last_run": { "status": "completed", "…": "…" }
}

payload_bytes counts event payloads only. Indexes and row overhead add to the on-disk size, which /health reports as retention.database_bytes.