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

CallGives
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/previewExactly 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

CallBodyNotes
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 aboveOnly 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.