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. |
Legal holds¶
| 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.