Authentication
Every /v1/verify call is authenticated with a merchant API key sent as a bearer token. A key is bound to one merchant_id and is rejected if it tries to verify for any other merchant. Keep it server-side.
Authorization: Bearer mg_live_xxxxxxxxxxxxxxxxxxxx
Missing or invalid key returns 401. A valid key used with the wrong merchant_id in the body returns 403. The /healthz and /v1/scan endpoints take no auth.
Endpoints
This page documents /v1/verify. The scan endpoint powers the public agent-ready scanner; /healthz returns {"status":"ok","version":"0.1.0"}.
Request body
A JSON object. Only merchant_id and request_context are required; the rest are supplied as the relevant layers apply to your setup.
- merchant_id required
- Your merchant identifier, for example
mch_store_01. Must match the merchant your API key is bound to. - request_context required
- The signature-relevant context of the agent's HTTP request:
method,target_uri(the exact RFC 9421@target-uri), the rawheaders(lowercase keys, includingsignature-inputandsignature), and an optionalreceived_attimestamp that anchors clock-skew and expiry windows. - checkout optional
- The checkout the merchant is about to settle.
checkout.cartis the server-built basket (amountin minor units, ISO 4217currency, optional lineitems).checkout.checkout_jwtis the merchant-signed checkout terms; when present, the mandate'scheckout_hashis verified against it and the signed amount is cross-checked against the cart. - mandates optional
- AP2 v0.2 mandates in SD-JWT form.
checkout_mandateis the agent's signed spending mandate;open_checkout_mandateis the user-signed open mandate used in autonomous (human-not-present) mode. - acp optional
- Agentic Commerce Protocol context for ACP-mode merchants: the Checkout Session
session_status, anallowanceobject (max_amount,currency,expires_at), and theshared_payment_token. - policy optional
- Your verification policy:
mode(strict,standard, ormonitor),required_layers, an optionaltrusted_agentsallow-list, andmax_clock_skew_seconds. Defaults to standard with[transport, mandate]required. See policy modes.
Example request
curl -X POST https://verify.mandategate.com/v1/verify \
-H "Authorization: Bearer mg_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"merchant_id": "mch_store_01",
"request_context": {
"method": "POST",
"target_uri": "https://shop.example.com/wp-json/wc/store/v1/checkout",
"headers": {
"signature-agent": "\"https://agent.example/.well-known/http-message-signatures-directory\"",
"signature-input": "sig1=(\"@authority\" \"@method\" \"@target-uri\" \"content-digest\");created=1781370000;keyid=\"ed25519-key-1\";tag=\"web-bot-auth\"",
"signature": "sig1=:K2qGT5srn2OGbOIDzQ6kYT+ruaycnDAAUpKv+ePFfD0=:",
"content-digest": "sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:"
},
"received_at": "2026-06-14T08:20:05Z"
},
"checkout": {
"checkout_jwt": "eyJhbGciOiJFUzI1NiIsInR5cCI6...",
"cart": { "amount": 4999, "currency": "GBP" }
},
"mandates": { "checkout_mandate": "eyJhbGciOiJFUzI1NiIsInR5cCI6InZjK3NkLWp3dCJ9..." },
"policy": { "mode": "standard", "required_layers": ["transport", "mandate"] }
}'
Response body
Always HTTP 200 for a well-formed, authorized request, including a deny.
- decision
- allow deny review. The verdict your plugin enforces.
- verification_id
- Unique id for this verification, used as the audit and replay-ledger key.
- reason_codes
- The granular signals that drove the decision, for example
transport.signature_validormandate.checkout_hash_mismatch. The full set is in the reason code reference. - layers
- Per-layer results (
transport,mandate,allowance), each withevaluated, aresultof pass / fail / indeterminate / skipped, and the individualchecks. - audit
- The tamper-evident record:
receipt_hashof this decision,prev_hashof the previous one, andlogged_at.
{
"decision": "allow",
"verification_id": "vrf_01J8Z3K4",
"reason_codes": [
"transport.signature_valid",
"mandate.signature_valid",
"mandate.exp_valid",
"mandate.checkout_hash_match",
"mandate.constraint_satisfied"
],
"layers": {
"transport": { "evaluated": true, "result": "pass" },
"mandate": { "evaluated": true, "result": "pass" },
"allowance": { "evaluated": false, "result": "skipped" }
},
"audit": { "receipt_hash": "sha256:9f2c...", "prev_hash": "sha256:00aa...", "logged_at": "2026-06-14T08:20:05Z" }
}
Status codes
| Code | Meaning |
|---|---|
| 200 | A verification decision. This includes deny and review, not only allow. |
| 400 | The request body is malformed or missing a required field. |
| 401 | The merchant API key is missing or invalid. |
| 403 | The key is valid but used for a merchant_id it is not bound to. |
| 422 | The body is well-formed but logically contradictory, for example mode: strict with no layer input at all. |
What it does not do
The verify API returns a decision and nothing more. It does not capture payment, hold funds, or touch your gateway; settlement stays where it is today. It does not see or store a human shopper's identity. Enforcement is the merchant plugin's job: the API judges, the plugin acts. That separation is what lets you deploy in monitor mode with no risk to live sales.