Unreleased documentation. These pages follow the main branch and can change before the next release. For supported guidance, use v0.43.0.
Look up here the HTTP API and events a compiled Base Registry Engine (BReg) package serves. Every
registry serves the same route shapes over the Record types, fields, profiles, and Actions your
project declares. API stability and versioning states what v1.0.0 promises.
For the request and response schemas of each operation, use the
API operations viewer, rendered from the facility template’s compiled OpenAPI
document. A running server publishes its own document at /openapi.json, filtered to the caller’s
routes, with the exact schemas its package compiled.
For what Actions and Proposals are and how to declare them, use Actions and Proposals. For deployment and recovery, use deploy a registry. For receivers and replay, use bind webhook receivers. For every YAML key, use the configuration reference.
Authentication and profile selection
Section titled “Authentication and profile selection”Every request except /health, /healthz, and /ready carries Authorization: Bearer <token>.
The token is a JWT from the configured issuer that carries iss, aud, and exp. Its scopes come
from the claim named by scopeClaim, split on scopeSeparator; the principal and purpose claims are
the ones named under authorityClaims. A missing or rejected token answers
401 authentication.refused before any profile is selected.
Each route has one default access profile; select another with the accessProfile query option. A
profile admits the caller when the token satisfies its requiredScopes, requiredPurposes, and
principalClaim, and the profile grants the operation on that Record type.
An unknown route, an unsupported method, an unknown or ungranted profile, and an ungranted
operation all answer the same 404 resource.not_found. Reads conceal records outside the caller’s
boundary; see Read refusals. A mutation rechecks authority over its targets and
prospective values and answers 412 precondition.failed when that fails, with no protected values
and no effects.
Routes
Section titled “Routes”Placeholders use the router’s snake_case form. {route} is the Record type’s configured route,
{record_id} a record UUID, {read_path} a declared read path, {action} an Action ID, and
{transition} one of the promoted Proposal transitions. Read routes answer GET only; only the
probes also answer HEAD.
| Route | Operation | Available when | Response |
|---|---|---|---|
GET /v1/registry | metadata | The caller reaches at least one route | Caller-filtered Record types, operations, Action descriptors, and native Proposal capabilities. A caller with no visible route gets the concealed 404. |
GET /v1/schemas/{record_type_id} | metadata | The caller reaches the Record type | JSON Schema for one record of the Record type named by its ID, the target of the describedby link. |
GET /openapi.json | metadata | The caller reaches at least one route | The compiled OpenAPI document, filtered to the caller's routes. |
GET /health and GET /healthz | liveness | Always | {"status":"alive"}. |
GET /ready | readiness | Always | {"status":"ready"}, or 503 runtime.not_ready until the package and database are usable. |
POST /v1/records/{route} | create | Configured Record type with this granted operation | 201 with the record envelope, Location, and ETag. |
GET /v1/records/{route} | list | Configured Record type with this granted operation | Collection. |
GET /v1/records/{route}/{record_id} | get | Configured Record type with this granted operation | Record envelope with ETag. |
GET /v1/records/{route}/{record_id}/access-log | get | Record type declares accessLog; the authenticated caller reaches the record through get and is its current subject | A bounded page of the record's retained subject-facing access events. limit defaults to 50 and is at most 100; cursor continues from an event ID. |
PATCH /v1/records/{route}/{record_id} | patch | mutationMode: mutable | Record envelope with data.snapshot. Requires If-Match. |
DELETE /v1/records/{route}/{record_id} | tombstone | tombstone: true | Envelope of the final revision with data.snapshot. Requires If-Match. |
POST /v1/records/{route}:lookup | lookup | Record type declares selectorProfiles | Record envelope, or 404 lookup.unresolved. |
POST /v1/records/{route}:batch | batch | Record type declares batch | {"snapshot", "results": [...]}. |
POST /v1/records/{route}/ingestion-runs | batch | Record type declares batch | 201 {"run"}: the run document with the binding, the announced counts and bounds, and nextChunkIndex. |
GET /v1/records/{route}/ingestion-runs | batch | Record type declares batch | {"runs", "hasMore", "nextCursor"} over the runs the caller created. Accepts accessProfile, limit (1 to 100, default 25), the keyset cursor from nextCursor, status, and inputDigest. after answers 400 query.invalid; any other key answers the concealed 404. |
GET /v1/records/{route}/ingestion-runs/{run_id} | batch | Record type declares batch | {"run"}. Another caller's run and an unknown run are the same 404. |
POST /v1/records/{route}/ingestion-runs/{run_id}/chunks | batch | Record type declares batch | {"run", "receipt"} after a commit or an exact replay. Takes no Idempotency-Key. |
POST /v1/records/{route}/ingestion-runs/{run_id}/cancel | batch | Record type declares batch | {"run"} after cancelling an open or blocked run. Empty body, no Content-Type, no Idempotency-Key. |
GET /v1/records/{route}/ingestion-runs/{run_id}/chunks/{chunk_index}/receipt | batch | Record type declares batch | The chunk receipt, or 410 ingestion.receipt_erased once the record history it describes is erased. |
GET /v1/records/{route}/{record_id}/revisions | revisions | Configured Record type with this granted operation | Collection of the record's revisions, at most 100 per read. |
GET /v1/records/{route}/{record_id}/revisions/{revision} | revisions | Configured Record type with this granted operation | Record envelope for one revision. |
GET /v1/records/{route}:snapshot | snapshot | Configured Record type with this granted operation | Collection as recorded at a saved snapshot. |
GET /v1/records/{route}:current | list | Record type declares temporal | Collection of the records valid now. |
GET /v1/records/{route}:as-of | list | Record type declares temporal | Collection of the records valid at asOf. |
GET /v1/records/{route}/{record_id}/{read_path} | list through readPaths[] | Record type declares readPaths | Collection of related records. |
POST /v1/actions/{action}/target-conditions | invoke | Immediate Action advertises required target conditions | {"preconditions": {...}} for the targets the input names. |
POST /v1/actions/{action}/availability | invoke | Immediate Action has at least one reference input | Advisory availability for one referenced target: true or false with its observed revision, or null when more input is required or the current profile cannot inspect the guard safely. |
POST /v1/actions/{action} | invoke | Action mode is immediate and the caller holds Invoke | {"action", "applicationId", "results"}. Requires Idempotency-Key. |
POST /v1/review-completions | review completion receiver | A review authority configures a completion token and recipient binding | 204 after durable acceptance or an exact replay. Requires bearer authentication, Registry-Recipient-Binding, and an Idempotency-Key equal to the completion event ID. |
GET /v1/gis | GIS adapter | listener.publicOrigin is configured; the response includes only GIS collections visible to the caller | Landing document with links to the service description, conformance document, and collection inventory. |
GET /v1/gis/api | GIS adapter | listener.publicOrigin is configured; the response includes only GIS collections visible to the caller | Caller-filtered OpenAPI 3.0 description for the visible collection item routes. |
GET /v1/gis/conformance | GIS adapter | listener.publicOrigin is configured | An empty conformsTo array; the bounded adapter makes no OGC conformance claim. |
GET /v1/gis/collections | GIS adapter | listener.publicOrigin is configured; the response includes only GIS collections visible to the caller | Collection inventory for direct list profiles with readable primary geometry and an explicit bbox grant. |
GET /v1/gis/collections/{collection} | GIS adapter | listener.publicOrigin is configured and the caller reaches the governed GIS collection | Collection detail with the Record type, access profile, primary geometry, page bound, fields, and item link. |
GET /v1/gis/collections/{collection}/items | list through the GIS adapter | listener.publicOrigin is configured and the caller reaches the governed GIS collection | Authorized GeoJSON FeatureCollection page. Accepts bbox, limit, cursor, and f=json. |
POST /v1/actions/{action}/proposals | start | Action mode is proposal and the caller holds Start with target authority | Durable Proposal with its current revision and promoted links. Requires Idempotency-Key. |
GET /v1/proposals | list | Caller holds explicit Proposal List authority | {"items", "nextCursor"} page of the Proposals the caller may see. Accepts only accessProfile, action, owner=caller, state, turn, target (a record UUID), cursor, and limit (1 to 100, default 50), each at most once. top answers 400 query.invalid; any other key, a repeated key, or a bad value answers the concealed 404. A page may hold fewer than limit items. |
GET /v1/proposals/{proposal_id} | get | Caller holds Proposal Get and current ownership/target scope | Proposal with its frozen binding, application status, authorized links, and ETag. |
GET /v1/proposals/{proposal_id}/submissions/{submission_version} | get | Caller holds Proposal Get and current ownership/target scope | One exact submission: its frozen effectDigest, its review result, its decision when one was made, and its application outcome as separate members, or 410 proposal.submission_expired once its result window has lapsed and no application receipt remains. |
POST /v1/proposals/{proposal_id}/{transition} | Promoted transition | Proposal promotes the exact authorized transition | Updated Proposal with a new ETag. Requires Content-Type: application/json, Idempotency-Key, and If-Match carrying the Proposal ETag. |
GET /v1/proposals/{proposal_id}/review-status | get-review-status | Caller holds a matching Proposal review-status grant | Closed review-status document; service grants are authority-scoped and value-free. |
GET /v1/proposals/{proposal_id}/review-context | get-review-context | Caller holds Proposal context and current record disclosure | Only permitted review context fields. |
GET /v1/proposals/review-context | get-review-context | Each item requires current Proposal context and Record disclosure authority under the selected profile | Pass 1 to 100 distinct canonical Proposal UUIDs in comma-separated ids and select accessProfile. Ordered items contain each ID with status: visible and its context, or status: concealed without a document. Operational failures refuse the whole batch; the response is bounded at 8 MiB. |
GET /v1/proposals/{proposal_id}/attachments/{slot_id} | Attachment download | Proposal declares the slot and promotes its caller-authorized download | Retained bytes under current Proposal disclosure and retention. |
PATCH /v1/proposals/{proposal_id}/attachments/{slot_id} | Attachment upload | Draft Proposal declares the slot and promotes its caller-authorized upload | Updated Proposal after bounded upload. Requires Idempotency-Key and If-Match carrying the Proposal ETag. |
DELETE /v1/proposals/{proposal_id}/attachments/{slot_id} | Attachment removal | Draft Proposal declares the slot and promotes its caller-authorized removal | Updated Proposal after removal. Requires Idempotency-Key and If-Match carrying the Proposal ETag. |
A successful GET /v1/registry carries a Registry-Engine-Version header with the engine release
that served it, the value breg --version prints. The concealed 404 does not carry it.
Record envelope
Section titled “Record envelope”Reads, creates, patches, tombstones, revisions, and snapshots return the Registry Record envelope:
{ "data": { "recordIdentifier": "2d7e5e0c-5b2c-4b8e-9f1e-0d3c1a2b3c4d", "revisionIdentifier": "3", "domainData": { "code": "SITE-001", "label": "North depot", "location": {"type": "Point", "coordinates": [30.5234, -1.9441]} } }, "meta": { "registryIdentifier": "asset-registry", "datasetIdentifier": "asset-sites", "entityTypeIdentifier": "asset-site" }}| Member | Type | Meaning |
|---|---|---|
data.recordIdentifier | string, UUID | Stable identity of the record. |
data.revisionIdentifier | string, ^[1-9][0-9]*$ | Revision counter, starting at 1. Compare as a string. |
data.domainData | object | The record’s fields under their apiName, narrowed to the grant’s readableFields and to $select. |
data.snapshot | string | On mutation responses only: an opaque token for :snapshot reads, at most 4096 bytes. |
meta.registryIdentifier, meta.datasetIdentifier, meta.entityTypeIdentifier | strings | The registry, dataset, and Record type IDs from the compiled package. |
Fields travel under their apiName: by default the camelCase form of the field ID, so
household-code is householdCode. The field ID is never accepted in its place.
| Header | Value |
|---|---|
Content-Type | application/json, or application/ld+json when the request sent Accept: application/ld+json. The JSON-LD form adds @context pointing at https://id.registrystack.org/contexts/registry-record/v1. |
ETag | "breg-hmac-sha256:<64 hex>", a strong validator of the current revision. Send it back in If-Match. |
Link | <https://id.registrystack.org/profiles/registry-record/v1>; rel="profile", then </v1/schemas/{record_type_id}>; rel="describedby". |
Location | On 201 Created: /v1/records/{route}/{record_id}. |
Each Record type’s generated JSON Schema carries these encodings.
| Field type | JSON encoding | Notes |
|---|---|---|
boolean | true or false | |
string | string | minimumLength and maximumLength from the field. |
text | string | maximumLength from the field. |
int64 | integer | Full 64-bit range; clients that parse JSON numbers as doubles lose precision above 2^53. |
decimal | string | Decimal digits with the declared precision and scale, never a JSON number. |
date | string | RFC 3339 full date. |
timestamp | string | RFC 3339 date-time. |
uuid | string | Lower-case UUID. |
vocabulary-code | string | One of the vocabulary values; the schema carries them as an enum with x-registry-vocabulary. |
reference | string | The target record identifier as a UUID. |
crs84-point | object | GeoJSON Point, {"type": "Point", "coordinates": [longitude, latitude]}. |
structured | object | Validated against the declared schema, bounded by maximumBytes, advertised as x-registry-maxBytes. |
Collections and paging
Section titled “Collections and paging”A collection read returns items, pageInfo, and the same meta as a single record. Collection
members carry recordIdentifier, revisionIdentifier, and domainData directly, so the field path
is items[0].domainData, not items[0].data.domainData:
{ "items": [{"recordIdentifier": "…", "revisionIdentifier": "1", "domainData": {}}], "pageInfo": {"nextCursor": "eyJ…"}, "meta": {"registryIdentifier": "asset-registry", "datasetIdentifier": "asset-sites", "entityTypeIdentifier": "asset-site"}}nextCursor is null on the last page. To continue, send it as $skiptoken with no other option
except accessProfile. The cursor is signed with the runtime cursor.secretRef, binds the whole
original query, and expires after cursor.maximumAgeSeconds; a cursor that fails any of those
checks answers 400 query.cursor_invalid. With $count, the response carries the matching total.
Query options
Section titled “Query options”Options apply only where the grant permits them: $select draws from readableFields, $filter
from filterableFields, $orderby from sortableFields, and $count needs allowCount. Anything
else answers 400 query.invalid with fieldPath naming the parameter. An unknown field and a
withheld field return the same problem, and the field itself is never named.
| Option | Routes | Meaning | Bound |
|---|---|---|---|
accessProfile | Every record route | Evaluate the request under a profile other than the route default. The caller must satisfy that profile. | One profile identifier. |
$select | Collection reads | Comma-separated domain fields to return. Envelope members are never selectable. | 128 fields. |
$filter | Collection reads | Predicate over filterableFields: eq, ne, lt, le, gt, ge, in, startswith(), contains(), combined with and, or, not. | 32 predicates, depth 16, 128 nodes, 100 in values, 1024-byte literals. |
$orderby | Collection reads | One field from sortableFields, ascending. A desc direction is refused with query.invalid. | One field. |
$top | Collection reads | Page size. | 100. |
$count | Collection reads | Adds the matching count. Refused unless the grant sets allowCount. | Grant flag. |
$skiptoken | Collection reads | Continuation from pageInfo.nextCursor. It carries the original query, so repeating snapshot or validAt beside it is refused. | Expires after cursor.maximumAgeSeconds. |
bbox | list on Record types with a crs84-point field | west,south,east,north in CRS84 degrees, permitted by spatialQueries.bbox on the grant. | 256 bytes, plus the span limits on the grant. |
asOf | :as-of | The valid-time instant to evaluate, in the type of the Record type's valid-time fields. | One value. |
snapshot | :snapshot | A data.snapshot value returned by an earlier mutation. | 4096 bytes. |
validAt | :snapshot | The valid-time instant to evaluate inside the snapshot, for temporal Record types. | One value. |
| Whole query string | Every route | Decoded size of all options together. | 16 KiB. |
$filter=status eq 'active' and (region in ('north','east') or startswith(label,'Depot'))$filter=not contains(label,'closed') and validFrom ge 2026-01-01bbox=30.0,-2.1,30.9,-1.8Strings are single quoted, numbers and dates are bare, and null compares with eq and ne.
Literals must fit the field type.
Read refusals
Section titled “Read refusals”A read never reveals whether a record outside the caller’s boundary exists. Row boundaries, membership boundaries, and consent requirements all answer the same way, and each read re-evaluates them against current data.
| Read | Answer |
|---|---|
get, lookup, or access-log of a record outside the row, membership, or consent boundary | 404 resource.not_found, or 404 lookup.unresolved for a lookup, identical to an absent record. |
Collection, :snapshot, $count, or $skiptoken continuation over such records | 200 with those records left out and the count taken over the visible records only. A cursor replay re-evaluates the boundary. |
| Revisions of such a record | 404 resource.not_found, identical to an absent record. |
GET /v1/records/{route}/{record_id}/{read_path} for a path the selected profile holds no read-path grant for | 404 resource.not_found, identical to an unknown path. |
| A read path rooted at a record outside the boundary | 200 with no items, identical to an absent root record. No related record is disclosed. |
$select, $filter, or $orderby naming a field outside the read-path grant | 400 query.invalid; the field is not named. |
Mutations
Section titled “Mutations”Every write needs Content-Type: application/json, except a patch, which needs
application/json-patch+json. A refused body answers 400 request.invalid; see
Problem documents for its fieldPath.
Create
Section titled “Create”POST /v1/records/{route} with the fields the grant lists in writableFields:
{"data": {"code": "SITE-001", "label": "North depot", "location": {"type": "Point", "coordinates": [30.5234, -1.9441]}}}The response is 201 Created with the envelope, ETag, and Location. Missing required fields,
values outside their type bounds, a reference to a missing or tombstoned target, and a violated unique
constraint are refused before anything is written.
PATCH /v1/records/{route}/{record_id} takes a JSON Patch document whose paths are /data/
followed by the field’s apiName:
[ {"op": "replace", "path": "/data/label", "value": "North depot (annex)"}, {"op": "add", "path": "/data/closedOn", "value": "2026-12-31"}]If-Match must carry the ETag of the revision you read: a stale value answers
412 precondition.failed and a missing one 428 precondition.required. The response is the new
revision’s envelope with data.snapshot.
Reference values must name active targets, as on Create.
Tombstone
Section titled “Tombstone”DELETE /v1/records/{route}/{record_id} with If-Match writes a final revision that marks the
record tombstoned and returns that revision’s envelope. The record stays readable through its
revisions and leaves live collection reads. The route exists only when the Record type sets
tombstone: true.
An active Record that references the target prevents its tombstone, including when you cannot
read that referencing Record. The response is the generic 409 mutation.conflict.
Clear an optional reference, point it at an active replacement, or tombstone its source Record
before tombstoning the target. Tombstoned Records and retained history keep their reference IDs.
POST /v1/records/{route}:batch applies several items to one Record type in one transaction,
bounded by its batch.maximumItems and batch.maximumBytes:
{ "items": [ {"operation": "create", "data": {"code": "SITE-002", "label": "East depot"}}, {"operation": "patch", "recordId": "2d7e5e0c-…", "ifMatch": "\"breg-hmac-sha256:…\"", "patch": [{"op": "replace", "path": "/data/label", "value": "West depot"}]} ], "changeContext": { "kind": "correction", "reasonCode": "effective-date-corrected", "reasonText": "Site start date was recorded one month late.", "sourceReferences": ["case-document:correction-001"] }}The response is {"snapshot", "results"}. Each result carries operation, id, revision,
etag, and data.
changeContext is optional and covers the whole batch; an item-level override is refused. A
correction requires a nonempty reasonCode. reasonCode holds at most 64 UTF-8 bytes,
reasonText at most 4 KiB, and sourceReferences at most 16 entries of 256 bytes each. Temporal
non-overlap constraints are checked on the final state of the batch. One stale ifMatch or one
violated constraint rolls back every item.
Idempotency
Section titled “Idempotency”Send Idempotency-Key (1 to 256 visible ASCII bytes, without a comma or semicolon) on any create,
patch, tombstone, or batch. Proposal starts and transitions, attachment changes, immediate Actions,
and statistics publications require it.
- Keys are scoped to the verified issuer and principal; two callers never share one.
- A spent key answers
409 idempotency.conflicton any different request: the binding names the route, method, target, body, package activation, profile, and verified claims. - Only successes spend a key. Correct a refused request and retry with the same key.
- An exact repeat returns the held response without writing, for
retention.receiptRetentionDaysafter the commit (7 days by default), then answers410 idempotency.expiredand is never executed; read the record instead.
Ingestion runs
Section titled “Ingestion runs”An ingestion run is the durable checkpoint of one bulk load that the client drives one chunk at a
time. The client keeps the source; the server keeps the checkpoint and never recomputes the source.
Only a chunk submission advances a run, and every operation rechecks the caller’s authority against
the compiled batch route. A run is visible only to the issuer and principal that created it;
another caller’s run and an unknown run answer the same 404. Run responses carry operational
metadata only, never source rows or record values. Every ingestion response carries
Cache-Control: no-store.
The ingestion routes refuse an Idempotency-Key header with 400 request.invalid. The server
derives each chunk’s attempt key from the run ID, input digest, chunk index, and chunk digest, so
resubmitting the exact chunk is always safe. For the procedure, see
load records through an import window.
Create a run
Section titled “Create a run”POST /v1/records/{route}/ingestion-runs, Content-Type: application/json, a body of at most
4 KiB with exactly these members:
{ "operation": "create", "profileId": "operator", "packageRevision": "sha256:9f2c…", "schemaFingerprint": "sha256:1a7b…", "inputDigest": "5e8d…", "inputLength": 48211, "itemCount": 12000, "chunkCount": 13, "chunkAlgorithmVersion": "greedy-canonical-http-batch-v1"}| Member | Rule |
|---|---|
operation | create or patch. A profile that holds import announces create. |
profileId, packageRevision, schemaFingerprint | Nonempty strings that must match the selected profile and the active package. |
inputDigest | SHA-256 of the whole source, 64 lowercase hexadecimal characters. |
inputLength, itemCount, chunkCount | Integers from 1 to 2^53 − 1. |
chunkAlgorithmVersion | Exactly greedy-canonical-http-batch-v1. |
The answer is 201 Created with {"run": <run document>}. An import run is created only while the
Record type holds an open import authority for the profile whose remaining volume covers
itemCount and, when the authority pins digests, lists this inputDigest; otherwise the answer is
412 precondition.failed and no run exists.
The run document
Section titled “The run document”| Member | Meaning |
|---|---|
runId | The run identifier, a lowercase UUID. |
status | open, complete, cancelled, or blocked. |
blockedReason | null, or activePackageChanged or importAuthorityClosed while blocked. |
recordType, operation, profileId | The bound Record type, operation, and profile. |
packageRevision, schemaFingerprint | The binding the run was created under. |
inputDigest, inputLength, itemCount, chunkCount, chunkAlgorithmVersion | The announced source. |
maximumItems, maximumBytes | The batch bounds every chunk stays inside, fixed at creation. |
nextChunkIndex | The chunk index the next submission must name. |
committedItems, committedPrefixDigest | Items committed so far and the prefix digest of the last committed chunk; the SHA-256 of empty input before the first. |
lastAttempt | null, or {"outcome", "chunkIndex"}. |
createdAt, updatedAt | RFC 3339 timestamps. |
complete | true when status is complete. |
lastAttempt.outcome is committed, replayed, invalidItem, refused, bindingChanged,
importAuthorityClosed, chunkMismatch, runNotOpen, or unavailable. invalidItem and
refused leave the checkpoint where it was: continue the remainder in a successor run.
A run whose binding no longer matches the active package reports blocked with
activePackageChanged. An import run whose authority closed, expired, or ran out of volume reports
blocked with importAuthorityClosed at its next chunk, which commits nothing and answers
409 ingestion.run_blocked. A blocked run stays readable and never resumes; continue in a successor
run.
Submit a chunk
Section titled “Submit a chunk”POST /v1/records/{route}/ingestion-runs/{run_id}/chunks, Content-Type: application/json, with
exactly these members:
| Member | Rule |
|---|---|
chunkIndex | Nonnegative integer. A fresh chunk must equal nextChunkIndex. |
items | 1 to maximumItems batch items, each the object the :batch route accepts, all with the run’s operation. |
digest | SHA-256 of the canonical JSON of {"items": [...]}, 64 lowercase hexadecimal characters. That canonical body must fit in maximumBytes. |
prefixDigest | SHA-256 of the source bytes from the start through the end of this chunk’s last item. On the last chunk it must equal inputDigest. |
The chunking algorithm is greedy: each chunk takes as many consecutive source items as fit in
maximumItems and maximumBytes, and every remaining chunk must still hold at least one item.
A committed chunk answers 200 with {"run", "receipt"}. Its records, revisions, audit, receipt,
and checkpoint advance commit in one transaction. A chunk index below nextChunkIndex is a replay:
when its digest and prefixDigest equal the stored ones, the answer is the stored receipt with
replayed: true and nothing is written. Any other mismatch of index, digest, prefix digest, count,
or operation answers 409 ingestion.chunk_mismatch and writes nothing.
| Receipt member | Meaning |
|---|---|
chunkIndex, digest | The committed chunk. |
replayed | true when this answer replays an earlier commit. |
erased | Always false in a served receipt. |
batch | The {"snapshot", "results"} answer the chunk committed, projected to the fields the run’s profile can read now. |
GET /v1/records/{route}/ingestion-runs/{run_id}/chunks/{chunk_index}/receipt returns the same
receipt without resending the chunk. A receipt is erased with the record history it describes;
then the read, and an exact replay of that chunk, answer 410 ingestion.receipt_erased. The
chunk’s counts stay in the run.
List, read, and cancel
Section titled “List, read, and cancel”The list and read routes are in the routes table. A restarting client rebuilds its plan from the run document alone and never resends a committed chunk.
POST /v1/records/{route}/ingestion-runs/{run_id}/cancel takes an empty body with no
Content-Type and cancels an open or blocked run, answering 200 with {"run"}. A complete
or cancelled run answers 409 ingestion.run_not_open. The counts, committed prefix, receipts, and
audit stay.
History reads
Section titled “History reads”GET /v1/records/{route}/{record_id}/revisions returns a collection of the record’s revisions,
at most 100 per read, and .../revisions/{revision} returns one. Revisions show stored field
values only; derived fields and read paths are not evaluated over history. Correction context from
a batch appears only when the revisions grant lists provenanceFields (from kind,
reasonCode, reasonText, sourceReferences) and every revision that batch produced is visible
to the caller.
A temporal Record type, one with temporal.startField and temporal.endField, gains :current,
the records whose interval contains now, and :as-of, the records whose interval contains asOf.
Intervals include the start and exclude the end; a null end is open. asOf is a UTC RFC 3339
timestamp such as 2026-06-05T00:00:00Z.
GET /v1/records/{route}:snapshot without snapshot captures the latest committed state and
returns its token with the page, so paging stays consistent while writes continue. With
snapshot, it reproduces that earlier state. validAt filters by effective time inside the
snapshot and is refused on a non-temporal Record type; it takes a calendar date for a date start
field and a UTC RFC 3339 timestamp for a timestamp one. asOf and recordedAsOf are refused
on this route.
A snapshot token grants nothing: the caller needs the snapshot operation, and current row
boundaries apply. A snapshot whose history was erased, whose field metadata no longer matches, or
whose two-second database budget runs out answers 503 source.unavailable.
Proposals
Section titled “Proposals”An Action with execution.mode: proposal starts a durable Proposal at
POST /v1/actions/{action}/proposals. The routes are in the routes table;
Actions and Proposals explains the model.
| Operation | Purpose | Authority |
|---|---|---|
edit | Update the caller-owned draft inputs. | Explicit Edit and current draft/target scope. |
submit | Freeze the exact plan and queue required review or application. | Explicit Submit and current author-stage target authority. |
revise | Retire the frozen submission and return to an editable draft. Refused once a Review rejected the Proposal. | Currently promoted Revise; old approval cannot authorize changed contents. |
rebase | Create a new draft binding after target/package evolution. | Current Start and Rebase; requires resubmission and fresh review when required. |
retry | Retry eligible recoverable application work. | Explicit Retry; automatic work additionally requires a verified human for external recovery. |
cancel | Cancel currently cancellable durable work. | Explicit Cancel under current ownership and target scope. |
apply | Apply only the exact frozen effects. | Separate Apply with current guards and required fresh approval; automatic system authority is restricted to its frozen binding. |
A Proposal read carries an ETag. Every transition, attachment upload, and attachment removal
requires If-Match with that value and answers 428 precondition.required without it. The
Proposal ETag is distinct from a record ETag and is bound to the caller’s profile, principal,
purpose, and target scope as well as the Proposal revision, so refetch after every transition and
never reuse another caller’s value. Follow only the transition and attachment links the current
representation promotes.
Observe an exact submission
Section titled “Observe an exact submission”GET /v1/proposals/{proposal_id}/submissions/{submission_version} reads one exact submission under
the Proposal Get authority: its frozen effectDigest, its review result, its decision, and its
application outcome as separate members. Only the current submission reports application
progress. Once its result window has lapsed and no application receipt remains, it answers
410 proposal.submission_expired; the Proposal stays readable.
Proposal attachments
Section titled “Proposal attachments”GET, PATCH, and DELETE /v1/proposals/{proposal_id}/attachments/{slot_id} read, upload, and
remove one slot’s bytes. Slot descriptors on the Proposal carry requiredness, byte limit, content
types, classification, verification, and the links the caller may follow. Submission binds the
exact content digest; changed content needs a new submission and fresh review.
Immediate actions
Section titled “Immediate actions”An Action with execution.mode: immediate runs one atomic plan at POST /v1/actions/{action} with
Idempotency-Key. Input names are exact apiName values, validated against the compiler-emitted
JSON Schema in the Action descriptor. When the descriptor advertises target conditions, read them
first at POST /v1/actions/{action}/target-conditions. Invoke requires Action authority over every
target. Effects, revisions, audit, receipt, and outbox commit together or not at all; resend the
original request with its original key when the outcome is uncertain. See
Actions and Proposals for the model.
An immediate Action with a reference input advertises one routes.availability endpoint at
POST /v1/actions/{action}/availability. Send targetInput with the reference input’s exact
apiName, plus an input object containing its UUID and any declared form values already known.
Each request checks one target. Query several reference inputs separately.
The response reports advisory available as true, false, or null. A known answer includes
observedRevision. guard-not-satisfied means a field guard failed. input-required names a
nonempty set of declared API input names in missingInputs. not-disclosed carries
observedRevision: null and no missing-input names because the selected profile cannot safely
inspect the guard or boundary. A known failed guard takes precedence over missing guard input.
No availability response names a guard ID or a Record value.
The route evaluates only field guards for that one target. It does not run a Function, test SQL
uniqueness or Evidence, inspect other targets, supply a condition token, or grant mutation
authority. A true answer means only that the selected target’s field guards are currently satisfied,
including when it has none. Form completeness, Functions, Evidence, other targets, and SQL
constraints may still refuse. Invoke checks authority, concurrency, and every guard in the commit
transaction.
Problem documents
Section titled “Problem documents”A refusal is an RFC 9457 problem document with Content-Type: application/problem+json:
{ "type": "https://id.registrystack.org/problems/registry-breg/request/invalid", "title": "Bad Request", "status": 400, "detail": "The request is invalid.", "code": "request.invalid", "traceId": "3f1a9c2e5b7d4e6f8a9b0c1d2e3f4a5b", "fieldPath": "/data/householdCode"}Branch on code. type is the same code with each dot read as a path segment under
https://id.registrystack.org/problems/registry-breg/. detail is the registered text of the code.
traceId is 32 hexadecimal characters that also appear in the request log and, for every journaled
request, in its audit record. A request refused before admission for presenting no credential is
counted on the metrics listener rather than journaled; see
scrape metrics.
fieldPath is present only when one member is at fault:
| Refusal | fieldPath |
|---|---|
| Create body | /data for a missing data member or a field you may not write, known or not; /data/<apiName> for a missing required field or a wrong value of a field you may write. |
| Patch document | /<n> for operation n, /<n>/op, /<n>/path for a path outside /data/ or a field you may not write, known or not, and /<n>/value. |
| Batch body | /items, /changeContext, /items/<n>, /items/<n>/operation, /items/<n>/recordId, /items/<n>/ifMatch, /items/<n>/patch, and the create and patch forms above under /items/<n> and /items/<n>/patch. |
| Header | Idempotency-Key when it is missing or malformed; If-Match when a batch request sends one, because each batch item carries its own ifMatch. |
| Query | The parameter: $select, $filter, $orderby, $top, $count, $skiptoken, bbox, accessProfile, asOf, snapshot, or validAt; on statistics routes also from, to, or status. A revision route takes none but accessProfile and names the first other one you sent. |
A name you sent is never echoed: an unknown member and a withheld one stop at the same location.
An oversized or non-JSON body, an undefined query parameter, a malformed query string, and the
lookup, ingestion, attachment, and GIS routes carry no fieldPath.
A direct create, patch, or batch that violates an authored row-local constraint answers
409 mutation.conflict and may add the declared location:
{ "recordType": "permit", "constraintId": "chronological-validity", "constraintKind": "compare", "left": "valid-from", "right": "valid-to"}compare carries left and right; int-range and vocabulary carry field. The members appear
only when the selected profile can read every referenced field, and never on Action or Proposal
effects. They hold declared identifiers only, never a submitted value or database message. An
explicit constraint id is the stable key to map on; an omitted one is compiler-derived and
changes with the declaration. unique and temporal-non-overlap conflicts stay generic.
| Code | Status | When |
|---|---|---|
authentication.refused | 401 | A bearer token is missing or rejected: bad signature, wrong issuer or audience, expired, denied key, or a claim outside policy. |
resource.not_found | 404 | A verified token that does not satisfy the profile, a route the profile cannot reach, or a record outside the caller's row boundary. The server does not distinguish these cases. |
lookup.unresolved | 404 | A lookup matched zero records or more than one. |
request.invalid | 400 | The body, headers, or path did not match the compiled contract. fieldPath names the body member or header at fault when one is, and never a field name the grant does not admit. |
query.invalid | 400 | A query option was malformed, over a bound, or not permitted by the grant. fieldPath names the query parameter, such as $select, when one is at fault. |
query.cursor_invalid | 400 | A $skiptoken that is malformed, was issued for a different query, or has expired. |
action.refused | 422 | An Action function intentionally refused the operation. refusalCode identifies a declared reason, detail contains its static label, and optional fieldPath names an input. No effects commit. |
action.guard_failed | 412 | An immediate Action field guard failed and the selected profile can safely read the target and the field that establishes the failure. Hidden guard fields, failed authority, and stale conditions remain precondition.failed. No effects commit. |
action.handler_failed | 500 | An activated Action function failed to produce an accepted result. Operators use the trace identifier and safe server diagnostics to repair the package. A function deadline returns 503 service.unavailable instead. |
action.evidence_failed | 503 | An immediate-action handler declared an Evidence dependency that could not be acquired or accepted: the provider did not answer, or the assertion was refused, failed verification, or exceeded the retention bound. fieldPath names the declared Evidence alias when the failure is bound to one. No effects commit. |
unsupported.media_type | 415 | A body without the required Content-Type. |
precondition.required | 428 | A route that requires If-Match was called without it. |
precondition.failed | 412 | If-Match did not match the current revision, or a proposed create, patch, or batch row was outside the caller's authorized boundary. Boundary refusals are value-free and retrying the unchanged request cannot succeed. |
proposal.rejected | 409 | A Review rejected the Proposal, and a rejection is final: Submit, Edit, Revise, Rebase, Apply, Retry and Cancel are refused. Start a new Proposal to try again. |
proposal.review_result_mismatch | 412 | The current Review result differs from the retained approval. Apply commits no effects. Reconcile the review result before attempting application again. |
proposal.submission_expired | 410 | The submission's review result window has lapsed or its detail was erased, and it has no application receipt. The Proposal itself stays readable. |
mutation.conflict | 409 | A constraint or workflow rule refused the write. A storage-pattern failure includes the declared recordType and fieldId; correct the input before retrying. A 409 does not imply that retrying an unchanged request can succeed. |
idempotency.conflict | 409 | The caller reused the Idempotency-Key with a different request, another write included, or retried a key whose held response a retention erase removed. A byte-identical body also conflicts when the first attempt committed under a different package activation, access profile, or claims. |
idempotency.expired | 410 | The caller's exact retry arrived after the receipt horizon (retention.receiptRetentionDays, 7 days by default). The first attempt committed and the key stays spent, so the request is never executed again. |
ingestion.profile_mismatch | 403 | The selected profile is not the one the run bound, or does not satisfy the compiled batch route for the bound operation. A run id grants nothing on its own. |
ingestion.run_not_open | 409 | The run completed or was cancelled, so it accepts no chunk and no cancel. The run stays readable. |
ingestion.run_blocked | 409 | The run is blocked: the active package revision or schema fingerprint no longer matches the run binding, or the import authority the run consumes closed, expired, or has too little volume left for the chunk. The run's blockedReason names which. The run stays blocked and inspectable; continue in a successor run. |
ingestion.chunk_mismatch | 409 | The chunk index, chunk digest, or rolling prefix digest does not match the run's expected next chunk. Nothing was written; reread the run and resubmit the exact chunk. |
ingestion.receipt_erased | 410 | The chunk receipt was erased with the record history it describes. The chunk and its counts stay visible in the run. |
request.timeout | 504 | The request exceeded operationalTimeouts.httpRequestMilliseconds. |
source.unavailable | 503 | The database refused or timed out while the request held it. |
service.unavailable | 503 | The runtime could not complete the mutation for a reason other than the request itself. |
runtime.not_ready | 503 | The readiness probe before the package and database are usable. |
runtime.field_encryption.unavailable | 503 | The route reaches a Record type with encrypted fields while the field-encryption key state is not usable. Record types without encrypted fields keep serving. |
statistical_dataset.domain_violation | 500 | A non-null source value falls outside a declared dimension domain; the value is not disclosed. |
statistical_dataset.release_refused | 422 | The period or lifecycle state is ineligible; refusalCode names a closed reason. |
statistical_dataset.version_conflict | 409 | The active package changed or a fresher computation superseded this publication. |
statistical_dataset.version_withdrawn | 410 | The directly requested version was withdrawn; reasonCode names the recorded reason. |
Events and webhooks
Section titled “Events and webhooks”Record type hooks[]Source preview, pre-1.0, 27 key pathsProposal hooks[]Source preview, pre-1.0, 12 key paths
Record type hooks[]
Source preview, pre-1.0
Generated from products/breg/generated/authoring/registry-module.schema.json. 27 key paths. Explained in docs/site/src/content/docs/configure/breg.mdx.
| Key path | Type | Required | Accepted values | Constraints |
|---|---|---|---|---|
displayAuthored presentation only; labels never establish identity or authority. | object | No | Not specified | None specified |
display.descriptionHuman-readable display text. Bidirectional embedding, override, and isolate controls, byte-order marks, line and paragraph separators, and Unicode noncharacters are refused; multilingual joiners and direction marks remain valid. | string | No | Not specified | None specified |
display.labelHuman-readable display text. Bidirectional embedding, override, and isolate controls, byte-order marks, line and paragraph separators, and Unicode noncharacters are refused; multilingual joiners and direction marks remain valid. | string | No | Not specified | None specified |
handlerGoverned, destination-neutral delivery. destinationId is a key in
runtime eventDestinations; the project carries no URL or secret, and
deployment configuration may tighten the bounds it binds but cannot
supply or widen this authority. Production compilation requires it. | object | No | Not specified | None specified |
handler.abiFor rhai: The handler ABI the script speaks. For wasm: The handler ABI the module speaks. | string | Conditional | Not specified | None specified |
handler.destinationIdKey in the runtime destination binding. | string | Conditional | Not specified | None specified |
handler.moduleModule path in the project. | string | Conditional | Not specified | None specified |
handler.scriptScript path in the project. | string | Conditional | Not specified | None specified |
handler.type | string | Yes |
| None specified |
idStable hook contract identifier, sent as ce-type. Use a new identifier for a breaking payload change. | string | Yes | Not specified |
|
phaseWhen the hook runs relative to the triggering transaction. | string | Yes |
| None specified |
principalThe access profile a proposal from this hook is applied under.
Optional: a hook that declares none is a non-proposing hook, and a
proposal from one is refused and dead-lettered at delivery time.
Declaring none is not an authoring error. | string | No | Not specified | None specified |
projectionDeclared field identifiers to include in values. System event metadata is included separately. | array | Yes | Not specified |
|
projection[]Array item | string | No | Not specified | None specified |
triggerCommitted record change that can produce this hook. | string | Yes |
| None specified |
whenOptional field tests, combined with AND. Omit to run on every matching trigger. | object | No | Not specified | None specified |
when.afterEqualsRequired values after the change. Valid with created and patched triggers. | object | No | Not specifiedSchema default, where declared: {} | None specified |
when.afterEquals.*Map valueA comparison literal: a boolean, a number, or text. | boolean | number | string | No | Not specified |
|
when.afterIsNullFields that must be unset after the change. Valid with created and patched triggers. | array | No | Not specifiedSchema default, where declared: [] |
|
when.afterIsNull[]Array item | string | No | Not specified | None specified |
when.beforeEqualsRequired values before the change. Valid with patched and tombstoned triggers. | object | No | Not specifiedSchema default, where declared: {} | None specified |
when.beforeEquals.*Map valueA comparison literal: a boolean, a number, or text. | boolean | number | string | No | Not specified |
|
when.beforeIsNullFields that must be unset before the change. Valid with patched and tombstoned triggers. | array | No | Not specifiedSchema default, where declared: [] |
|
when.beforeIsNull[]Array item | string | No | Not specified | None specified |
when.changedFields whose values must change. Only valid with the patched trigger. | array | No | Not specifiedSchema default, where declared: [] |
|
when.changed[]Array item | string | No | Not specified | None specified |
when.type | string | Yes |
| None specified |
Proposal hooks[]
Source preview, pre-1.0
Generated from products/breg/generated/authoring/registry-project.schema.json. 12 key paths. Explained in docs/site/src/content/docs/configure/breg.mdx.
| Key path | Type | Required | Accepted values | Constraints |
|---|---|---|---|---|
displayAuthored presentation only; labels never establish identity or authority. | object | Conditional | Not specified | None specified |
display.descriptionHuman-readable display text. Bidirectional embedding, override, and isolate controls, byte-order marks, line and paragraph separators, and Unicode noncharacters are refused; multilingual joiners and direction marks remain valid. | string | Conditional | Not specified | None specified |
display.labelHuman-readable display text. Bidirectional embedding, override, and isolate controls, byte-order marks, line and paragraph separators, and Unicode noncharacters are refused; multilingual joiners and direction marks remain valid. | string | Conditional | Not specified | None specified |
handlerWhere a hook's handler runs.
The pairing rule: rhai requires script, wasm requires module,
url requires destinationId, and each
kind requires exactly its own fields and nothing else. abi is required on
rhai and wasm and forbidden on url; its value is closed and checked at
compile time in [crate::validate_hooks]. | object | Conditional | Not specified | None specified |
handler.abiFor rhai: The handler ABI the script speaks. For wasm: The handler ABI the module speaks. | string | Conditional | Not specified | None specified |
handler.destinationIdKey in the runtime destination binding. | string | Conditional | Not specified | None specified |
handler.moduleModule path in the project. | string | Conditional | Not specified | None specified |
handler.scriptScript path in the project. | string | Conditional | Not specified | None specified |
handler.type | string | Conditional |
| None specified |
idA local identifier: a lowercase letter, then up to 63 lowercase letters, digits, _, or -. | string | Conditional | Not specified |
|
transitionsOmit to observe every committed lifecycle transition. | array | Conditional | Not specified |
|
transitions[]Array item | string | Conditional |
| None specified |
Record hooks live under recordTypes[].hooks; Proposal hooks under
actions[].execution.proposal.hooks. Capture commits with the source operation and delivery
follows the commit, so later edits never change a captured event. The deployment’s
eventDestinations supplies the URL, network and TLS policy, HMAC key, classification ceiling, and
limits; it may narrow the compiled contract but never widen it.
HTTP request
Section titled “HTTP request”| Trigger | changed | beforeEquals | afterEquals | Projected snapshot |
|---|---|---|---|---|
created | Not allowed | Not allowed | Allowed | After |
patched | Allowed | Allowed | Allowed | After |
tombstoned | Not allowed | Allowed | Not allowed | Before |
| Header | Format or value | Meaning |
|---|---|---|
Accept | application/json | Accepted response media type. |
Content-Type | application/json | Canonical JSON event data. |
ce-specversion | 1.0 | CloudEvents version. |
ce-id | UUID | Captured event identity; unchanged by retry or replay. |
ce-source | urn:registrystack:registry:<registry-id>:instance:<instance-id> | Registry instance identity. |
ce-type | Captured event type | Configured record event ID or native proposal.<transition> event type. |
ce-time | RFC 3339 timestamp | Mutation capture time; unchanged by retry or replay. |
ce-dataschema | urn:breg:event-schema:<registry-id>:<record-type-id>:<event-id>:sha256:<digest> | Record payload schema identity and fingerprint; Proposal events use https://id.registrystack.org/contracts/breg/proposal-event/v1. |
Idempotency-Key | sha256:<digest> | Opaque key for deduplicating automatic retries. |
X-Registry-Event-Generation | Positive integer, initially 1 | Increments on operator replay. |
X-Registry-Delivery-Attempt | Positive integer, initially 1 | Attempt number within the generation. |
X-Registry-Delivery-Time | RFC 3339 timestamp | Current attempt time; used for receiver freshness checks. |
X-Registry-Signature | v1=<base64url-without-padding> | HMAC-SHA-256 signature over the versioned signing input. |
| Field | Type | Meaning |
|---|---|---|
id | UUID string | Captured event identity; equals ce-id. |
type | String | Record events: the authored hook ID. Proposal events: proposal.<transition>. Equals ce-type. |
source | urn:registrystack:registry:<registry-id>:instance:<instance-id> | Registry instance identity; equals ce-source. |
time | RFC 3339 timestamp, UTC, whole milliseconds | Mutation capture time; equals ce-time. |
subject | Object with recordReference and recordRevision | Record events: recordReference is hmac-sha256:<64-hex>, never the raw record id, and recordRevision is the record revision the change produced. Proposal events: recordReference is the Proposal path /v1/proposals/<proposal-id> and recordRevision is the Proposal revision after the transition. |
dataschema | urn:breg:event-schema:<registry-id>:<record-type-id>:<event-id>:sha256:<digest> | Captured payload schema identity; equals ce-dataschema. Proposal events use their dedicated closed schema. |
data | Object | The captured event data, described below. |
causation | Object with root, hop, and optional parent | Chain position. A captured registry mutation is a root event: root equals id, hop is 0, and parent is absent. |
| Field | Type | Meaning |
|---|---|---|
recordType | String | Authored Record type ID. |
recordId | UUID string | Record identity. |
revision | Positive integer | Record revision after the mutation. |
trigger | created, patched, or tombstoned | Captured record mutation trigger. |
packageRevision | sha256:<digest> string | Package active at capture time. |
values | Object | Exactly the declared projection, using authored field IDs; unset optional fields are null. |
| Field | Type | Meaning |
|---|---|---|
actionId, actionRevision | String and digest | Exact governed Action binding. |
proposalId, submissionVersion, revision | UUID and positive integers | Exact durable work and transition revision. |
fromState, state, transition | Closed strings | Native lifecycle transition and its states. transition is started, edited, submitted, revised, rebased, retried, cancelled, applied, rejected or review-returned; rejected ends the Proposal in the terminal rejected state. |
effectDigest | Digest or null | Frozen plan identity when available. |
authorityKind | caller, system or review | Authority used for the transition; review for a transition a recorded review result caused. |
systemApplyGrantRevision | Digest or null | Restricted system grant binding for automatic application. |
turn | party, official or nobody | Whose move the Proposal waits on after the transition. |
A Proposal event’s id is derived from its Action, Proposal, revision, and transition, so the same
transition always carries the same event identity.
Signature
Section titled “Signature”X-Registry-Signature is v1= followed by the unpadded base64url HMAC-SHA-256 of the signing
input, under a key of at least 32 bytes. The signing input is the bytes breg-webhook-signature-v1
followed by these fields in order, each as an unsigned 64-bit big-endian length and then its bytes:
the version 1.0, ce-id, ce-source, ce-type, ce-time, ce-dataschema,
X-Registry-Event-Generation, X-Registry-Delivery-Attempt, X-Registry-Delivery-Time, the method,
the request target (the destination’s configured path), Content-Type, Idempotency-Key, and the
exact body. The body is at most 1 MiB and the other fields together at most 32 KiB.
Compare in constant time, then check the expected source, type, schema, and the freshness of
X-Registry-Delivery-Time, not of ce-time, before accepting durably.
Delivery, retry, and replay
Section titled “Delivery, retry, and replay”| Property | Contract |
|---|---|
| Delivery mode | Asynchronous, after commit, at least once. |
| Acknowledgement | Any 2xx response. |
| Failed attempt | Redirect, other non-2xx response, timeout, or transport failure. Redirects are not followed. |
| Total attempts | Compiled ceiling: 5 per generation, including the first attempt. Runtime may lower it. |
| Attempt timeout | Compiled ceiling: 5,000 ms. Runtime may lower it to a minimum of 100 ms. |
| Retry delays | 1, 2, 4, then 8 seconds, stopping at the deployed attempt limit. |
| Payload size | Event-specific compiled bound, at most 1 MiB. |
| Payload retention | retention.hookPayloadRetentionDays: default 7, range 1 to 30 days, measured from capture. Replay does not extend expiry. |
| Successful payload | Erased when the successful terminal state commits. |
| Ordering | No global or per-record ordering guarantee. |
| Property | Automatic retry | Operator replay |
|---|---|---|
| Event ID, source, type, time, and schema | Unchanged | Unchanged |
| Body and captured package revision | Unchanged | Unchanged |
| Generation | Unchanged | Incremented |
| Idempotency key | Unchanged | New key |
| Attempt number | Incremented | First send is 1 |
| Delivery time and signature | New for the attempt | New for the attempt |
| Payload expiry | Unchanged | Unchanged |
| State | Meaning | Shown by webhook list | Replay eligible |
|---|---|---|---|
pending | Waiting for an attempt or retry. | Yes | No |
leased | An attempt is in progress. | No | No |
delivered | Acknowledged successfully; payload erased. | No | No |
dead-lettered | Attempt limit exhausted. | Yes | Only with unexpired retained payload and matching generation and destination binding. |
expired | Payload retention ended. | Yes | No |
Operator commands
Section titled “Operator commands”| Field | Type | Meaning |
|---|---|---|
eventId | UUID string | Captured event ID; matches ce-id. |
deliveryId | String | Compiled delivery identifier used for replay. |
generation | Positive integer | Current generation used as --expected-generation. |
state | pending, dead-lettered, or expired | Reported delivery state. |
attempt | Nonnegative integer | Attempt count; 0 before the first send of a generation. |
payloadAvailable | Boolean | Payload is retained and unexpired. |
payloadExpiresAt | RFC 3339 timestamp | Capture-based payload expiry. |
replayEligible | Boolean | Dead letter with an available payload; replay also validates generation and destination binding. |
| Command | Required arguments | Result |
|---|---|---|
explain events | <project> | Compiled event inventory; no database access. |
webhook sample | <project> --event <event-id> | Synthetic request with a placeholder signature; no HTTP send. |
webhook list | --runtime-config <absolute-file> | Delivery metadata without record values; optional --limit defaults to 50 and accepts 1 to 100. |
webhook replay | --runtime-config <absolute-file> --event-id <uuid> --delivery-id <id> --expected-generation <number> | Queues an eligible dead letter in a new generation; does not confirm receiver acceptance. |
Replay and discard name the exact event, delivery, and expected generation. Replay starts a new
generation; discard stops future attempts and cannot undo an earlier acceptance. Each command’s
--help lists its flags.
Spatial adapter
Section titled “Spatial adapter”A Record type with a crs84-point field and a geojson.geometryField is also served under
/v1/gis for desktop GIS clients, with bbox, limit (at most 10000), and cursor on the item
route. It answers GeoJSON feature collections under the same profile, grant, and row-boundary rules
as the record routes and declares the profile breg-gis-bounded-crs84-point-bbox-v1. It makes no
OGC API Features conformance claim and computes no collection extents.
Statistical datasets
Section titled “Statistical datasets”| Route | Profile grant | Result |
|---|---|---|
GET /v1/statistics/{dataset}:live | Live profile with count-capable source list grant | Exact JSON or CSV; optional from and to. |
GET /v1/statistics/{dataset}/releases | Released-data reader, publisher, or live profile | Version headers and pageInfo; $top and opaque $skiptoken. |
GET /v1/statistics/{dataset}/releases:series | Released-data access | Latest eligible document per period; required from and to, optional status=final. |
GET /v1/statistics/{dataset}/releases/{period} | Released-data access | Latest eligible JSON or CSV; optional status=final. |
GET /v1/statistics/{dataset}/releases/{period}/versions/{version} | Released-data access | Immutable JSON or CSV; 410 after withdrawal. |
POST /v1/statistics/{dataset}/releases/{period}/versions | Publisher | 201 version header; {"status":"provisional"} or {"status":"final"}; Idempotency-Key required. |
POST /v1/statistics/{dataset}/releases/{period}/versions/{version}/withdrawal | Publisher | 200 header with withdrawal; closed reason; Idempotency-Key required. |
Every statistical operation requires a profile selected with accessProfile; a released-data
reader needs no record permissions.
Document reads from the live, series, latest-version, and exact-version routes send
Cache-Control: no-store, Vary: authorization, accept, and Repr-Digest for the exact JSON or
CSV bytes. The release listing omits Repr-Digest. A version header’s contentDigest identifies
the canonical stored JSON; a withdrawn version’s header omits it. No strong ETag is issued.
CSV columns are period,periodStart,periodEnd,<dimension field IDs...>,value,status. A suppressed
value is blank; a zero stays zero with its status. Totals round independently; see
publish statistics for the
inference risks. Unknown datasets and ungranted profiles answer the concealed 404, and invalid
ranges 400 query.invalid.
Sources
Section titled “Sources”Configuration fields are schema-generated; the route, query, problem, and protocol tables are maintained data checked against the implementation.
- Deploy a registry for runtime configuration and erasure.
- Bind webhook receivers for destinations, the signing key, and replay.
- Actions and Proposals for the Action and Proposal model.
- Base Registry Engine configuration reference for every project, module, and runtime key.
- Create your first registry to see these responses from a running server.