HTTP contract
September 8, 2026 · View on GitHub
openapi.json is the checked-in contract pin. The SDK authenticates every
route except public GET /health; bearer tokens are redacted from failures.
A non-2xx answer typed as { error: { code, message } } becomes a
NikaOperationError carrying status, code, and the refused operation;
For a check by served name, a typed 404 or 422 instead returns
{ clean: false, error }; authentication and transport failures still throw.
Any other non-2xx body is discarded and reported as a redacted
NikaTransportError.
| HTTP route | SDK surface | Contract |
|---|---|---|
GET /health | internal identity handshake | public liveness and protocol versions |
GET /v1/openapi.json | generation only | authenticated OpenAPI 3.1 document |
GET /v1/workflows | listWorkflows() | contained relative workflow names |
GET /v1/workflows/{name} | workflow(name) | path-free metadata, never source bytes |
POST /v1/check | check() | validates a served name or immutable snapshot bytes without a job |
POST /v1/jobs | run() | admits a served name or exact snapshot bytes with an idempotency key |
GET /v1/jobs/{id} | internal settlement | durable job identity, outputs, receipt, settlement, or redacted error |
GET /v1/jobs/{id}/status | status(run) | current status only |
GET /v1/jobs/{id}/events | events(run) / attachRun() | bounded, sequenced SSE with replay |
POST /v1/jobs/{id}/cancel | cancel(run) | 200 a settled job or its terminal replay; 202 the request accepted on a running job, settled later by observation |
GET /v1/jobs/{id}/trace/verify | traceVerify(receipt) | engine-owned typed trace verdict; reason only on a verdict that does not hold |
GET/PUT /v1/schedules/{id} | scheduleStatus() / schedule() | resident schedule projection and CAS mutation |
Connection rules
- HTTPS is required for every host except loopback. Plain HTTP is accepted
only for
localhost,127.0.0.0/8, or[::1], and only with an explicitallowInsecureHttp: true; that opt-in never admits a routable host. - URLs containing credentials, a query, or a fragment are rejected.
- Tokens must contain 32–512 visible ASCII bytes and are never sent to
/health. - Each request has a bounded timeout and each JSON/SSE machine frame has a byte ceiling.
- Remote
check()refusesmodelandnativeStrict; remoterun()refusesvars,model, andmaxCostUsduntil the request envelope owns them. - Caller-provided workflow catalog names must be contained slash-separated
paths. Absolute paths, backslashes, empty segments,
.and..are rejected before network I/O.
A contained .nika.yaml name uses the resident registry without a local
engine. Prefix a local file with ./ to capture and submit its snapshot.
A successful by-name check returns clean: true and the compact resident
acknowledgement; no local check report or exit code is fabricated.
Settlement
The terminal execution.settled frame and the durable job nest the run's
settlement whole (engine 0.118, ADR-128): its status and cause, the
elapsed time, the task tally, the spend with its qualifier, and the failure
named with its task. The SDK types every known field, refuses a settlement
whose status contradicts the record carrying it, keeps fields it does not
know, and never derives a settlement from an exit code; a job the resident
lost (interrupted) carries none.
SSE recovery
The client checks that SSE ids are canonical positive integers and equal
data.sequence. An identical duplicate is ignored. A conflicting duplicate,
gap, or out-of-order frame is a protocol failure. After a reset the client
asks durable job state before reconnecting with Last-Event-ID; retry delays
and attempts are bounded.
A replacement Node process can call attachRun(jobId, { lastEventId }). The
SDK proves that the durable job exists before returning an owned run handle,
then sends the cursor as Last-Event-ID. Persist the job id and last event
sequence in the same application transaction that records each consumed event.
A cursor means “fully processed”, not merely “received”.
Idempotency and schedules
An omitted run idempotency key is generated once per admission. A caller key must be 1–255 bytes. Reusing a key with different snapshot bytes is an engine conflict, not a retry success.
The namespace is the whole durable job store under the server's configured
state-root, across workflows, clients, schedules, and server restarts. The
current engine has no time-based eviction: keys remain bound while that state
root exists and still count toward its configured job capacity. Use globally
unique, business-stable keys; do not recycle daily counters or workflow-local
names.
Schedules use compare-and-swap semantics. Create omits revision; update must
carry the exact previous sha256:... revision. The SDK never fabricates or
normalizes schedule facts.