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 routeSDK surfaceContract
GET /healthinternal identity handshakepublic liveness and protocol versions
GET /v1/openapi.jsongeneration onlyauthenticated OpenAPI 3.1 document
GET /v1/workflowslistWorkflows()contained relative workflow names
GET /v1/workflows/{name}workflow(name)path-free metadata, never source bytes
POST /v1/checkcheck()validates a served name or immutable snapshot bytes without a job
POST /v1/jobsrun()admits a served name or exact snapshot bytes with an idempotency key
GET /v1/jobs/{id}internal settlementdurable job identity, outputs, receipt, settlement, or redacted error
GET /v1/jobs/{id}/statusstatus(run)current status only
GET /v1/jobs/{id}/eventsevents(run) / attachRun()bounded, sequenced SSE with replay
POST /v1/jobs/{id}/cancelcancel(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/verifytraceVerify(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 explicit allowInsecureHttp: 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() refuses model and nativeStrict; remote run() refuses vars, model, and maxCostUsd until 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.