Skip to main content

HTTP API

The machine-readable contract is docs/openapi.json (OpenAPI 3.1), also served by a running instance at /openapi.json. This page adds the action per route, the bodies the document leaves out, and the refusal codes.

Conventions​

  • JSON in and out; request bodies at most 1 MiB.
  • Values are standard base64 strings in value.
  • A reference is {"tenant", "namespace", "key"}; each part is 1 to 255 bytes with no NUL byte.
  • Every response carries x-request-id, generated when the request has none.
  • Actions and authorities: Authentication.

Errors​

Every refusal is exactly {"error": "<reason phrase>", "code": "<code>"}. The code tells two refusals with one status apart, and neither field repeats the request.

StatusCodeMeaning
400malformed-bodythe body is not JSON of the route's shape
400malformed-path{transaction} is not a UUID
400malformed-referencea reference part is empty or holds a NUL byte
400invalid-referencea reference part is longer than 255 bytes
400empty-batcha prepared batch has no mutation
400cross-tenant-batcha batch names a tenant other than the caller's
401unauthorizedno bearer token, or the authority rejected it
403missing-actionthe principal lacks the route's action
403forbiddenthe principal's tenant differs from the request's
404not-foundno such secret or transaction, or a revoked secret on a workload read
404not-ownedon a user route, the caller owns no secret at the reference
404delete-target-missinga commit's delete names a reference with no secret
404route-not-foundno route has this path
405method-not-allowedthe path does not take this method
409duplicatethis transaction ID is already prepared in the tenant
413too-largethe body is longer than 1 MiB
503unavailablethe database, keyring or an authority is unavailable, or decryption failed

A body or path that does not parse is refused before the token is checked. not-owned answers the same bytes whether someone else owns the secret or none exists.

Metadata​

Metadata never contains a value. created_at and updated_at are PostgreSQL timestamptz text.

{
"id": "0199...",
"reference": {"tenant": "tenant-a", "namespace": "connectors", "key": "conn-123"},
"owner_subject": "user-42",
"disclosure": "workload_only",
"state": "active",
"version": 3,
"labels": {"provider": "example"},
"created_at": "2026-09-01 10:00:00.000000+00",
"updated_at": "2026-09-01 10:05:00.000000+00"
}

User routes​

RouteActionBodySuccess
GET /v1/user/secretssecret:listnone200 {"secrets": [Metadata]}: the caller's own, revoked included
POST /v1/user/secrets:detailsecret:read_metadatareference200 Metadata
POST /v1/user/secrets:revokesecret:revokereference200 Metadata, state: revoked
DELETE /v1/user/secretssecret:deletereference204

Workload routes​

RouteActionBodySuccess
PUT /v1/workload/secretssecret:writePutSecret200 Metadata of the new version
POST /v1/workload/secrets:getsecret:read_valuereference200 {"metadata", "value"}
POST /v1/workload/secrets:existssecret:read_metadatareference200 {"exists"}, true only when active
POST /v1/workload/secrets:listsecret:list{"tenant", "namespace"}200 {"secrets": [Metadata]}, every owner and state
POST /v1/workload/secrets:deletesecret:delete{"reference", "actor"?}204
{
"reference": {"tenant": "tenant-a", "namespace": "connectors", "key": "conn-123"},
"owner_subject": "user-42",
"value": "c2VjcmV0LWJ5dGVz",
"disclosure": "workload_only",
"labels": {"provider": "example"}
}

disclosure defaults to workload_only and labels to {}; an empty owner_subject becomes the calling workload's subject. Writing an existing reference adds a version and replaces owner, disclosure and labels; writing a revoked one makes it active.

Prepared batches​

A workload stages puts and deletes in its tenant and applies them atomically.

RouteActionBodySuccess
PUT /v1/workload/tenants/{tenant}/transactions/{transaction}secret:prepare{"actor", "mutations"}204
POST .../transactions/{transaction}/commitsecret:commitnone204
POST .../transactions/{transaction}/abortsecret:abortnone204
{
"actor": "system:serviceaccount:connectors:connectors",
"mutations": [
{"op": "put", "secret": {"reference": {"tenant": "tenant-a", "namespace": "connectors", "key": "new"}, "owner_subject": "user-42", "value": "bmV3"}},
{"op": "delete", "reference": {"tenant": "tenant-a", "namespace": "connectors", "key": "old"}}
]
}

{transaction} is a caller-chosen UUID. Nothing is visible until commit, which applies every mutation in one transaction or none of them, leaving the batch prepared. A batch expires 600 seconds after prepare; commit or abort after that, or of an unknown ID, is 404.

Service routes​

No authentication: GET /health/live (204), GET /health/ready (204, or 503 when the database does not answer), GET /metrics (Prometheus text), GET /openapi.json, GET /docs.

The OpenAPI document at 0.6.0 does not declare the bodies of secrets:list, secrets:delete and prepare, or the /metrics, /openapi.json and /docs routes; this page describes them.