{
  "openapi": "3.1.0",
  "info": {
    "title": "AgenticRail Public API",
    "version": "1.2.0",
    "summary": "Deterministic sequence enforcement for AI agents. ALLOW / DENY / HALT, with a signed receipt for every decision.",
    "description": "AgenticRail is a pre-execution enforcement layer. An agent calls `POST /v1/evaluate` BEFORE it runs each step of a sequence; the gate returns `ALLOW` or `DENY` and seals an Ed25519-signed receipt of that decision. It enforces strict step order, replay protection (nonce + timestamp freshness), and sequence sealing at the final step.\n\nWhat it is not: AgenticRail does not run your step for you, and an `ALLOW` does not assert that the downstream action succeeded. It is an enforcement layer, not an execution runtime.\n\nEvidence design: receipts are Ed25519-signed over the canonical JSON of the receipt with the `signature` field removed. The durable report ships the raw signature alongside the exact `signed_canonical` preimage, so any third party can run `ed25519_verify(pubkey, signed_canonical, signature)` offline with no callback to AgenticRail. Public keys are published at https://agenticrail.nz/spec/receipt-public-keys.json\n\nTimestamps: `ts_ms` is generated by AgenticRail and covered by the signature, so it cannot be altered after signing without breaking verification. It is not an independently attested time — no RFC 3161 token is issued. A receipt is a record of when AgenticRail observed a step, not proof of when it occurred.",
    "termsOfService": "https://agenticrail.nz/api-terms/",
    "contact": {
      "name": "AgenticRail (TUARA KURI LIMITED)",
      "email": "hello@agenticrail.nz",
      "url": "https://agenticrail.nz/docs/"
    },
    "license": {
      "name": "Method may be freely implemented. Text (c) 2026 TUARA KURI LIMITED.",
      "url": "https://agenticrail.nz/api-terms/"
    }
  },
  "externalDocs": {
    "description": "Developer documentation and the enforcement specification",
    "url": "https://agenticrail.nz/docs/"
  },
  "servers": [
    {
      "url": "https://api.agenticrail.nz",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "enforcement",
      "description": "Pre-execution gate decisions"
    },
    {
      "name": "evidence",
      "description": "Verifiable records of what was enforced"
    },
    {
      "name": "operational",
      "description": "Health and latency helpers"
    }
  ],
  "paths": {
    "/v1/evaluate": {
      "post": {
        "operationId": "evaluateStep",
        "tags": [
          "enforcement"
        ],
        "summary": "Evaluate one agent step before it runs",
        "description": "Call this BEFORE executing a step. If the response is `DENY`, do not run the step.\n\nNine enforcement rules apply, in this order: unknown step for the sequence's declared step order (`UNKNOWN_STEP`), action type not permitted for the function (`ACTION_NOT_ALLOWED`), `step` not equal to `function` (`FUNCTION_STEP_MISMATCH`), sequence already sealed (`SEALED_SEQUENCE`), nonce reused (`REPLAY_NONCE`), step out of order (`SEQUENCE_VIOLATION`), timestamp outside a 300-second window (`STALE_TIMESTAMP`), and a `RECORD_RESULT` at `boundary` whose `attestation.witnessed_pack_id` does not bind to the last allowed receipt (`ARTIFACT_UNBOUND`). A sequence also refuses a `step_order` that differs from the one it was opened with (`STEP_ORDER_MISMATCH`) — the declared order is locked on the first call so a later call cannot shorten it to skip a step. Otherwise `ALLOW`.\n\nNote that action-type is checked before step-order, so a wrong `action_type` masks a would-be `SEQUENCE_VIOLATION`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EvaluateRequest"
              },
              "example": {
                "sequence_id": "order-4471",
                "step": "intake",
                "function": "intake",
                "action_type": "CHECK_STATE",
                "action": "receive_customer_request",
                "inputs": {
                  "signal": "new order"
                },
                "nonce": "6f1c9a2e-6b4d-4a17-9a7e-0f2b1d3c4e5a",
                "ts_ms": 1785487358119
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A decision was reached. This is returned for both ALLOW and DENY — read the `decision` field, not the HTTP status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EvaluateResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {}
        ]
      }
    },
    "/v1/report": {
      "post": {
        "operationId": "getReport",
        "tags": [
          "evidence"
        ],
        "summary": "Generate the verifiable enforcement report for a sequence",
        "description": "Returns the durable evidence for a sequence: every enforcement decision, each receipt's raw `signature` and the exact `signed_canonical` preimage it was computed over, the `prev_receipt_hash` chain verdict, and an independent-archive comparison.\n\nThis is the surface that supports offline verification. The synchronous `/v1/evaluate` response deliberately carries signature metadata only; the raw signature surfaces here.\n\nA human-readable version of the same evidence is at https://report.agenticrail.nz/report",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReportRequest"
              },
              "example": {
                "sequence_id": "order-4471",
                "format": "json"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The enforcement report.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Report"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {}
        ]
      }
    },
    "/v1/warm": {
      "post": {
        "operationId": "warmSequence",
        "tags": [
          "operational"
        ],
        "summary": "Pre-warm a sequence's enforcement state",
        "description": "Advisory only. Reduces cold-start latency on the first evaluate for a sequence. Always returns 200 and never blocks or affects an enforcement decision. Measured benefit in isolation is modest (~9%).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sequence_id"
                ],
                "properties": {
                  "sequence_id": {
                    "type": "string"
                  },
                  "model_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Always returned.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                },
                "example": {
                  "ok": true
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {}
        ]
      }
    },
    "/v1/health": {
      "get": {
        "operationId": "health",
        "tags": [
          "operational"
        ],
        "summary": "Liveness check",
        "security": [],
        "responses": {
          "200": {
            "description": "Service is up.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "service": {
                      "type": "string"
                    },
                    "now": {
                      "type": "integer",
                      "description": "Server time, epoch milliseconds."
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "service": "agenticrail-wrapper",
                  "now": 1785487362545
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "`Authorization: Bearer <your-key>`. Optional.\n\nAn UNRECOGNISED identity is served on the public demo lane rather than refused: no header at all, a string that cannot be a key, or a well-formed prefix that was never issued. The call runs, the receipt is real, and the response carries `lane`, `lane_reason` and `lane_notice` saying so.\n\nA KNOWN identity presented wrongly is still refused: a wrong secret on a real prefix returns 401, and a revoked key returns 403.\n\nThe public demo key `DEMO-AGENTICRAIL-PUBLIC-2026` is real, rate-limited to 300 requests per minute, and forces every `sequence_id` to carry a `demo-` prefix. Sequences on the demo lane are readable by anyone holding the sequence id, so do not put anything private in `attestation`."
      }
    },
    "schemas": {
      "EvaluateRequest": {
        "type": "object",
        "required": [
          "sequence_id",
          "step",
          "function",
          "action_type",
          "nonce",
          "ts_ms"
        ],
        "properties": {
          "sequence_id": {
            "type": "string",
            "description": "Stable identifier for this run. All steps of one sequence share it."
          },
          "step": {
            "type": "string",
            "description": "The step being attempted. Must equal `function`."
          },
          "function": {
            "type": "string",
            "description": "The function being attempted. Must equal `step`."
          },
          "action_type": {
            "type": "string",
            "description": "What kind of action this is. Each function permits a specific set.",
            "enum": [
              "CHECK_STATE",
              "SELECT_NEXT_STEP",
              "PAUSE_CYCLE",
              "RECORD_RESULT"
            ]
          },
          "action": {
            "type": "string",
            "description": "Human-readable label for the action. Recorded, not enforced."
          },
          "inputs": {
            "type": "object",
            "description": "Arbitrary step inputs. Screened for prompt-injection patterns before enforcement.",
            "additionalProperties": true
          },
          "nonce": {
            "type": "string",
            "format": "uuid",
            "description": "Unique per step. Reuse is refused with `REPLAY_NONCE`."
          },
          "ts_ms": {
            "type": "integer",
            "description": "Caller's timestamp, epoch milliseconds. Refused with `STALE_TIMESTAMP` if more than 300 seconds from server time in either direction."
          },
          "step_order": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Optional. The sequence's declared step order, sent on EVERY call — the gate reads it from the payload and does not store it. Omit to use the default MSMD spine: intake, disruption, instability, state_read, internal_driver, execution, boundary, settle."
          },
          "attestation": {
            "type": "object",
            "description": "Required for a `RECORD_RESULT` at the `boundary` step. Binds the recorded result to a specific prior receipt.",
            "properties": {
              "witnessed_pack_id": {
                "type": "string",
                "description": "Must equal the last ALLOWED, durably-written receipt's `pack_id`, which is then re-fetched and re-checked. Mismatch is refused with `ARTIFACT_UNBOUND`."
              }
            }
          }
        }
      },
      "EvaluateResponse": {
        "type": "object",
        "properties": {
          "decision": {
            "type": "string",
            "enum": [
              "ALLOW",
              "DENY"
            ],
            "description": "The enforcement decision. `HALT` is a status, not a decision: a halted request is refused at the boundary before enforcement and therefore produces no receipt."
          },
          "lane": {
            "type": "string",
            "enum": [
              "public-demo"
            ],
            "description": "Present ONLY when the caller was moved to the public demo lane because the credential presented was not recognised. Absent on an authenticated call, and absent when the demo key was presented deliberately -- so its presence always means this is not the lane the caller asked for."
          },
          "lane_reason": {
            "type": "string",
            "enum": [
              "no_key_supplied",
              "key_format_invalid",
              "key_not_registered"
            ],
            "description": "Why the caller landed on the demo lane."
          },
          "lane_notice": {
            "type": "string",
            "description": "Human-readable notice: the sequence is `demo-` prefixed and its report can be read by anyone with the sequence id."
          },
          "execution_submitted": {
            "type": "boolean",
            "description": "Whether the executor was handed the step. This is the EXECUTOR outcome and is NOT signed into the receipt. Do not confuse it with the receipt's `executed` field, which means only that the decision was ALLOW."
          },
          "pack_id": {
            "type": "string",
            "description": "This decision's receipt identifier (sha256)."
          },
          "reasons": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DenialCode"
            },
            "description": "Empty on ALLOW. On DENY, the rule that refused the step."
          },
          "sequence_id": {
            "type": "string"
          },
          "step": {
            "type": "string"
          },
          "function": {
            "type": "string"
          },
          "action_type": {
            "type": "string"
          },
          "model_id": {
            "type": "string",
            "description": "Always `client:<your-client-id>`."
          },
          "result": {
            "type": "object",
            "description": "Executor outcome detail. `status` is `submitted` on ALLOW, `skipped` on DENY.",
            "additionalProperties": true
          },
          "receipt": {
            "$ref": "#/components/schemas/ReceiptMetadata"
          },
          "log": {
            "type": "object",
            "properties": {
              "ok": {
                "type": "boolean"
              },
              "error": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "ReceiptMetadata": {
        "type": "object",
        "description": "Signature METADATA only. The raw signature is deliberately never echoed here — retrieve it, with its `signed_canonical` preimage, from `POST /v1/report`. Two surfaces on purpose: the synchronous path stays lean, the report is the evidence.",
        "properties": {
          "pack_id": {
            "type": "string"
          },
          "key_id": {
            "type": "string",
            "description": "Selects the verification key. Active: `k2_2026-06-07_ed25519`."
          },
          "signature": {
            "type": "null",
            "description": "Always null here by design. See the description of this schema."
          },
          "signature_alg": {
            "type": "string",
            "enum": [
              "Ed25519",
              "HMAC-SHA256"
            ]
          },
          "payload_hash": {
            "type": "string"
          },
          "prev_receipt_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Anchor: the `pack_id` of the last ALLOWED receipt. Null before the first ALLOW in a sequence, which is legitimate — a denied opening step and the retry after it both carry null. Chain verification resolves predecessors by this anchor, never by position."
          },
          "prev_receipt_hash": {
            "type": [
              "string",
              "null"
            ],
            "description": "sha256 of the predecessor's full canonical JSON, signature included. An identifier reference and a content hash are different things; this is the content hash."
          },
          "ts_ms": {
            "type": "integer"
          },
          "version": {
            "type": "string",
            "enum": [
              "slp8_receipt_v2",
              "slp8_receipt_v3"
            ]
          },
          "attestation": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          }
        }
      },
      "DenialCode": {
        "type": "string",
        "description": "Why a step was refused.",
        "enum": [
          "UNKNOWN_STEP",
          "ACTION_NOT_ALLOWED",
          "FUNCTION_STEP_MISMATCH",
          "SEALED_SEQUENCE",
          "REPLAY_NONCE",
          "SEQUENCE_VIOLATION",
          "STALE_TIMESTAMP",
          "ARTIFACT_UNBOUND",
          "STEP_ORDER_MISMATCH"
        ]
      },
      "ReportRequest": {
        "type": "object",
        "required": [
          "sequence_id"
        ],
        "properties": {
          "sequence_id": {
            "type": "string"
          },
          "model_id": {
            "type": "string"
          },
          "format": {
            "type": "string",
            "enum": [
              "json",
              "html"
            ],
            "default": "json"
          }
        }
      },
      "Report": {
        "type": "object",
        "properties": {
          "report_version": {
            "type": "string"
          },
          "report_hash": {
            "type": "string"
          },
          "generated_at": {
            "type": "integer"
          },
          "sequence_id": {
            "type": "string"
          },
          "model_id": {
            "type": "string"
          },
          "verification_status": {
            "type": "string",
            "enum": [
              "VERIFIED_INTACT",
              "CHAIN_BROKEN",
              "SIGNATURE_INVALID"
            ],
            "description": "Recomputed fresh on every call, never stored."
          },
          "seal_status": {
            "type": "string",
            "enum": [
              "OPEN",
              "SEALED"
            ]
          },
          "signatures": {
            "type": "object",
            "additionalProperties": true
          },
          "hash_chain": {
            "type": "object",
            "additionalProperties": true
          },
          "independent_archive": {
            "type": "object",
            "description": "Verdict from comparing against a separate write-once archive bucket held under a different credential. Answers verdict-only and never returns archived bytes. Includes `archived_at`, the storage service's own upload stamp — corroboration, explicitly not a cryptographically attested timestamp.",
            "additionalProperties": true
          },
          "verification": {
            "type": "object",
            "description": "How to verify this report yourself, including the public key reference and the timestamp caveat.",
            "additionalProperties": true
          },
          "summary": {
            "type": "object",
            "additionalProperties": true
          },
          "enforcement_log": {
            "type": "array",
            "description": "One entry per decision, each carrying the raw `signature` (base64), the exact `signed_canonical` preimage, `key_id` and `signature_alg`, plus `third_party_verifiable` so an Ed25519 receipt is distinguishable from a legacy server-side-only one. This is a presentation projection: `prev_receipt_id` is not a field here, and `chain_integrity` is the authoritative block for linkage.",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "chain_integrity": {
            "type": "array",
            "description": "Authoritative per-receipt linkage verdicts.",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "enforcement_summary": {
            "type": "string",
            "description": "Deterministic plain-language summary of the enforcement record, composed from the counts in this document. NO LANGUAGE MODEL IS INVOLVED: the same inputs always produce byte-identical text, so this field reproduces like the rest of the report. It states what the record establishes (the order steps were permitted in, and that the record has not changed since it was written) alongside what it does not (that a permitted step's downstream work ran, or that timestamps are independently attested). Replaced an LLM-written `compliance_narrative` on 2026-08-07."
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Malformed JSON, or a payload rejected by input screening (oversized, over-nested, or containing prompt-injection patterns).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "invalid_json",
              "message": "Body is not valid JSON"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "The credential was RECOGNISED but presented wrongly -- a wrong secret on a real key prefix. An unrecognised credential is not refused; it is served on the public demo lane. A revoked key returns 403, not 401.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "missing_bearer_token",
              "message": "Authorization: Bearer <token> required"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded. The public demo key allows 300 requests per minute.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
