Enruta

Docs · preview

API

Base URL https://api.enruta.ai/v0. JSON in, JSON out. One endpoint matters: request payment.

Authentication

Authorization: Bearer <key>. Keys belong to an organization and carry a role. Agent keys can create payment requests and read the records they produced. Developer keys can manage policies, rails, webhooks and read everything. Auditor keys can read and export records only.

Idempotency

POST /payment-requests requires an Idempotency-Key header. A repeated key returns the original response with decision.reasons including idempotent_replay. Keys live 24 hours.

Payment requests

POST/v0/payment-requests

Ask to pay. Returns the decision, and the mandate and credential on allow. Requires Idempotency-Key.

curl https://api.enruta.ai/v0/payment-requests \
  -H "Authorization: Bearer $ENRUTA_AGENT_KEY" \
  -H "Idempotency-Key: a3c9…" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "agt_procurement_02",
    "payee": { "name": "Demo Store", "domain": "store.enruta.ai" },
    "amount": { "value": "168.40", "currency": "USD" },
    "purpose": "20 reams A4 paper, 1 stapler",
    "checkout": { "protocol": "ucp", "reference": "chk_8f3…" },
    "evidence": [{ "type": "quote", "ref": "https://store.enruta.ai/q/1042", "digest": "sha256:…" }]
  }'

On allow, mandate carries id, amount, expires_at and credential: the reference the payee’s payment handler accepts (a network token or a single-use card reference, never a card number). trace is optional: a digest of the agent’s own reasoning or run log, so the record can prove what the agent knew without Enruta storing it.

GET/v0/payment-requests/{id}

The request, its current decision, and the approval and mandate state. Poll this after a step_up, or subscribe to the webhook.

Approvals

Approvals are completed by a person, in the console or through the signed link. The API exposes state only.

GET/v0/approvals/{id}
{ "id": "apr_…", "state": "pending" | "approved" | "declined" | "expired",
  "actor": "usr_…", "method": "passkey", "completed_at": "…" }

Mandates

GET/v0/mandates/{id}

One mandate: scope, expiry, consumed state, the request hash it binds.

GET/v0/mandates

Filter with agent_id and state (issued, consumed, expired).

Records

GET/v0/records/{id}

The record. Add /bundle for its attestation bundle.

GET/v0/records

Filter with agent_id, from, to. Paginated with cursor.

POST/v0/exports
{ "from": "2026-11-01", "to": "2026-11-30" }
→ 202 { "id": "exp_…" }
GET /v0/exports/{id} → a signed download URL when ready

An export is a directory: one .json per record, one .bundle.json beside it, the verifier, the schema and the test vectors.

Policies and rails

Policies are created and signed in the console; the API reads them (GET /v0/policies, GET /v0/policies/{id}/versions/{n}). Rail connections are created by an administrator in the console with credentials scoped on the rail’s side; the API lists them (GET /v0/rails).

Webhooks

EventWhen
payment_request.decidedA decision was made
approval.completedA person approved or declined
mandate.issuedA mandate exists and can be redeemed
mandate.consumedThe rail authorized against the mandate
record.sealedThe record was sealed and time-stamped

Deliveries carry an Enruta-Signature header: HMAC-SHA256 over the timestamp and body with your endpoint secret. Retries follow an exponential schedule for 24 hours.

Errors

StatusCodeMeaning
400invalid_requestA field is missing or malformed
401unauthenticatedNo key, or an unknown key
403forbiddenThe key’s role cannot do this
404not_foundNo such object in this organization
409idempotency_conflictSame key, different body
429rate_limitedSlow down; Retry-After is set

A denied payment is not an error. It is a 201 with decision.result: "deny", and it produces a record.