Merchants
Create and manage merchants. Creating a merchant mints its Ed25519 receipt-signing key; the public JWKS is served for offline receipt verification.
A merchant is the top-level resource. Creating one mints an Ed25519 receipt-signing
identity: the private seed is generated server-side, AES-GCM encrypted at rest, and
never returned; the public JWK and its kid are published so any buyer can verify a
receipt offline. A project may host several merchants.
The receipt key is the only key Veto holds.
The signing seed never leaves the server and is never in any response. The public half is
served at /v1/merchants/:id/jwks and the canonical
/.well-known/jwks.json.
The merchant object
{
"id": "mrch_01J…",
"slug": "acme",
"name": "Acme Corp",
"domain": "shop.acme.example",
"receiving": { "x402": { "chain": "base", "address": "0x…", "asset": "USDC" } },
"rails": ["x402", "mock"],
"receipt_kid": "mrch_01J…-receipts-v1",
"receipt_pub_jwk": { "kty": "OKP", "crv": "Ed25519", "x": "…", "kid": "mrch_01J…-receipts-v1" },
"trusted_veto_jwks_url": null,
"livemode": false,
"created_at": "2026-06-24T12:00:00.000Z",
"updated_at": "2026-06-24T12:00:00.000Z"
}POST /v1/merchants
Create a merchant. livemode is inherited from the resolving key — a test key creates a
test merchant, a live key a live merchant.
| Auth | API key · scope merchants:write |
|---|
Request
{
"slug": "acme",
"name": "Acme Corp",
"domain": "shop.acme.example",
"receiving": { "x402": { "chain": "base", "address": "0x…", "asset": "USDC" } },
"rails": ["x402", "mock"],
"trusted_veto_jwks_url": "https://veto-ai.com/.well-known/jwks.json"
}| Field | Required | Notes |
|---|---|---|
slug | ✅ | Unique within (slug, livemode). |
name | ✅ | Display name. |
domain | ✅ | The merchant's storefront domain. |
receiving | ✅ | Validated at write time — see Receiving. |
rails | optional | Subset of x402, card, mock. Defaults to ["x402","mock"]. |
trusted_veto_jwks_url | optional | The Veto issuer JWKS used to verify buyer mandates. |
Status codes
| Status | Reason code | Meaning |
|---|---|---|
201 | — | Created; returns the merchant object. |
400 | VALIDATION_FAILED | Missing slug/name/domain, or bad rails. |
400 | RECEIVING_ADDRESS_INVALID · RECEIVING_CHAIN_UNSUPPORTED · RECEIVING_MOCK_IN_LIVE | Receiving failed validation. |
409 | MERCHANT_SLUG_TAKEN | Slug already used in this mode. |
curl -X POST https://api.veto-ai.com/v1/merchants \
-H "Authorization: Bearer veto_test_8f2c…" \
-H "Content-Type: application/json" \
-d '{
"slug": "acme",
"name": "Acme Corp",
"domain": "shop.acme.example",
"receiving": { "x402": { "chain": "base-sepolia", "address": "0x1111111111111111111111111111111111111111", "asset": "USDC" } }
}'GET /v1/merchants
List merchants in the project (current mode), newest first.
| Auth | API key · scope merchants:read |
|---|---|
| Query | limit (1–100, default 20) |
{ "data": [ /* merchant objects */ ], "has_more": false, "next_cursor": null }curl "https://api.veto-ai.com/v1/merchants?limit=20" \
-H "Authorization: Bearer veto_test_8f2c…"GET /v1/merchants/:id
Fetch one merchant the caller's project owns (in the caller's mode).
| Auth | API key · scope merchants:read |
|---|
| Status | Meaning |
|---|---|
200 | The merchant object. |
404 | NOT_FOUND — unknown id, or it belongs to another project/mode. |
PATCH /v1/merchants/:id
Update name, domain, receiving, rails, or trusted_veto_jwks_url. Only the fields
present are changed. slug, livemode, and the receipt key are immutable.
| Auth | API key · scope merchants:write |
|---|
{ "name": "Acme Inc.", "rails": ["x402"] }| Status | Reason code | Meaning |
|---|---|---|
200 | — | Updated merchant object. |
400 | VALIDATION_FAILED / receiving codes | Bad rails or receiving. |
404 | NOT_FOUND | Not found in this project/mode. |
GET /v1/merchants/:id/jwks
Public. The merchant's receipt-verification JWKS — the hosted twin of the self-hosted
/.well-known/jwks.json. Any buyer can fetch it to verify a receipt offline. Not mode-guarded
(a public key reveals nothing); resolved by id only.
| Auth | Public |
|---|---|
| Headers | Access-Control-Allow-Origin: *, Cache-Control: public, max-age=300 |
{ "keys": [ { "kty": "OKP", "crv": "Ed25519", "x": "…", "kid": "mrch_01J…-receipts-v1" } ] }curl https://api.veto-ai.com/v1/merchants/mrch_01J…/jwksSee Verify receipts for the verification recipe.