Servatto Menu API
For scripts and AI agents that manage a venue's menu without a person signing in. Everything the back office's Items & menus page can do, over HTTPS with JSON. Machine-readable: menu-openapi.json.
Getting a key
Back office → Items & menus → API access → New key (owner only). The key is shown once. It acts as a manager of every location, for the menu only; any other endpoint answers 403.
Authorization: Bearer svk_…
Content-Type: application/json
Base URL: https://app.servatto.com/api/office. The menu must be running from Servatto (imported from Square and switched) before edits are accepted; reads work either way. Money is in cents (450 = $4.50). Ids are strings; imported objects keep their Square ids, ones made here start with sv_.
Read
| Call | Gives |
|---|---|
GET /menu | { master, orders, importedAt, menu: { categories, items, modifierLists } } — the whole menu. Add ?venue=<id> for that location's sold-out flags (defaults to the first location). |
GET /menu/items/{id} | One item in the same shape. |
GET /menu/preview | Exactly what a till at the location is served. |
An item looks like:
{ "id": "sv_ab12…", "name": "Flat White", "description": null, "color": null, "imageUrl": null, "gstFree": false, "ordinal": 3,
"categoryIds": ["…"],
"modifierLists": [{ "id": "…", "minSelected": 1, "maxSelected": 1 }],
"variations": [{ "id": "…", "name": "Regular", "priceCents": 450, "sku": null, "upc": null, "soldOut": false }],
"soldOut": false }
Write
| Call | Body | Notes |
|---|---|---|
POST /menu/items | { name, description?, color?, imageUrl?, gstFree?, variations: [{ name, priceCents, sku?, upc? }], categoryIds?, modifierLists?: [{ id, minSelected?, maxSelected? }] } | priceCents: null = variable price typed at the till. At least one variation. |
PATCH /menu/items/{id} | Any of the fields above | Only what you send changes. Sending variations replaces the set (include id to keep a size's id). |
POST /menu/items/{id}/duplicate | — | A copy named "… (copy)". |
DELETE /menu/items/{id} | — | Archives it; past sales keep it. |
POST /menu/categories | { name, parentId?, isMenu?, imageUrl? } | isMenu: true makes a top-level menu (Dine In, Takeaway…) that holds categories. |
PATCH /menu/categories/{id} · DELETE … | ||
POST /menu/modifier-lists | { name, selectionType: "SINGLE"|"MULTIPLE", minSelected?, maxSelected? (-1 = no limit), modifiers: [{ name, priceCents?, onByDefault? }] } | |
PATCH /menu/modifier-lists/{id} · DELETE … | Sending modifiers replaces the set (include id to keep one). | |
PUT /menu/order | { items?: [ids], categories?: [ids], modifierLists?: [ids] } | First to last; anything not named keeps its place after. |
POST /menu/sold-out?venue=<id> | { objectId, soldOut, duration?, until?, reason? } | An item, a size or a modifier, at one location. duration: today (until 5 am), 4h or indefinite (the default). |
GET /menu/sold-out?venueId=<id> | — | Everything sold out now, with when it comes back (every location without venueId). |
PUT /menu/items/{id}/locations/{venueId} | { priceCents?, sizes?: [{ id, priceCents }], absent?, soldOut?, duration? } | This location's price (null = the base price), not sold here (absent: true), sold out. |
GET /menu/items/{id}/locations | — | The item at each location. |
PUT /menu/modifiers/{id}/locations/{venueId} | { soldOut, duration? } | A modifier choice (e.g. Oat milk) sold out at one location. |
Errors are { "error": "…" } with 400 (bad input), 404 (no such thing), 409 (menu is read from Square), 403 (outside the key's scope).
A worked example
KEY=svk_…
BASE=https://app.servatto.com/api/office
# 1. read the menu, find the category
curl -s -H "Authorization: Bearer $KEY" $BASE/menu | jq '.menu.categories[] | {id, name}'
# 2. add an item with two sizes and the Milk modifier set
curl -s -X POST -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' $BASE/menu/items -d '{
"name": "Iced Latte", "categoryIds": ["<coffee-id>"],
"variations": [{"name": "Regular", "priceCents": 550}, {"name": "Large", "priceCents": 650}],
"modifierLists": [{"id": "<milk-id>", "minSelected": 1}]
}'
# 3. sold out at one location for today
curl -s -X POST -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' "$BASE/menu/sold-out?venue=<venue-id>" -d '{"objectId":"<item-id>","soldOut":true}'
Every change is in the audit log under the key's name, and every till at the venue picks it up within a minute.