Skip to content
Registry StackDocsDevelopment (unreleased)

Registry Coordinator API

View as Markdown

Registry Coordinator serves one HTTP surface for starting a run of a served workflow, following and recovering that run, operating the deployment, and taking Casework completion wake-ups. Every route except /health, /ready, /v1/review-completions, and /v1/events/{workflow}/{trigger} takes a caller access token, and the caller policy that token resolves to decides what the caller may do. A Casework completion authenticates with its completion token, and a product event with its source’s delivery signature. The contract is pre-1.0 and carries no frozen compatibility promise.

Open the Registry Coordinator API operations for every route, parameter, schema, and reachable problem code.

Every caller request presents Authorization: Bearer <token> holding an RFC 9068 access token, typ: at+jwt, signed with RS256 or ES256 by the one issuer the deployment names, for its audience, to an exact client keyed in authentication.policies, with a lifetime of at most one hour. The token’s scopes are read from registry_scopes unless the deployment sets scopeClaim. A missing, malformed, or unverified token answers 401 authentication.refused with WWW-Authenticate: Bearer.

Each exact verified client identity resolves its keyed caller policy, which lists the scopes it requires and the actions it allows:

  • The run actions start, status, inspect, retry-same, cancel, and reconcile reach only the workflows the policy lists in workflows.
  • The deployment-wide actions doctor, restore-hold, release-restore-hold, release-admission-hold, complete-execution-recovery, and retain act on every workflow at once and need operator: true on the policy.

A resolved caller refused an action answers 403 operation.not-authorized. Run ownership and the idempotency scope come from the token’s exact verified iss and sub, never from input fields; input identifiers and grant references manufacture no caller authority. A caller allowed to start a run gains no authority over the products its workflow calls: each call uses the identity the operator configured for that connection, and the receiving product decides it.

GET /v1/workflows/{workflow} returns the active permitted contract, including workflowRevision, input, and declared outcomes. It requires the caller’s start action for that Workflow. Use that revision for fresh starts or a generated revision-pinned TypeScript/Python wrapper. Generated wrappers provide typed start, get, and cancel over the maintained public generic clients; they validate returned workflow identity and declared outcome names. They add no authority.

POST /v1/runs takes a Start body with exactly three members: workflow, its name of 1 to 128 characters; workflowRevision, the expected computed revision; and input, checked against the declared schema. It requires an Idempotency-Key header of 1 to 256 bytes. An admitted start answers 200 with the run’s status, admitted under the named workflow’s active definition only when its revision matches.

The same key from the same verified issuer and subject returns the original run for the same workflow revision and input. A conflicting revision or input answers 409 idempotency.key-reused. Retain the exact original revision during unknown-admission recovery, even if activation changes. A new workflow version cannot reinterpret a repeated key. A fresh key is a fresh run, so a client that does not know whether a start was admitted resends it under its original key.

A start is refused before admission when:

  • the caller’s policy does not list the workflow, with 403 operation.not-authorized;
  • the policy lists the workflow but the deployment does not serve it, with 404 workflow.not-served;
  • the expected revision differs from the active revision, with 409 workflow.revision-mismatch;
  • the input does not satisfy the workflow’s input schema and value bounds, with 400 input.invalid;
  • the instance runs in recovery-only mode, with 409 admission.recovery-only;
  • a restore left admissions held, with 409 admission.held.

POST /v1/events/{workflow}/{trigger} admits a run from a signed product event, when the workflow declares that trigger and the runtime file lists its source under eventSources. The body is the platform hook envelope, at most 1,048,576 canonical JSON bytes, above which it answers 413 request.body-too-large; the admitted event value must then fit the 131,072-byte value profile, or it answers 413 event.too-large. The X-Registry-Signature header carries the HMAC delivery signature over the envelope, the signed delivery headers, the method, and the exact request target; the ce-* headers must match the body and the trigger’s authored source, event type, and data schema. The delivery time must be within 30 seconds of the Coordinator’s clock, so a producer signs each delivery attempt afresh.

The exact source, event identity, and trigger bind admission across retries: a redelivered event returns the run it already admitted, and the run belongs to the authenticated source, not to a caller. An admitted event answers 204 with an empty body and an X-Coordinator-Run-Id header. An unknown workflow or trigger, or a signature that does not verify, answers 401 event.unauthenticated, a malformed envelope 400 event.invalid, and a source whose key is not configured or cannot be read 503 event.source-unavailable. The event grants no authority for the calls the run then makes.

GET /v1/runs/{run} answers the run’s RunStatus: its workflow, computed workflowRevision, bindingDigest, its current step and state, when it was admitted and when its deadline falls, whether an effect is uncertain, and whether it needs restore review. cancelRequested records ordinary stop cancellation; authoredCancelRequested records cancellation that took a completion wait’s authored cleanup path. A finished run adds its outcome and the reviewed output its workflow declares. A failed run adds its failureCode, and failurePath when the failure concerns a call input: an RFC 6901 pointer into that input, never the value.

A run that does not exist, a run another caller owns, and a run in a workflow the caller’s policy does not list all answer the same 404 run.not-visible. An operator policy reaches every owner’s runs in the workflows it lists.

GET /v1/runs answers at most 100 of the most recently admitted runs the caller may see, newest first. limit takes 1 to 100 and defaults to 20; any other value answers 400 request.limit-out-of-range. Optional state accepts running, finished, failed, cancelled, cancelled-after-effect, expired, attention, or cancel-requested. attention=true selects effective states attention and failed; attention=false excludes both. Filters compose. Results are ordered by admission time and run ID, newest first. When the response includes nextCursor, pass it as cursor with the same filters to continue. Every page reapplies current caller and workflow permissions. An invalid cursor or one from a different caller or filter answers 400 request.invalid. Keep the run ID a start returns for direct status lookup.

GET /v1/runs/{run}/inspect answers the run, one entry per step with its state, attempt count, whether its command is prepared, whether its effect is uncertain, and whether its receipt expired, and a recovery block. recovery.retryAllowed and recovery.reason describe whether the run can be scheduled again. For a compatible current call, recovery.operation names the pinned operation’s ID, version, product, effect, key and preparation requirements, recovery semantics, and readReceipt capability. Neither proves current product authority or that an original-key receipt exists. No status, list, or inspection answer carries original inputs, raw product responses, or prepared commands. Terminal output is only the authored outcome projection.

Inspection also includes effects with accepted or unknown state and pinned Operation revisions, and waits with their accepted step, named completion, completion revision, timeout, next observation, durable floor, observation count, and limit. Protected correlation values and commands remain absent. A wait’s blocked or uncertain evidence cannot become a business-failure outcome. Persistent read floors and original deadlines survive wake-up, restart, and recovery.

Every recovery and operator POST carries a JSON body:

  • cancel, reconcile, retry-same, restore-hold, and release-restore-hold take a Reason, whose reason is a bounded investigation reference of 1 to 256 characters without personal data, such as incident-2026-10-09. The audit journal records a keyed reasonRef, never the reference.
  • /v1/retention takes a Retention: a before cutoff in the past and a limit of 1 to 100. It answers how many payloads it erased.
  • /v1/release-admission-hold takes an AdmissionRelease: a recoveryReference and the two attestations admissionHistoryComplete and priorDeploymentFenced. A false or omitted attestation refuses the release.
  • /v1/complete-execution-recovery takes an ExecutionRecovery: a recoveryReference and the attestations executionHistoryComplete and priorDeploymentFenced.

The attestations are audited operator statements, not automated proof. A cancel answers the run’s status, a reconcile its inspection, and a retry-same the run ID and its scheduled status. An authorized cancellation of a parked completion wait with onCancel takes that authored path durably. A leased result read settles or expires before the path advances. Repeating cancellation preserves the cleanup, which can withdraw the original Casework request under current producer authority and the original deadline. Without that edge, or after the wait advanced, ordinary cancellation stops future calls where possible. It never reverses a product effect already accepted.

These routes authenticate and authorize before they report a malformed body. A body is application/json or another application media type ending in +json; any other media type answers 415 request.unsupported-media-type, after authorization. Bodies are bounded to 65,536 bytes, and duplicate or unknown members are refused. An empty, overlong, or control-character reason answers 400 request.reason-invalid, and an out-of-bounds cutoff or limit 400 request.retention-invalid. State and recovery conflicts answer 409.

POST /v1/review-completions takes Casework’s completion events for a review producer connection. It authenticates the sender by the Authorization: Bearer completion token and the Registry-Recipient-Binding header together, comparing the token in constant time, and refuses any other sender with 401 authentication.refused before reading the body. Without a configured completion block on the connection, it refuses every event.

An authenticated event is application/json of at most 2048 bytes, with type, eventId, requestId, resultId, and completedAt. It repeats its eventId as the Idempotency-Key header, and its completedAt is no older than 365 days and no more than five minutes ahead of the Coordinator’s clock. Anything else answers 400 request.invalid or 415 request.unsupported-media-type.

Every authentic, well-formed event answers 204, whether or not a run waits on its request. The event only brings forward the next result read of a run already waiting on that request: the run reads the result itself through the connection’s producer credential. An event starts no run. 503 service.unavailable asks Casework to deliver the event again.

Every refusal, including a request refused before its route runs and a refused completion event, is an RFC 9457 application/problem+json document with type, title, status, detail, code, and traceId. The type is the code with its dot replaced by a slash under https://id.registrystack.org/problems/registry-coordinator/. The title and detail are fixed sentences that belong to the code, and no member repeats a submitted value. A refusal carries Cache-Control: no-store and a traceparent header whose trace matches traceId; a 401 also carries WWW-Authenticate: Bearer, and a 503 carries Retry-After.

Every problem response carries one code from this closed catalogue, answered with the HTTP status listed beside it. request.not-found answers a path the contract does not declare, and request.unprocessable a body the HTTP framework rejects before an operation reads it, so no operation lists either. coordinatorctl reports its own diagnostics with coordinator.* codes, which this catalogue does not hold.

CodeStatusWhen
admission.held409Starts stay held after a restore until an operator releases the admission hold.
admission.recovery-only409This instance runs in recovery-only mode and admits no runs. Send starts to the ordinary service.
audit.response-unavailable503The operation may have completed, but its audited result could not be released. Inspect the run before repeating the operation.
authentication.refused401The bearer credential is missing, invalid, or expired.
binding.conflict409The run's original connection settings differ from this service's runtime configuration.
binding.live-conflict409Connection settings that runs in progress use differ from this service's runtime configuration.
definition.refused409The workflow definition, one of its mappings or one of its functions refused the request.
deployment.activation-changed409An apply changed the active packages after this service started. Restart it with the runtime file that apply used.
deployment.refused409The deployment's activation, runtime configuration or state key custody check failed.
evaluation.uncertain409An evaluation may have completed at its provider, so the run is never evaluated again.
event.invalid400The event envelope, source, type, schema, or trigger mapping does not match the authored contract.
event.source-unavailable503The configured event source key cannot be read. Restore that binding before redelivery.
event.too-large413The signed event exceeds the event value profile: 131072 canonical bytes, 32 levels of nesting, 4096 values, 256 items per array, 256 members per object, and 16384 bytes per string. Project fewer or smaller fields into the event.
event.unauthenticated401The delivery signature, signed metadata, or delivery time cannot be verified.
execution.held409Execution is under restore hold until an operator releases it.
workflow.not-served404This deployment does not serve the named workflow.
workflow.revision-mismatch409The expected workflow revision is not active. Discover the active contract before a new start; preserve the original revision for unknown-admission recovery.
idempotency.key-invalid400Supply an Idempotency-Key header of 1 to 256 bytes.
idempotency.key-reused409This idempotency key already names a different workflow revision or input.
input.invalid400The start input does not satisfy the workflow's declared input schema and value bounds.
operation.not-authorized403The caller's policy does not allow this operation.
receipt.expired409The original command's receipt can no longer be recovered. This is not evidence that the command was not sent.
reconcile.raced409The run's progress changed while it was being reconciled. Inspect it again.
reconcile.refused409Reconcile applies only to a held mutation with a prepared command, its original binding and no active lease.
reconcile.unsupported409The held operation has no original-result lookup.
request.body-too-large413The request body exceeds the accepted size: 65536 bytes for a JSON request, or 1048576 bytes for a signed event delivery.
request.invalid400The request could not be read as a Coordinator request.
request.limit-out-of-range400The list limit must be between 1 and 100.
request.method-not-allowed405The route exists but not for this method.
request.not-found404The requested route does not exist.
request.reason-invalid400The reason or recovery reference must be a bounded reference without personal data or control characters.
request.retention-invalid400The retention cutoff must be in the past and the limit between 1 and 100.
request.unprocessable422The request body could not be processed.
request.unsupported-media-type415The request body is not JSON.
restore.hold-required409This recovery action requires execution to be under restore hold.
restore.lease-incompatible409Held work no longer matches its protected definition or prepared command.
restore.review-required409Execution history must be complete and the prior deployment fenced before recovery is reviewed.
restore.unresolved409Held work remains unresolved, so the restore hold cannot be released.
retry.refused409Retry-same applies only to held work before its original deadline.
run.definition-incompatible409This service's Coordinator version cannot interpret the run's pinned definition.
run.not-visible404No run with this identifier is visible to the caller.
run.payload-erased409Retention erased this run's payloads, so it can no longer be retried or reconciled.
runtime.failure500Coordinator could not complete the request.
service.unavailable503A dependency of the Coordinator, such as its database, audit stream or token verifier, is unavailable. Try again after the service recovers.
MethodPathAuthenticationPurpose
GET/healthNone200 with {"live":true} while the process answers.
GET/readyNone200 with {"ready":true}, or 503 with {"ready":false} while the service cannot accept work.
GET/v1/workflows/{workflow}Bearer, start on the named workflowDiscover the active revision, input schema, and declared outcomes.
POST/v1/runsBearer, start on the named workflowStart one run with the expected workflowRevision and an Idempotency-Key.
GET/v1/runsBearer, statusList at most 100 of the most recently admitted runs the caller may see, newest first.
GET/v1/runs/{run}Bearer, statusRead one run's progress and its reviewed terminal output.
GET/v1/runs/{run}/inspectBearer, inspectRead bounded steps, accepted or unknown effects, durable wait budgets, and available recovery actions.
POST/v1/runs/{run}/cancelBearer, cancelStop future dispatch where possible, without rolling back an accepted effect.
POST/v1/runs/{run}/reconcileBearer, reconcileSettle an uncertain call from the receiving product's original-key receipt.
POST/v1/runs/{run}/retry-sameBearer, retry-sameSchedule held work again with its original command, key, binding, and authority.
GET/v1/doctorBearer, operatorReport activation, audit readiness, holds, and run counts.
POST/v1/restore-holdBearer, operatorPlace the restore hold, which stops dispatch and admissions.
POST/v1/release-restore-holdBearer, operatorRelease the restore hold once no held work is unresolved.
POST/v1/release-admission-holdBearer, operatorReopen admissions after a restore, with audited attestations.
POST/v1/complete-execution-recoveryBearer, operatorAttest complete execution history and prior fencing while the restore hold remains.
POST/v1/retentionBearer, operatorErase eligible terminal payloads that ended before a cutoff.
POST/v1/review-completionsA Casework connection's completion token and recipient bindingBring forward the next result read of a run waiting on that review request.
POST/v1/events/{workflow}/{trigger}Configured event source signatureAdmit one run per exact authenticated event and trigger; exact redelivery returns the same run.

GET /metrics is not part of this surface. When the deployment configures a metrics listener, the metrics are served on that separate socket only, and the service listener has no metrics route.

coordinatorctl openapi prints the OpenAPI document offline.