AgenticRail Enforcement API

Pre-execution gate decisions

Operations 1

POST /v1/evaluate Evaluate one agent step before it runs #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/agenticrail-nz:agenticrail-nz-enforcement-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

agenticrail-nz-enforcement-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: AgenticRail Public Enforcement API
  version: 1.2.7
  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. A sequence is only finished, and its record only complete, once the final step of its declared order is ALLOWED: that call returns `sealed: true`, the sequence closes to any further step, and its report reads `SEALED`. Until then the record can only say what happened so far, so run every sequence to its last declared step.


    What 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.


    Evidence 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


    Timestamps: `ts_ms` is supplied by the caller (or, if omitted, taken from the gate''s clock) and covered by the signature, so it cannot be altered after signing without breaking verification. The gate refuses any timestamp more than 300 seconds from its own clock (STALE_TIMESTAMP), so the asserted time is bounded. 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/
servers:
- url: https://api.agenticrail.nz
  description: Production
security:
- bearerAuth: []
tags:
- name: enforcement
  description: Pre-execution gate decisions
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.


        Checks run in two phases. **Phase 1, evaluated together (several can be reported at once):** timestamp more than 300 seconds from the gate''s clock (`STALE_TIMESTAMP`), `step` not equal to `function` (`FUNCTION_STEP_MISMATCH`), action type not permitted for the step (`ACTION_NOT_ALLOWED`), and a `RECORD_RESULT` at `boundary` whose `attestation.witnessed_pack_id` does not bind to the last allowed receipt (`ARTIFACT_UNBOUND`). **Phase 2, only if phase 1 found nothing:** step not in the sequence''s declared step order (`UNKNOWN_STEP`), then sequence already sealed (`SEALED_SEQUENCE`), a `step_order` different from the one the sequence was locked to on its first call (`STEP_ORDER_MISMATCH`), nonce reused (`REPLAY_NONCE`), step out of order (`SEQUENCE_VIOLATION`). Otherwise `ALLOW`.


        Because phase 1 runs first, a wrong `action_type` is reported as `ACTION_NOT_ALLOWED` even when the step would also be unknown or out of order. Every DENY carries what would have worked (see the response schema).'
      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. Without `deny_status`, this is returned for both ALLOW and DENY — read the `decision` field, not the HTTP status. With `deny_status=409`, only ALLOW returns 200.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvaluateResponse'
        '409':
          description: Returned only when the request carried `deny_status=409` and the decision was not ALLOW. The body is the same EvaluateResponse a 200 would have carried, with its signed receipt.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvaluateResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '403':
          $ref: '#/components/responses/Forbidden'
      security:
      - bearerAuth: []
      - {}
      parameters:
      - name: deny_status
        in: query
        required: false
        description: Optional. Set to `409` to receive HTTP 409 on any non-ALLOW decision instead of 200. For callers that only read the HTTP status -- shell scripts with `curl --fail`, schedulers, CI steps, no-code webhook steps -- which would otherwise read a DENY as success and run the next step. The response body, receipt and signature are identical either way; only the status changes. Any other value is refused with 400. Omit it and behaviour is unchanged (the SDKs and the MCP server read `decision` and do not send it).
        schema:
          type: string
          enum:
          - '409'
components:
  schemas:
    Error:
      type: object
      properties:
        error:
          type: string
        message:
          type: string
    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.
        sealed:
          type: boolean
          enum:
          - true
          description: 'Present ONLY on the ALLOW that closes the sequence, i.e. the final step of its declared order. Never sent on a DENY or on an intermediate step, so its absence means the sequence is still open. Once sealed, any further call on the same sequence_id is refused with `SEALED_SEQUENCE`. A sealed sequence is the finished record: it can be read whole from the report, which shows `seal_status: SEALED`.'
        seal_notice:
          type: string
          description: 'Human-readable notice sent with `sealed: true`: the sequence is closed, and where to read its report.'
        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, every rule that refused the step; phase-1 checks can report more than one at once.
        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'
        allowed_action_types:
          type: array
          items:
            type: string
          description: 'DENY with `ACTION_NOT_ALLOWED` only: the action types this step would have accepted. Not signed.'
        next_expected_step:
          type: string
          description: 'DENY with `SEQUENCE_VIOLATION` only: the step the sequence is waiting for. Not signed.'
        locked_step_order:
          type: array
          items:
            type: string
          description: 'DENY with `STEP_ORDER_MISMATCH` only: the order this sequence was locked to on its first call. Resend this list. Not signed.'
        expected_step_order:
          type: array
          items:
            type: string
          description: 'DENY with `UNKNOWN_STEP` only: the step order the step had to appear in. Not signed.'
        step_order_source:
          type: string
          enum:
          - caller
          - msmd_spine
          description: 'With `expected_step_order`: whether that order was the caller''s `step_order` or the built-in MSMD spine (used when `step_order` was absent). Not signed.'
        sequence_scope_notice:
          type: string
          description: Present when the sequence was scoped differently from what the caller may expect. Not signed.
    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.
        decision_index:
          type:
          - integer
          - 'null'
          minimum: 1
          description: This decision's position among every decision in the sequence, counted from 1 across ALLOW and DENY alike, and signed into the receipt (added 2026-10-08). The prev_receipt chain runs through ALLOWED receipts only, so nothing links to a DENY; the numbering is what makes a removed receipt of either decision visible as a missing number. Keep it with your own copy of each response and you can name a missing receipt without reading ours. Null if the number could not be reserved.
        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
    EvaluateRequest:
      type: object
      required:
      - sequence_id
      - step
      - action_type
      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: Optional; defaults to `step`. If sent, must equal `step` (else `FUNCTION_STEP_MISMATCH`).
        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: 'Optional step inputs. Screened for prompt-injection patterns before enforcement. NEVER stored in the receipt: the receipt carries only `payload_hash`, a SHA-256 of the whole request. Use `attestation` for anything that must be kept as evidence.'
          additionalProperties: true
        nonce:
          type: string
          format: uuid
          description: 'Optional; generated by the gate if absent. If sent, unique per step: reuse is refused with `REPLAY_NONCE`.'
        ts_ms:
          type: integer
          description: Optional. Caller's timestamp, epoch milliseconds. If sent, refused with `STALE_TIMESTAMP` when more than 300 seconds from server time in either direction. If absent, the gate's own clock is used and the freshness check is skipped.
        step_order:
          type: array
          items:
            type: string
          description: 'Optional, but send it on every call, identical each time, for your own process. The sequence''s declared step order; one step is a valid sequence. It 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 `locked_step_order`. Omit to use the default MSMD spine: intake, disruption, instability, state_read, internal_driver, execution, boundary, settle. Not an array, or empty: 400.'
        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`.
  responses:
    Forbidden:
      description: 'A KNOWN credential that may not be used: the key is revoked (`key_disabled`) or the client account is disabled (`client_disabled`). Never a fallback to the demo lane.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: missing_bearer_token
            message: 'Authorization: Bearer <token> required'
    BadRequest:
      description: 'Refused before evaluation, so no receipt is written: malformed JSON (`invalid_json`); a missing or malformed field (`invalid_request`, with a message naming it: no `sequence_id`, `step` or `action_type`, a `step_order` that is not a non-empty list, a non-numeric `ts_ms`, a keyed `sequence_id` starting `demo-`, an unrecognised `deny_status`); 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 per IP. Keys are limited per minute by plan: developer 120, growth 3,000, scale 30,000. There is no monthly quota.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: '`Authorization: Bearer <your-key>`. Optional.


        An 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.


        A KNOWN identity presented wrongly is still refused: a wrong secret on a real prefix returns 401, and a revoked key returns 403.


        The 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`.'
externalDocs:
  description: Developer documentation and the enforcement specification
  url: https://agenticrail.nz/docs/