Catalog
CRUD the products an agent can buy. Prices are exact decimal strings; deletes are soft. Every write invalidates the manifest cache.
The catalog is the list of CatalogItems an agent sees at /agent/catalog. All catalog
routes are tenant-resolved: they target the merchant named by ?merchant_id=, or the
project's default merchant when omitted.
Tenant resolution
Pass ?merchant_id=mrch_… to target a specific merchant. Without it, the route falls back to
the project's first/default merchant. An id from another project or mode resolves to 404
NOT_FOUND.
The CatalogItem
{
"sku": "widget",
"name": "Widget",
"description": "An optional blurb",
"price": { "amount": "25.00", "currency": "USD" },
"available": true,
"category": "hardware",
"unit": "each",
"metadata": { "any": "json" }
}price.amount is an exact decimal string (≤ 8 dp). Every catalog write invalidates the
merchant's manifest cache so the next discovery serve rebuilds.
GET /v1/catalog
List products for the resolved merchant, ordered by category then name.
| Auth | API key · scope products:read |
|---|---|
| Query | merchant_id (optional), available=true (optional — only available items) |
{ "items": [ /* CatalogItem[] */ ] }curl "https://api.veto-ai.com/v1/catalog?merchant_id=mrch_01J…&available=true" \
-H "Authorization: Bearer veto_test_8f2c…"POST /v1/catalog
Create a product. The sku is unique per merchant.
| Auth | API key · scope products:write |
|---|
Request
{
"sku": "widget",
"name": "Widget",
"price": { "amount": "25.00", "currency": "USD" },
"available": true,
"description": "An optional blurb",
"category": "hardware",
"unit": "each",
"metadata": {}
}| Field | Required | Rule |
|---|---|---|
sku | ✅ | Matches ^[A-Za-z0-9._-]{1,128}$; unique per merchant. |
name | ✅ | — |
price.amount | ✅ | Decimal string, ≤ 8 dp. |
price.currency | ✅ | Stored upper-cased. |
available | optional | Defaults to true. |
Status codes
| Status | Reason code | Meaning |
|---|---|---|
201 | — | Created; returns { id, ...CatalogItem }. |
400 | VALIDATION_FAILED | Missing/invalid sku, name, or currency. |
400 | PRICE_AMOUNT_INVALID | price.amount not a ≤8-dp decimal string. |
409 | PRODUCT_SKU_TAKEN | SKU already exists for this merchant. |
curl -X POST "https://api.veto-ai.com/v1/catalog?merchant_id=mrch_01J…" \
-H "Authorization: Bearer veto_test_8f2c…" \
-H "Content-Type: application/json" \
-d '{ "sku": "widget", "name": "Widget", "price": { "amount": "25.00", "currency": "USD" } }'PATCH /v1/catalog/:sku
Update price, availability, or metadata of a product by SKU. Only present fields change.
| Auth | API key · scope products:write |
|---|
{ "price": { "amount": "29.99", "currency": "USD" }, "available": false }| Status | Reason code | Meaning |
|---|---|---|
200 | — | The updated CatalogItem. |
400 | PRICE_AMOUNT_INVALID | Bad price.amount. |
404 | NOT_FOUND | No such SKU for this merchant. |
DELETE /v1/catalog/:sku
Soft-delete — flips available to false so existing orders and receipts keep pointing
at a real row. The product still exists; it just won't be offered.
| Auth | API key · scope products:write |
|---|
| Status | Meaning |
|---|---|
204 | Soft-deleted (no body). |
404 | NOT_FOUND — no such SKU. |
curl -X DELETE "https://api.veto-ai.com/v1/catalog/widget?merchant_id=mrch_01J…" \
-H "Authorization: Bearer veto_test_8f2c…"Merchants
Create and manage merchants. Creating a merchant mints its Ed25519 receipt-signing key; the public JWKS is served for offline receipt verification.
Receiving
The merchant's pay_to destination(s). Validated loud and early at write time so an agent never hits a broken receiving config at runtime.