Developers

Your kitchen, over HTTPS.

Everything Chefrys One knows about a kitchen — recipes, costs, stock, counts, orders, sales, variance — is readable with an API key, and the moments that matter arrive as signed webhooks. No SDK to install; any language that can make an HTTPS request will do.

Authentication

A manager or owner creates a key under Integrations → API & webhooks in the sidebar. The key is shown once. Send it as a bearer token:

curl https://www.chefrys.com/recipes \
  -H "Authorization: Bearer cos_0123456789abcdef…"

A key acts for one kitchen with a chef's access: every operational read and write, but never people, billing, exports or the audit log. Revoke it at any time from the same screen; every request it made is in the kitchen's activity log under the key's name.

Endpoints most integrations use

Method · pathWhat it returns or does
GET /recipesEvery recipe with cost, price, food-cost % and completeness.
GET /recipes/{id}/rollupOne recipe in full: ingredients in grams, sub-recipes, steps, allergens, batch and portion cost.
GET /ingredientsThe ingredient library with prices per gram, yields and categories.
GET /suppliersSupplier items: pack, pack weight (pack_g, grams), pack price, code. pack_g 0 is an item sold by the each (gloves, foil pans, a rental): it has a pack price but no price per gram, so leave it out of any per-gram maths.
GET /inventory/fullStock on hand with values and pars.
GET /count/sheet?date=YYYY-MM-DD · POST /countThe count sheet in shelf units, and saving counts ({date, lines:[{id, entries:[{key, qty}]}]}).
GET /variance?from=&to=Period variance between two counts: opening + purchases − closing vs ideal, in grams and dollars.
GET /menu-engineering?range=30&by=categoryStars, plowhorses, puzzles and dogs with margin and popularity.
GET /sales · GET /sales/pendingSales history, and POS names not yet linked to a recipe.
GET /order · GET /orders · POST /orders/placeThe suggested order sheet, placed orders, and placing one.
GET /invoicesInvoices on file with their lines.
GET /price-watchSupplier price moves of the last 30 days and the dishes they hit.
GET /prep · GET /planToday's prep list and the production plan.
POST /messageSend a document (multipart files[]) or text for reading — the same path as the composer; spends the kitchen's tokens. With files the reply comes back at once with a batch_id; poll GET /message/batch/{id} for the results as they land (each delivered once) until finished.

Responses are JSON. Dates are YYYY-MM-DD; weights are grams; money is US dollars. Errors carry {"detail": "…"} with the usual status codes; a key that cannot do something gets 403. While there is nothing to show yet (no plan, or no counts), these three return status 404: GET /prep, /order and /variance. Add empty=1 to the query to get 200 {"empty": true, "detail": "…"} instead.

Webhooks

Register an HTTPS URL under the same screen and pick the events. Each delivery is a JSON POST:

{
  "event": "count.finalized",
  "kitchen": "the_bistro",
  "ts": 1789300000.123,
  "data": {"date": "2026-09-13", "items": 212, "value": 18342.10, "by": "marco"}
}
EventWhendata
count.finalizedA count is locked for a date.date, items, value, by
stock.lowItems fell below par (after a count, a delivery or the day's sales); the suggested orders that were drafted.trigger, items[{id, ingredient, on_hand_g, par_g, short_g, supplier, new}], suggested_orders[{id, supplier, lines, total}]
order.calledChefrys One rang a supplier and read an order aloud.id, supplier, to, lines, by, sid
order.confirmedThe supplier confirmed an order: clicked Confirm in the email, replied by email, or pressed 1 on the call.id, supplier, via (link | reply | call), note
order.problemAn order needs a person: the email bounced, the supplier said they can't fill it, or their reply reads like a problem.id, supplier, problem (bounced | declined | reply), detail
order.call_endedHow that call went (completed, busy, no-answer…) and what the supplier pressed.id, supplier, status, seconds, answered_by, result (confirmed | declined)
order.placedOrder sheet lines become purchase orders.orders: [{id, supplier, lines, total}]
order.sentA purchase order is emailed to the supplier.id, supplier, to, lines
price.alertA supplier price moved enough to change plate costs.ingredient, pct, old_pack_price, new_pack_price, dishes
invoice.receivedAn invoice was read and its purchases booked.supplier, number, date, total, lines (food lines), supplies, supplies_cost (non-food lines, kept out of food cost)
sales.syncedSales arrived from a connected POS.source, days, rows, matched, unmatched
webhook.testYou pressed Test.hello

Verify every delivery. The header X-ChefOS-Signature is sha256= followed by the hex HMAC-SHA256 of the raw request body, keyed with the webhook's secret (shown once when you create it). Reject anything that does not match. Respond with a 2xx within eight seconds; Chefrys One retries once, and pauses a webhook after twenty consecutive failures.

# Python
import hmac, hashlib
expected = "sha256=" + hmac.new(SECRET.encode(), request_body_bytes, hashlib.sha256).hexdigest()
ok = hmac.compare_digest(expected, request.headers["X-ChefOS-Signature"])

Limits and fair use

Keys spend the kitchen's tokens, and share its daily cap, when they send documents or questions. Plain reads are free. Be gentle: a few requests a second is fine; a scraper is not. Questions: chefrys.com/talk.