VetoVetoDocs
Hosted REST API

Ingest

The reporting path. A self-hosted SDK posts terminal gate outcomes here so hosted reputation, orders, receipts, and webhooks stay current. Idempotent so retries are safe.

The /v1/ingest/* routes are how a self-hosted SDK keeps the hosted control plane in sync. After the SDK runs the gate locally, it fire-and-forget POSTs the outcome here; Veto records orders/receipts, applies the reputation delta, and fans out webhooks. Every route is idempotent on session.id, so SDK retries never double-count.

AuthAPI key · scope ingest:write (all three routes)

Anchored vs. unanchored

Order/receipt rows are persisted only when the session anchors to a known merchant (by mrch_ id). When the merchant is unknown, the call still succeeds and the reputation delta still runs (reputation is global) — it just doesn't write order rows.


POST /v1/ingest/session

Upsert a session snapshot. Idempotent on session.id; persists only when the session anchors to a known merchant, otherwise acknowledges without persisting.

Request

{ "session": { "id": "…", "merchantId": "mrch_01J…", "agentId": "…", "state": "awaiting_payment", "...": "a CheckoutSession" } }

Response — 200

{ "ok": true, "session_id": "…", "persisted": true }
StatusReason codeMeaning
200Acknowledged (persisted says whether a row was written).
400VALIDATION_FAILEDsession.id missing.

POST /v1/ingest/order — terminal accept

Report an accepted settle. In one transaction this writes the order + receipt rows (when anchored), runs the reputation delta, and enqueues order.accepted + order.settled webhook events.

Request

{
  "session": { "id": "…", "state": "accepted", "agentId": "…", "merchantId": "mrch_01J…", "...": "a CheckoutSession" },
  "receipt": "<compact JWS receipt>",
  "settlement": { "reference": "0xabc…", "...": "a SettlementResult" },
  "fulfillment": { "any": "json" }
}

The session.state must be accepted.

Response — 201

{
  "order_id": "ord_01J…",
  "receipt_id": "rcpt_01J…",
  "event_ids": ["evt_01J…", "evt_01J…"],
  "reputation": { "agentId": "…", "score": 540, "tier": "trusted" }
}

Idempotent replay

A second POST for the same session.id does not re-run anything — it returns the original result with "replayed": true:

201 — replay (anchored)
{ "order_id": "ord_01J…", "receipt_id": "rcpt_01J…", "event_ids": [], "replayed": true }
201 — replay (reputation-only)
{ "order_id": null, "receipt_id": null, "event_ids": [], "replayed": true, "reputation_score": 540 }

Status codes

StatusReason codeMeaning
201Recorded (or replayed).
400VALIDATION_FAILEDMissing session or state !== "accepted".
report an accepted settle
curl -X POST https://api.veto-ai.com/v1/ingest/order \
  -H "Authorization: Bearer veto_test_8f2c…" \
  -H "Content-Type: application/json" \
  -d '{ "session": { "id": "sess_1", "state": "accepted", "agentId": "…", "merchantId": "mrch_01J…" }, "receipt": "<jws>" }'

POST /v1/ingest/decision — terminal reject / hold

Report a non-accept outcome. Runs the reputation delta and fires order.rejected or order.held depending on the verdict.

Request

{ "session": { "id": "…", "agentId": "…", "merchantId": "mrch_01J…", "verdict": { "decision": "hold", "...": "…" }, "...": "a CheckoutSession" } }

The webhook type is order.held when verdict.decision === "hold", else order.rejected.

Response — 200

{
  "ok": true,
  "decision": "hold",
  "event_ids": ["evt_01J…"],
  "reputation": { "agentId": "…", "score": 480, "tier": "standard" }
}
StatusReason codeMeaning
200Recorded.
400VALIDATION_FAILEDsession missing.

Why two accept events

order.accepted fires before irreversible capture; order.settled confirms the rail. Subscribers should fulfill on order.settled. See Webhooks.