Zero to first gate call
in under 10 minutes.
One endpoint. One header. POST before each step — the gate returns ALLOW or DENY. Your agent only proceeds on ALLOW. Every ALLOW writes a cryptographic receipt — a signed, chained record of what ran, when, in what order, written before the action executed. The same gate answers the engineering question and the compliance question.
Three steps. No magic.
Every gate call follows the same path. Your agent sends a request. The gate enforces sequence. You get a receipt or a halt.
POST to /v1/evaluate with your agent's current step, a unique nonce, and a timestamp. Auth via the Authorization: Bearer header.
The gate validates step order, checks the nonce for replay, verifies function and action_type against the policy for this step, and confirms the sequence is not sealed. All checks must pass.
On pass: a cryptographic receipt with decision ALLOW — Ed25519-signed, chained, written to R2 as a tamper-evident record. That receipt is your compliance record: structural proof of what ran and in what order, timestamped by AgenticRail. On failure: a DENY with a reason code. Your agent only proceeds on ALLOW.
One header. Every request.
All requests to the wrapper require the Authorization: Bearer header. The demo key is public and rate-limited — use it to test without signing up.
Add to every request: Authorization: Bearer DEMO-AGENTICRAIL-PUBLIC-2026
The wrapper expects the Authorization header; the gate uses x-slp8-key.
For production use, contact hello@agenticrail.nz for a private key with no rate limits.
Wrapper, Gate, and Report
AgenticRail deploys three public services:
Endpoint: https://api.agenticrail.nz/v1/evaluate
Auth: Authorization: Bearer <key>
Adds API key management, rate limiting, D1 logging, and demo key bypass. All client requests should use this endpoint.
Pure sequence enforcement layer — validates step order, nonce, function/action_type policy, and sequence seal. Called internally by the wrapper via service binding. Not publicly accessible — all client traffic enters through the wrapper.
Endpoint: https://report.agenticrail.nz/report
Auth: x-slp8-key: <key>
Generates HTML/JSON compliance reports for any sequence. Read-only; no state mutation.
Demo key DEMO-AGENTICRAIL-PUBLIC-2026 works with all three services. Wrapper prefixes demo sequence IDs with demo- and isolates receipts.
Fields, one by one.
All requests are Content-Type: application/json via POST. Fields are validated in order — a missing required field halts at gate step 1.
| Field | Type | Description |
|---|---|---|
| schema_version | string | Always "1.0" |
| sequence_id | string | Unique per sequence — e.g. a UUID or session ID. Groups steps together. |
| step | string | Your step name — must match function. Must be a valid step in your configured sequence, called in the defined order. e.g. "verify_identity", "assess_risk", "execute_transfer". |
| function | string | Canonical function name — must equal step. e.g. "verify_identity", "assess_risk". Used for policy lookup. |
| action_type | string | Canonical action type for this step. Must be in the allowed set for the function. e.g. "CHECK_STATE", "RECORD_RESULT", "WAIT_FOR_SIGNAL". |
| model_id | string | Your agent identifier — e.g. "my-agent-v2". When using the demo key, the wrapper transforms this to "client:demo" before passing to the gate. |
| nonce | string | Unique string per request (any format). Used for replay protection — never reuse. |
| action | string | Descriptive action label for this step. e.g. "verify identity", "assess risk". |
| ts_ms | number | Unix timestamp in milliseconds. Required — use Date.now() or equivalent. Must be within ±300 seconds of current time when received by the gate. |
| inputs | object | Optional. Any context you want to log alongside the step. |
| attestation | object | Optional. Evidence object signed into the receipt at this step — e.g. {"aml_check": "passed", "approved_by": "risk-committee-id"}. The full object is signed into the receipt and stored in R2 alongside it, so any later alteration is detectable. Use it to embed proof of deliverables, external check results, or human approvals directly in the audit trail. Can contain hashes, IDs, and long strings — excluded from poison hardening. |
| step_order | string[] | Required on every call. Array of all step names in execution order — e.g. ["verify_identity", "assess_risk", "execute_transfer"]. The gate reads it from each payload to resolve the step's position. The gate does not store it between calls. |
Timestamp freshness: The gate enforces a ±300 second window around the current time. If your request arrives more than 5 minutes early or late, it will be rejected with reason STALE_TIMESTAMP. Always generate ts_ms fresh using Date.now() or equivalent.
Copy. Paste. Run.
This example works against the live wrapper right now. Each snippet generates a fresh timestamp, a unique nonce, and a unique sequence ID on every run, so it returns ALLOW the moment you paste it.
# Paste the whole block. Fresh timestamp, unique nonce + sequence id - runs every time. TS=$(( $(date +%s) * 1000 )) NONCE=$(openssl rand -hex 8) SEQ="my-seq-$(date +%s)" curl -X POST https://api.agenticrail.nz/v1/evaluate \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer DEMO-AGENTICRAIL-PUBLIC-2026' \ -d "{ \"schema_version\": \"1.0\", \"model_id\": \"my-agent\", \"sequence_id\": \"$SEQ\", \"step\": \"verify_identity\", \"function\": \"verify_identity\", \"action_type\": \"CHECK_STATE\", \"action\": \"verify identity\", \"nonce\": \"$NONCE\", \"ts_ms\": $TS, \"inputs\": {}, \"step_order\": [\"verify_identity\", \"assess_risk\", \"execute_transfer\", \"audit_ledger\"] }"
# Paste the whole block into PowerShell $ts = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() $nonce = -join ((1..16) | ForEach-Object { '0123456789abcdef'[(Get-Random -Maximum 16)] }) $seq = "my-seq-$ts" $body = '{"schema_version":"1.0","model_id":"my-agent","sequence_id":"' + $seq + '","step":"verify_identity","function":"verify_identity","action_type":"CHECK_STATE","action":"verify identity","nonce":"' + $nonce + '","ts_ms":' + $ts + ',"inputs":{},"step_order":["verify_identity","assess_risk","execute_transfer","audit_ledger"]}' $headers = @{ "Content-Type" = "application/json"; "Authorization" = "Bearer DEMO-AGENTICRAIL-PUBLIC-2026" } (Invoke-WebRequest -UseBasicParsing -Uri https://api.agenticrail.nz/v1/evaluate -Method POST -Headers $headers -Body $body).Content
ts_ms is a required field — the current Unix time in milliseconds, within 300 seconds of server time. The snippets generate it for you. Note: date +%s%3N is GNU-only; the cross-platform form $(( $(date +%s) * 1000 )) works on macOS and Linux.
// One gate call — adapt into your agent loop const res = await fetch('https://api.agenticrail.nz/v1/evaluate', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer DEMO-AGENTICRAIL-PUBLIC-2026', }, body: JSON.stringify({ schema_version: '1.0', model_id: 'my-agent', sequence_id: 'my-seq-' + Date.now(), step: 'verify_identity', function: 'verify_identity', action_type: 'CHECK_STATE', action: 'verify identity', nonce: crypto.randomUUID().replace(/-/g, '').slice(0, 16), ts_ms: Date.now(), inputs: {}, step_order: ['verify_identity', 'assess_risk', 'execute_transfer', 'audit_ledger'], }), }); const gate = await res.json(); if (gate.decision === 'ALLOW') { // Proceed with your step } else { // Halt — do not proceed console.error('DENY:', gate.reasons); }
No SDK is required. The gate is a plain HTTPS JSON call, exactly as above, so any language that can POST can use it. These packages wrap that call for convenience and ship the integrations below.
# Python — ships LangGraph and CrewAI integrations pip install agenticrail # JavaScript / TypeScript — dual ESM + CJS, works with LangGraph.js, Mastra, Genkit npm install @agenticrail/core
Both are MIT-licensed and open source. Evaluate against the live gate with the public demo key above — no account, no signup. step_order is sent on every call; the gate reads it from each payload and does not store it.
ALLOW or DENY. Nothing in between.
The wrapper returns a flattened response with all decision details at the top level. There is no pack wrapper — the decision, reasons, and executed fields are directly in the response object. The nested receipt object carries the receipt metadata for that decision — key_id, signature_alg, payload_hash, and version.
{ "decision": "ALLOW", "executed": true, "pack_id": "80fe6e6887ca024d2325260f83224c86c3d2157b10ef60cc73ee8fded4661552", "reasons": [], "sequence_id": "demo-test-seq-001", "step": "verify_identity", "function": "verify_identity", "action_type": "CHECK_STATE", "model_id": "client:demo", "result": { "status": "submitted", "data": { "state": null } }, "receipt": { "pack_id": "80fe6e6887ca024d2325260f83224c86c3d2157b10ef60cc73ee8fded4661552", "key_id": "k2_2026-06-07_ed25519", "signature": null, "signature_alg": "Ed25519", "payload_hash": "201031e1bce583b179f7b9b8b9c794de2cd8514da84adae974232ed3f2ff0774", "prev_receipt_id": null, "ts_ms": 1780732058107, "version": "slp8_receipt_v2", "attestation": null }, "log": { "ok": true, "error": null } }
{ "decision": "DENY", "executed": false, "pack_id": "1493ba42f5e0ffb81227e700eda1e03e762058fd9da4080f320c432b6fa23c2f", "reasons": ["SEQUENCE_VIOLATION"], "sequence_id": "demo-replay-test-seq", "step": "execute_transfer", "function": "execute_transfer", "action_type": "CHECK_STATE", "model_id": "client:demo", "result": { "status": "skipped", "message": "No execution triggered" }, "receipt": { "pack_id": "1493ba42f5e0ffb81227e700eda1e03e762058fd9da4080f320c432b6fa23c2f", "key_id": "k2_2026-06-07_ed25519", "signature": null, "signature_alg": "Ed25519", "payload_hash": "9f2c1a77b4e83d0516a8c4f9e2b7d3061c5a8e94f0b2d6713a9e4c8051f7b2d4", "prev_receipt_id": "80fe6e6887ca024d2325260f83224c86c3d2157b10ef60cc73ee8fded4661552", "ts_ms": 1780732061488, "version": "slp8_receipt_v2", "attestation": null }, "log": { "ok": true, "error": null } }
About the signature. The inline receipt carries the decision metadata and the payload_hash. The signature itself is finalized in the durable receipt written to storage — it is not echoed in the synchronous response, so the calling system cannot verify it at the moment of decision, only afterward. That's deliberate: the synchronous path stays lean, and every verification is forced through the one durable, tamper-evident record rather than trusting an ephemeral API response. To verify it, call report.agenticrail.nz — the compliance report includes each receipt's raw signature (base64) and its exact signed_canonical preimage, so you can run ed25519_verify(public_key, signed_canonical, signature) yourself, entirely offline, against the published key at /spec/receipt-public-keys.json — no callback to AgenticRail, no trust in our own verification claim required.
execute_transfer before verify_identity has completed.sequence_id.action_type is not in the allowed set for this function/step.step/function is not present in this sequence's own declared step_order. An unrecognised name alone does not deny — it falls through to a permissive generic policy so custom step_order sequences work. Only a name absent from your declared step_order is rejected. (Corrected 2026-07-05 — supersedes the previously-documented but unreachable NO_POLICY_MATCH.)function and step fields do not match.attestation.witnessed_pack_id did not match the real prior receipt — missing, wrong, or unverifiable against the durable record.step_order sent differs from the one this sequence was opened with. The declared order is locked on the first call, so a later call cannot shorten it to skip a required step. If the process genuinely changed, start a new sequence_id.missing_function, missing_action_type, missing_nonce.Every code above arrives as an entry in the reasons array of a DENY, and every DENY is written to a signed receipt. A request can also be refused before it reaches enforcement, in which case you get a HALT instead. Those are a different class, and they are listed next.
Refused at the door. No receipt.
HALT is not an enforcement decision. ALLOW and DENY are decisions: the gate evaluated your step against the sequence and reached a verdict, and either way a signed receipt is written before your action runs. HALT means the request was rejected at the boundary — malformed, oversized, unauthenticated, or matching a prompt-injection pattern — and never reached the enforcement engine at all.
A HALT produces no receipt. Nothing was decided, so there is nothing to sign or store. If you are reconciling receipts against calls, HALTed calls will have no corresponding receipt, and that is correct behaviour, not a gap.
A HALT is returned with a non-2xx HTTP status and carries status rather than decision:
{
"status": "HALT",
"halt_gate_step": 1,
"reason_code": "SCHEMA_VIOLATION",
"reason_detail": "REJECT_ROLE_DIRECTIVES"
}
reason_code is the bucket; reason_detail, when present, is the specific trigger.
reason_detail is MISSING_KEY or BAD_KEY. On the public API this means the Authorization: Bearer header.reason_detail carries the trigger: BAD_CONTENT_TYPE (415), BODY_TOO_LARGE (413), BAD_JSON (400), BODY_READ_ERROR (400), or one of the injection patterns below (403).attestation, which legitimately holds hashes and IDs.\n that appears in JSON-stringified bodies.attestation object exceeds its size cap.attestation object nests deeper than the allowed depth.Three rejections happen earlier still, at the public wrapper, and return a plain error field rather than a HALT envelope: invalid_api_key (401) when a real key prefix is presented with the wrong secret, invalid_json (400) when the body will not parse, and rate_limited (429) when you exceed your rate limit. Treat all of these the same way you treat a HALT: the call did not reach enforcement, and there is no receipt.
Sending no key is not one of them. An unrecognised credential — no header at all, a placeholder, or a key that was never issued — does not fail. The call runs on the public demo lane and the response says so, carrying lane, lane_reason and lane_notice. Only a recognised key presented wrongly is refused: a wrong secret returns 401, a revoked key returns 403. The trade is that a demo-lane sequence is prefixed demo- and its report can be read by anyone holding the sequence id, so nothing private belongs in attestation.
The rules the gate enforces.
These aren't soft guidelines. A violation on any one of them returns DENY immediately.
verify_identity → assess_risk → request_approval → execute_transfer. An out-of-order step returns SEQUENCE_VIOLATION.REPLAY_NONCE.function must match step. action_type must be in the allowed set for that function. Invalid combinations return ACTION_NOT_ALLOWED or FUNCTION_STEP_MISMATCH.settle is accepted, the sequence locks. Any further request on that sequence_id returns SEALED_SEQUENCE. Start a new sequence with a fresh ID.METHOD_NOT_ALLOWED immediately.Embed evidence in the receipt.
Each step can carry an attestation object — arbitrary evidence that travels with the request and gets signed into the R2 receipt. Use it to prove what happened at each step: an AML check passed, a human approved the action, an external system returned a specific result.
The attestation is stored alongside the receipt and appears in every compliance report generated for that sequence. It's not a separate log entry — it's part of the cryptographic chain.
from agenticrail import RailClient import time client = RailClient(api_key="DEMO-AGENTICRAIL-PUBLIC-2026") seq = client.sequence("payment-run-001", [ "verify_identity", "assess_risk", "request_approval", "execute_transfer", "audit_ledger" ]) # Attach evidence at each step — signed into the receipt seq.next("verify_identity", attestation={ "kyc_provider": "acme-kyc", "result": "pass", "checked_at": int(time.time() * 1000), }) seq.next("assess_risk", attestation={ "risk_score": 23, "threshold": 50, "decision": "below_threshold", }) seq.next("request_approval", attestation={ "approved_by": "risk-committee-id-7f3a", "approval_ref": "APR-2026-00412", }) seq.next("execute_transfer") # attestation optional — omit if nothing to prove seq.next("audit_ledger") # seals sequence — all attestations locked in chain
import { RailClient } from "@agenticrail/core"; const client = new RailClient({ apiKey: "DEMO-AGENTICRAIL-PUBLIC-2026" }); const seq = client.sequence("payment-run-001", [ "verify_identity", "assess_risk", "request_approval", "execute_transfer", "audit_ledger" ]); // Attach evidence at each step — signed into the receipt await seq.next("verify_identity", { attestation: { kyc_provider: "acme-kyc", result: "pass", checked_at: Date.now() } }); await seq.next("assess_risk", { attestation: { risk_score: 23, threshold: 50, decision: "below_threshold" } }); await seq.next("request_approval", { attestation: { approved_by: "risk-committee-id-7f3a", approval_ref: "APR-2026-00412" } }); await seq.next("execute_transfer"); // attestation optional await seq.next("audit_ledger"); // seals sequence — all attestations locked in chain
The attestation field accepts any plain JSON object. Values can include strings, numbers, and nested objects. Large binary blobs are not supported — store those in your own system and include a reference ID or hash here instead.
One command. Full provenance report.
The report worker reads every receipt for a sequence, verifies the cryptographic chain, and produces a human-readable compliance report. This is the deliverable your lawyer, auditor, or regulator asks for — proof of what ran, verified independently of what the agent claims.
Not a log export. Chain-verified receipt evidence, generated on demand for any sequence.
Endpoint: POST https://report.agenticrail.nz/report
Headers: Content-Type: application/json, x-slp8-key: <key>
Body: { "sequence_id": "your‑sequence", "format": "html"|"json" }
Demo key restricts to sequences prefixed demo‑. Production key accesses any sequence.
Worker scans R2 for all receipts matching the sequence ID, verifies each pack ID hash (multi‑generation logic), validates the receipt chain, and composes a deterministic enforcement_summary from the resulting counts. No language model is involved anywhere in report generation — the same inputs always produce byte-identical text, so the summary reproduces like the rest of the document.
HTML: Full‑page report with cover, sequence summary, enforcement log, chain proof, and the deterministic enforcement summary.
JSON: Structured data containing all verified receipts and verification results.
Read‑only; no state mutation.
# Replace SEQUENCE_ID with your sequence (demo‑ prefix for demo key)
curl -X POST https://report.agenticrail.nz/report \
-H 'Content-Type: application/json' \
-H 'x-slp8-key: DEMO-AGENTICRAIL-PUBLIC-2026' \
-d '{
"sequence_id": "demo‑my‑seq‑001",
"format": "html"
}'
Reports are deterministic — the same sequence always yields identical output. Verification uses multi‑generation hash checking to handle Gen‑1 and Gen‑2 receipts.
What the receipt chain proves — and to whom.
Every gate call produces enforcement output (ALLOW/DENY). It also produces a compliance record — a signed, chained, tamper-evident receipt that answers the question regulators and lawyers actually ask: "Can you prove what the AI did?"
These are the same facts. Different audience, different frame.
"Did the agent proceed correctly? Was the step blocked? Why?"
Answered by the gate decision: ALLOW, DENY, reason codes.
"Can you prove what the AI did last Tuesday — not what it reported, what it actually executed?"
Answered by the receipt chain: signed, chained, tamper-evident.
Receipt field reference — what each field proves
| Receipt field | What it proves |
|---|---|
| pack_id | Unique identifier for this enforcement decision — the canonical reference for this chain link. |
| signature (Ed25519) | Tamper evidence. An Ed25519 signature (base64) over the canonical receipt, verifiable offline against the published public key (/spec/receipt-public-keys.json). If the receipt was altered after writing, verification fails. Legacy receipts before 2026-06-07 use HMAC-SHA256. |
| prev_receipt_id | pack_id of the previous receipt — an identifier reference establishing chain order. On its own, proves something with that identifier came before; see prev_receipt_hash below for content-tamper protection. |
| prev_receipt_hash | SHA-256 of the previous receipt's full canonical JSON, signature included (added 2026-07-08). An in-place edit to any earlier receipt breaks this link even if prev_receipt_id references still match — this is what actually invalidates the report on tampering. Null for the chain's first receipt and for any link whose anchor predates this field. |
| ts_ms | Timestamp to the millisecond — when the gate decision was made. Written at enforcement time, not retrieved or reconstructed later. |
| payload_hash | SHA-256 of the raw request body — the fingerprint of your payload, not the payload itself. Your agent's input data never enters receipt storage. An auditor can verify the receipt is cryptographically bound to a specific payload without ever seeing that payload's contents. |
| attestation | Per-step evidence signed into the receipt — AML check results, human approval tokens, risk scores, KYC references. Proves what justified this specific decision. |
Logs are self-reported — the AI agent tells you what it did. Receipts are structural — the gate wrote them before the agent acted, independent of the agent's own reporting. An auditor cannot distinguish a genuine log from a reconstructed one. They can verify a receipt chain cryptographically. That difference is what makes AgenticRail evidence rather than documentation.
Ready to go further?
The demo key is open for testing, and you can drive the gate in the browser first — skip a step, replay one, and watch it get refused before you write any code. When you're ready for production — dedicated rate limits, private key, support, and integration help — get in touch.
Wiring this up from code, or pointing an agent at it? The machine-readable API description is published as OpenAPI 3.1 at agenticrail.nz/openapi.json — every endpoint, the full payload contract, all eight denial codes and the response schemas. It is linked as service-desc from /.well-known/api-catalog, so a client that follows RFC 9727 discovery will find it without being told. Framework integrations: the Python SDK ships LangGraph and CrewAI adapters, the JavaScript SDK covers LangGraph.js, Mastra and Genkit, and MCP clients can call the gate as a tool at mcp.agenticrail.nz.
Working out whether this fits at all, rather than how to call it? The product overview sets out what AgenticRail is in one page: what it enforces, what a receipt contains, what it is used for — EU AI Act Article 12 record-keeping, provable human oversight and sign-off, segregation of duties — how it sits alongside observability, guardrails and orchestration, and what it does not do.
Blunt questions — whether you can self-host, who holds the signing keys, what certifications we do and don't have, and what a receipt does not prove — are answered on the FAQ.
Looking for the formal side instead? The enforcement specification (versioned, fingerprinted, frozen) and the published briefs — evidence completeness, provable safeguards for automated decisions, sector gap analyses for NZ health and NZ education — all live under /spec/.