Developer Documentation

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 decision, ALLOW or DENY, writes a cryptographic receipt — a signed, numbered record of what was authorised or refused, in what order, issued before the gate answers. The same gate answers the engineering question and the compliance question.

Wrapper live — https://api.agenticrail.nz/v1/evaluate

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.

01
Send request

POST to /v1/evaluate with your agent's current step, a unique nonce, and a timestamp. Auth via the Authorization: Bearer header.

02
Gate enforces sequence

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.

03
Receive receipt or halt

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 was permitted and in what order, with the timestamp the caller supplied, bounded by the gate's clock. On failure: a DENY with a reason code. Your agent only proceeds on ALLOW.

One header. Every request.

Send your key in the Authorization: Bearer header on every request. The demo key is public and rate-limited — use it to test without signing up. A request with no key, or one the gate does not recognise, runs on the public demo lane and the response says so.

Demo key — public, free to use
DEMO-AGENTICRAIL-PUBLIC-2026

Add to every request: Authorization: Bearer DEMO-AGENTICRAIL-PUBLIC-2026
A private key is US$39 a month, bought without speaking to anyone: buy a developer key. For a deployment, write to hello@agenticrail.nz.

Wrapper, Gate, and Report

AgenticRail runs three services. Two are public; the gate is reached only through the wrapper:

01
Wrapper (front door)

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.

02
Gate (enforcement)

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.

03
Report (compliance)

Your own sequences: POST https://api.agenticrail.nz/v1/report
Auth: Authorization: Bearer <key>
Public demo sequences: https://report.agenticrail.nz/report — no key required.
Generates HTML/JSON compliance reports. Read-only; no state mutation.

Demo key DEMO-AGENTICRAIL-PUBLIC-2026 works with the wrapper and both report routes. Wrapper prefixes demo sequence IDs with demo- and isolates receipts.

Fields, one by one.

All requests are Content-Type: application/json via POST. Only three fields are required: sequence_id, step and action_type. Everything else is optional, and the defaults are listed below. A missing or malformed field is refused with HTTP 400 invalid_request, naming the field, before anything is evaluated, so it produces no receipt.

Field Type Description
sequence_id string Required. Groups the steps of one run together, e.g. a UUID. Use a fresh id for every run: sealing is permanent, and on the shared demo lane a fixed id is shared with everyone. On the demo key it comes back prefixed demo-; use the id returned for later steps. A keyed caller may not send an id starting demo- (400).
step string Required. Your step name, e.g. "verify_identity". Must appear in step_order and is compared byte for byte, so spell it identically on every call.
action_type string Required. One of eight enforcement classes: CHECK_STATE, CLARIFY_NEXT_STEP, SELECT_NEXT_STEP, RECORD_RESULT, WAIT_FOR_SIGNAL, VALIDATE_INPUT, PAUSE_CYCLE, REDUCE_STIMULUS. Built-in steps accept only a subset; a step in your own step_order accepts all eight.
step_order string[] Optional, but send it on every call for your own process. All step names in order, e.g. ["verify_identity", "assess_risk", "execute_transfer"]. One step is a valid sequence. If absent, the built-in eight-step MSMD spine applies. The order is locked on the sequence's first permitted call (a refused call locks nothing): a different list later is refused STEP_ORDER_MISMATCH and the refusal returns the locked list. Omitting it later does not clear the lock; it selects the spine and your steps come back UNKNOWN_STEP. Not an array, or empty: 400.
function string Optional, defaults to step. If sent, must equal step, or the call is refused FUNCTION_STEP_MISMATCH.
action string Optional, defaults to "run <step>". A human-readable label, e.g. "verify identity". Put descriptive sentences here, never in step.
nonce string Optional, generated for you if absent. A value used once per request, for replay protection. If you send one, never reuse it: a repeat is refused REPLAY_NONCE.
ts_ms number Optional. Your clock, Unix milliseconds, e.g. Date.now(). If sent, it must be within 300 seconds of the gate's clock or the step is refused STALE_TIMESTAMP. If absent, the gate's own clock is used and the freshness check is skipped. Not a number: 400.
model_id string Optional. Your agent's identifier, e.g. "my-agent-v2", up to 64 characters, no ::. It is kept, prefixed by your account: client:<your client>:my-agent-v2.
inputs object Optional. Context for the step. It is never stored in the receipt: the receipt carries only a SHA-256 hash of the whole request. Use attestation for anything that must be kept as evidence.
attestation object Optional. Evidence signed into the receipt at this step, e.g. {"aml_check": "passed", "approved_by": "risk-committee-id"}. It is stored and published verbatim with the receipt, so any later alteration is detectable. On the public demo key the report needs no key, so anything placed here on a demo sequence is world-readable. Can carry hashes, ids and long strings.

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.

curl — bash / mac / linux
# 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\"]
  }"
powershell — windows
# 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 optional — the current Unix time in milliseconds; if sent it must be within 300 seconds of server time, and if absent the gate's own clock is used. 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.

The order in this example is not arbitrary. Under UK anti-money-laundering law, the customer's identity must be verified before the transaction is carried out, so the order is the obligation; the customer due diligence specification sets out where the law says so.

javascript
// 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.

SDKs — Python and JavaScript
# Python — ships LangGraph and CrewAI integrations
pip install agenticrail

# JavaScript / TypeScript — dual ESM + CJS, plain fetch, any framework
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, identical each time; the gate locks it on the sequence's first permitted call.

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 execution_submitted fields are directly in the response object. execution_submitted reports whether the wrapper's executor accepted the action, with the detail in result. It is not the signed receipt's executed field, which records only that the step was permitted; the response carries no executed key. The nested receipt object carries the receipt metadata for that decision — key_id, signature_alg, payload_hash, decision_index, and version.

✓ ALLOW — step accepted
{
  "decision": "ALLOW",
  "execution_submitted": true,
  "pack_id": "0d2f96b09dafaec463ffa606e65163c561792088756125896210a8874e3eddc5",
  "reasons": [],
  "sequence_id": "demo-whakamatau-docs-aa3e2f4f",
  "step": "verify_identity",
  "function": "verify_identity",
  "action_type": "CHECK_STATE",
  "model_id": "client:demo:my-agent",
  "result": {
    "status": "submitted",
    "data": {
      "state": null
    }
  },
  "receipt": {
    "pack_id": "0d2f96b09dafaec463ffa606e65163c561792088756125896210a8874e3eddc5",
    "key_id": "k2_2026-06-07_ed25519",
    "signature": null,
    "signature_alg": "Ed25519",
    "payload_hash": "b067f50657de8c2cad4b98e0d4ac851c7be70ab53895e7bd12127d68ef53157f",
    "prev_receipt_id": null,
    "prev_receipt_hash": null,
    "decision_index": 1,
    "ts_ms": 1791444261196,
    "version": "slp8_receipt_v2",
    "attestation": null
  },
  "log": {
    "ok": true,
    "error": null
  }
}
✗ DENY — sequence violation
{
  "decision": "DENY",
  "execution_submitted": false,
  "pack_id": "42f5b4d20894835ea4883a48fca11650fa015a7fbe720011f694efa04dfad721",
  "reasons": ["SEQUENCE_VIOLATION"],
  "next_expected_step": "assess_risk",
  "sequence_id": "demo-whakamatau-docs-aa3e2f4f",
  "step": "execute_transfer",
  "function": "execute_transfer",
  "action_type": "CHECK_STATE",
  "model_id": "client:demo:my-agent",
  "result": {
    "status": "skipped",
    "message": "No execution triggered"
  },
  "receipt": {
    "pack_id": "42f5b4d20894835ea4883a48fca11650fa015a7fbe720011f694efa04dfad721",
    "key_id": "k2_2026-06-07_ed25519",
    "signature": null,
    "signature_alg": "Ed25519",
    "payload_hash": "a50a3e8bec77377a35b09bd5829573b1698acf1959731b9fff2d654e80d2f878",
    "prev_receipt_id": "0d2f96b09dafaec463ffa606e65163c561792088756125896210a8874e3eddc5",
    "prev_receipt_hash": "cf530a36b8ddec8fa72124d9cfb139d3034452f0872b9e59fd2b2735f95a8f1f",
    "decision_index": 2,
    "ts_ms": 1791444262561,
    "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, use report.agenticrail.nz/report — 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.

Reason code
Meaning
SEQUENCE_VIOLATION
Step arrived out of order — e.g. execute_transfer before verify_identity has completed.
REPLAY_NONCE
Nonce has already been used. Generate a fresh nonce per request.
STALE_TIMESTAMP
Timestamp (ts_ms) is outside the allowed ±300 second window from current time. Always generate ts_ms fresh using Date.now() or equivalent.
SEALED_SEQUENCE
This sequence has already been completed. Start a new sequence_id.
ACTION_NOT_ALLOWED
action_type is not in the allowed set for this function/step.
UNKNOWN_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.
FUNCTION_STEP_MISMATCH
function and step fields do not match.
ARTIFACT_UNBOUND
A witness step's attestation.witnessed_pack_id did not match the real prior receipt — missing, wrong, or unverifiable against the durable record.
STEP_ORDER_MISMATCH
The step_order sent differs from the one this sequence was opened with. The declared order is locked on the first permitted call, so a later call cannot shorten it to skip a required step. If the process genuinely changed, start a new sequence_id.
HTTP 400
Not a DENY. A missing or malformed field (no sequence_id, step or action_type; a step_order that is not a non-empty list; a non-numeric ts_ms) is refused with invalid_request and a message naming the field, before evaluation, so no receipt is written.

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.

The HTTP status of a DENY. By default both ALLOW and DENY return HTTP 200, so read the decision field, which is what the SDKs and the MCP server do. A caller that reads only the status code — a shell script using curl --fail, a scheduler, a CI step, a no-code webhook step — would treat a DENY as success and run the next step. For those, add ?deny_status=409 to the URL: POST https://api.agenticrail.nz/v1/evaluate?deny_status=409. Every non-ALLOW decision then returns HTTP 409, so the flow stops on its own. The body, the receipt and the signature are the same either way. Any other value for deny_status is refused with a 400 rather than ignored. In n8n, put the flag in the HTTP Request node's URL; the node then fails on a refusal and the workflow stops, with the gate's full answer, next_expected_step included, in the error.

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 decision is issued before your action runs. HALT means the request was rejected at the boundary — oversized, 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:

HALT response
{
  "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_code
Meaning
UNAUTHORIZED
401. Gate-level: the wrapper always presents its own credential, so a public caller does not receive this. A missing or unrecognised key on the public API runs on the demo lane instead (below).
SCHEMA_VIOLATION
The body failed hardening before parsing. reason_detail carries the trigger: BODY_TOO_LARGE (413), BAD_JSON (400), BODY_READ_ERROR (400), or one of the injection patterns below (403). The wrapper re-sends your fields to the gate as JSON, so your own Content-Type never reaches this check, and a body that will not parse is refused earlier as invalid_json.
  ↳ REJECT_ROLE_DIRECTIVES
The body contains role-directive text of the kind used in prompt-injection attempts.
  ↳ REJECT_BASE64_BLOBS
A base64-shaped string over 40 characters. Payloads are metadata, not carriers. Not applied to attestation, which legitimately holds hashes and IDs.
  ↳ REJECT_YAML_FRONT_MATTER
YAML front-matter markers, matched both as real newlines and as the escaped \n that appears in JSON-stringified bodies.
  ↳ ATTESTATION_TOO_LARGE
The attestation object exceeds its size cap.
  ↳ ATTESTATION_TOO_DEEP
The attestation object nests deeper than the allowed depth.
METHOD_NOT_ALLOWED
405. Gate-level. On the public API a method other than POST gets the wrapper's 404 not_found instead.
NOT_FOUND
404. Gate-level. On the public API an unknown path gets the wrapper's 404 not_found instead.
DEMO_LIMIT
413. The demo key has a tighter body-size cap than a production key.

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.

Steps must run in the configured order. No skipping. Each step must follow the one before it — for example, verify_identity → assess_risk → request_approval → execute_transfer. An out-of-order step returns SEQUENCE_VIOLATION.
Nonces are single-use. Each request in a sequence must carry a nonce not used before in that sequence (one is generated if you send none). Reusing one returns REPLAY_NONCE.
function and action_type must be valid. 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.
Sequences seal after completion. Once the last step of the declared order is accepted (settle on the built-in spine), the sequence locks. Any further request on that sequence_id returns SEALED_SEQUENCE. Start a new sequence with a fresh ID.
Only POST is accepted. Any other method on /v1/evaluate returns 404 not_found and reaches no enforcement.

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 record, at each step, what your system asserts: an AML check passed, a human approved the action, an external system returned a specific result. The gate signs what you send; it does not check that it is true.

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 signed receipt.

Python — attestation per step
from agenticrail import RailClient
import time, uuid

client = RailClient(api_key="DEMO-AGENTICRAIL-PUBLIC-2026")
seq = client.sequence(f"payment-run-{uuid.uuid4().hex[:12]}", [
    "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
JavaScript — attestation per step
import { RailClient } from "@agenticrail/core";

const client = new RailClient({ apiKey: "DEMO-AGENTICRAIL-PUBLIC-2026" });
const seq = client.sequence(`payment-run-${crypto.randomUUID()}`, [
  "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 was permitted, and in what order, verified independently of what the agent claims.

Not a log export. Chain-verified receipt evidence, generated on demand for any sequence.

01
Request a report

Endpoint: POST https://api.agenticrail.nz/v1/report
Headers: Content-Type: application/json, Authorization: Bearer <key>
Body: { "sequence_id": "your-sequence", "format": "html"|"json" }
Your key reaches your own sequences here. The demo key is restricted to sequences prefixed demo-, and those also verify with no key at all at report.agenticrail.nz/report — that host serves public verification only and refuses a customer key.

02
Report generation

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.

03
Output formats

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.

curl — generate HTML report
# Runs as-is. Swap in your own sequence_id and your own key.
curl -X POST https://api.agenticrail.nz/v1/report \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer DEMO-AGENTICRAIL-PUBLIC-2026' \
  -d '{
    "sequence_id": "demo-mcp-payment-workflow-001",
    "format": "html"
  }'

The enforcement summary is deterministic, and apart from the time the report was generated, the same receipts always yield the same report. 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, numbered, tamper-evident receipt of what the gate permitted and refused, and in what order.

These are the same facts. Different audience, different frame.

Engineering question

"Did the agent proceed correctly? Was the step blocked? Why?"

Answered by the gate decision: ALLOW, DENY, reason codes.

Compliance question

"Which steps was the agent permitted last Tuesday, in what order, and which was it refused — from a record the agent did not write?"

Answered by the receipts: signed, numbered, tamper-evident. They record the decisions, not that each action then ran.

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 last permitted receipt's full canonical JSON, signature included (added 2026-07-08); the chain runs through ALLOWED receipts only, and decision_index numbers every decision so a removed refusal shows as a gap. An in-place edit to any earlier permitted 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 The time the caller asserted for this step, in milliseconds. Signed, so it cannot be changed afterwards, but self-asserted: the gate refuses it only if it is more than 300 seconds from its own clock. If absent, the gate's clock is used.
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. Records what your system asserted at this step; the gate signs it but does not check it.
Key distinction

Logs are self-reported — the AI agent tells you what it did. Receipts are issued by the gate, signed before it answers, outside the agent's own reporting. A log has to be taken on trust; a receipt can be verified cryptographically against a published key. That difference is what makes AgenticRail evidence rather than documentation.

Calling the gate from an MCP client

AgenticRail runs a public Model Context Protocol server, so an agent can call the gate as a tool without installing an SDK. It is a thin adapter over the same public API described above: it holds no extra privileges, stores nothing of its own, and enforces nothing the gate does not already enforce.

Endpoint https://mcp.agenticrail.nz/ — JSON-RPC over POST /. A GET / returns the discovery card, which is also served at /.well-known/mcp.json; robots.txt and sitemap.xml are served too. Paths for protocols we do not implement — the oauth-* discovery endpoints, agent.json, ai-plugin.json — return 404, and that is the correct answer rather than a fault: a 404 on the OAuth paths is how an MCP server says no auth is required.
Transport Streamable HTTP, stateless. No session to establish and nothing to keep open.
Protocol version 2026-07-28
Registry name nz.agenticrail/gate, version 1.3.0

Two tools are published:

  • evaluate_step — ask the gate to ALLOW or DENY a single step before it runs. Calls /v1/evaluate. A DENY comes back with the remedy attached: the action types that step does permit, the step the sequence expected next, or the locked step order.
  • verify_receipt — prove a sequence's receipt chain is intact. Calls the report worker, which resolves each receipt's predecessor by anchor and recomputes the verdict fresh.
MCP client configuration
# Add your own key once you have one. Without it you land on the public demo lane.
{
  "mcpServers": {
    "agenticrail": {
      "type": "http",
      "url": "https://mcp.agenticrail.nz/",
      "headers": { "Authorization": "Bearer YOUR-KEY" }
    }
  }
}

Your key travels in Authorization: Bearer and is passed straight through. Send no key and the call still runs — on the public demo lane, and the response says so. Two consequences follow, and they are the reason the notice exists: a demo sequence's report needs no key, so anything placed in attestation is world-readable; and demo receipts are deleted 30 days after they are written. Neither applies once you send your own key.

One thing worth knowing before the first call: each step accepts only a subset of the eight action types, and a sequence that declares its own step_order is enforced against that order verbatim. A first call refused with ACTION_NOT_ALLOWED is usually an invented action_type, not a broken endpoint — the denial carries the list that would have worked. A refused call does not advance the sequence and locks nothing, so the run is not spoiled: the call was wrong, not the sequence. Correct the field the denial names and send the step again.

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.

Get a developer key

US$39 a month, bought without speaking to anyone. Your sequences move off the public demo lane, so they stop being world-readable, and your receipts persist instead of being deleted after 30 days. 120 requests a minute. The key is emailed when the payment clears. For a deployment, write to hello@agenticrail.nz.

Buy a developer key →

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 nine 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 documents a LangGraph.js example, 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, 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/.