Send orders to a kitchen screen

The key needs See orders and sales and Update orders (orders:read and orders:write). Give each branch's screen a key limited to that branch, so it only sees that branch's orders.

1. Poll for open orders

curl "https://serveu-psi.vercel.app/api/v1/orders?status=new,preparing,ready&limit=100" -H "Authorization: Bearer serveu_live_XXXX…"
  • Poll every 5–10 seconds. That is 6–12 of the key's 60 requests per minute, leaving room for the buttons.
  • A page holds up to 100 orders; if next_cursor isn't null, fetch the next page too.
  • Prefer webhooks, which tell you about a new order the moment it arrives: see the section below.

2. Show each order

  • number (e.g. A-042), type and status.
  • items: quantity, name, size, options and the line's note; plus the order's own note.
  • Where it goes: table.label for dine-in, or car.plate and car.description for orders from the car.

3. Buttons

Each button sends one action. Use <order id>-<action>-<order.updated_at> as the Idempotency-Key, with the updated_at of the order the screen is showing:

curl -X POST https://serveu-psi.vercel.app/api/v1/orders/<order id>/actions -H "Authorization: Bearer serveu_live_XXXX…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: <order id>-start-2026-11-08T09:41:12.482Z" \
  -d '{"action":"start"}'
  • A retry of the same tap (or a second screen tapping the same button) sends the same key and gets the same answer.
  • An error answer is stored with its key for 24 hours. Because updated_at moves whenever the order changes, a tap after the order changed (say, it was marked paid after serve answered payment_required) uses a new key and is tried again, instead of replaying the old error.
  • start: new → preparing. ready: → ready. serve: → served, and the customer gets their stamps or points.
  • Two screens tapping the same button is fine: repeating an action that already happened answers replayed: true.
  • reject needs a reason, one of item_unavailable, closing_soon, suspicious, cancelled_by_customer, other; without one the answer is 400 invalid_request. Rejecting an order paid online refunds it in full.
  • If the business wants counter orders paid first, serve answers 409 payment_required until the order is paid — and when it wants payment before preparing, so does start. Mark a counter payment with {"action":"mark_paid","method":"cash"} (cash, card, apple_pay or other), then send the action again.
  • An action the order's status doesn't allow answers 409 order_not_actionable: refresh the screen.
The answer to every action is the updated order, so the screen can redraw without waiting for the next poll. See Errors for every code.

4. Get orders pushed instead of polling

Add a webhook endpoint for the screen's URL with the events order.created and order.status_changed: the owner does it in Settings → Developers → Webhooks, or your system does it with a key that has webhooks:manage (see Webhooks). Verify every request's signature first.

  • On order.created, show the ticket from data.object: its items, table or car and note, as in step 2. It arrives when the order reaches the counter (a pay-now order, once it's paid).
  • On order.status_changed, move or remove the ticket (data.object.status is the new status, data.previous_status the one before). Ignore an event whose data.object.updated_at isn't newer than the one the screen shows: events can arrive late, twice or out of order, and the screen's own taps come back this way too.
  • The buttons don't change: POST /api/v1/orders/{id}/actions with start, ready or serve and the Idempotency-Key from step 3.
  • Keep a slow poll — say once a minute, with updated_since (see Pagination, filtering & sync) — as a safety net for a missed delivery.
  • A screen for one branch can use an endpoint limited to that branch: the owner picks the branch in the dashboard, or a key for all branches sends branch_id (a key limited to one branch can't manage webhooks).

Next: Connect Zapier or n8n →