{
  "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`). 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" }
        }
      }
    },
    "/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" }
        }
      }
    },
    "/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 }
              }
            }
          }
        }
      }
    },
    "/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>`. This is the only auth the public API accepts; an `x-slp8-key` header is an internal header and returns 401 here.\n\nA public demo key is available for evaluation: `DEMO-AGENTICRAIL-PUBLIC-2026`. It is rate-limited to 300 requests per minute and forces every `sequence_id` to carry a `demo-` prefix."
      }
    },
    "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."
          },
          "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"
        ]
      },
      "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 }
          },
          "compliance_narrative": {
            "type": ["string", "null"],
            "description": "Optional LLM-written plain-language summary. Never load-bearing: verification, signatures, chain resolution and `signed_canonical` never touch a model, and this field is null when no narrative is available."
          }
        }
      },
      "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": "Missing, malformed, or rejected bearer token.",
        "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" }
          }
        }
      }
    }
  }
}
