API reference

The API the bakery's console calls, and a visitor's own client can too.

Every console action has a call here. Every bakery is a private sandbox of synthetic data, wiped at the next 10:00 UTC. From a browser, the __Host-session cookie carries the session; from anywhere else, the sandbox's API token goes in Authorization: Bearer (signed-in sandboxes only; issue it in the console or with POST /v1/sandboxes/{id}/token). A request with a token and no cookie needs no Origin. Every POST carries an Idempotency-Key, a ULID; the same key again gets the first answer with Idempotent-Replayed: true. Every error is application/problem+json (RFC 9457), its type https://bakery.ihaveasegway.com/problems/<slug>. Money is integer US cents; business times carry the bakery's Mountain offset, wall times are UTC with Z. Generated from crates/contracts: the route table and the schemas.

The document itself, OpenAPI 3.1, is at /docs/api/openapi.json. This page is built from it, and it is built from the same route table and schemas as the console’s types, so the three cannot disagree.

Servers

Using your token

Sign in, open the console and press Issue token. The token is shown once. Send it as Authorization: Bearer <your token> on any route marked “token accepted”, with no cookie and no Origin. A token lasts 24 hours, dies with the sandbox and stops the moment you rotate it. A token in a query string, or a token beside a cookie, is refused with 400 token; a rotated or expired one is 401token. Every POST carries an Idempotency-Key, a ULID.

Index

72 operations, by area.

Service

Sign-in

Sandbox

Menu

Orders

Kitchen

Simulated traffic

Payments

Schedule

Catering

CRM, inbox and activity

Dispatch

API token

The kitchen socket · Schemas

Operations

Service

GET /v1/health

The Worker answers, and which build it is

Anyone.

Operation health · added in PR 2. Public: no session needed. No token: the cookie or nothing.

Answers
The answer, HealthResponse
NameTypeRequiredLimitsMeaning
commitstringyesThe commit the bundle was built from; dev on a laptop.
statusHealthStatusyesone of ok
versionstringyesThe API's version.
curl
curl 'https://api.bakery.ihaveasegway.com/v1/health'

GET /v1/status

Today's admission state, the status page's counters and the breaker's state, from the Control object

Anyone.

Operation status · added in PR 2. Public: no session needed. No token: the cookie or nothing.

Answers
  • 200 StatusResponse: today's sandboxes, guests and demo accounts with their caps, the last snapshot of the kept Analytics Engine queries with counters_as_of and counters_state (live, stale or unavailable), the breaker's state (unconfigured, armed, read-only or off) with its last run, and the flag
The answer, StatusResponse
NameTypeRequiredLimitsMeaning
breakerBreakerStatusyesThe 15-minute cron breaker's state and its last run, kept in Control.
countersStatusCounters or nullyesThe last snapshot of the kept queries; null until the first refresh succeeds.
counters_as_ofstring or nullyesWhen that snapshot was taken, an RFC 3339 UTC time such as 2026-11-06T10:15:02.114Z; null with counters.
counters_stateCountersStateyesone of live, stale, unavailableWhether the counters are fresh: live, stale or unavailable.
daystringyesToday's UTC date, YYYY-MM-DD: the key of the daily ledger.
demo_account_capintegeryesat least 0The most demo accounts created in one UTC day.
demo_accounts_todayintegeryesat least 0Demo accounts created today.
flagFlagyesone of live, read-only, offThe demo's flag.
guest_capintegeryesat least 0The guests' share of the day's sandboxes.
guests_todayintegeryesat least 0Guest sandboxes admitted today.
sandbox_capintegeryesat least 0The most sandboxes admitted in one UTC day.
sandboxes_todayintegeryesat least 0Sandboxes admitted today, guests' included.
curl
curl 'https://api.bakery.ihaveasegway.com/v1/status'

Sign-in

POST /v1/auth/demo

Create a demo account behind Turnstile; the username is shown once

Anyone; a session, if present, is read but not required. Carries an Idempotency-Key.

Operation demo-signup · added in PR 3. Public: no session needed. No token: the cookie or nothing.

Parameters
NameInRequiredTypeMeaning
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Request body, DemoSignupRequest
NameTypeRequiredLimitsMeaning
passwordstringyes12 to 128 characters, no composition rules.
turnstile_tokenstringyes
Answers
  • 201 DemoSignupResponse: the generated username, and the cookie
  • 400 Problem (problems: idempotency-key, body): idempotency-key: no Idempotency-Key, or not a ULID; body: a body that is not the contract's JSON
  • 403 Problem (problems: turnstile, origin): turnstile: Turnstile refused the token; origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site
  • 422 Problem (problems: password, idempotency-key): password: 12 to 128 characters; idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, with Retry-After
  • 503 Problem (problem: demo-cap): demo-cap: the day's demo accounts are used up
The answer, DemoSignupResponse
NameTypeRequiredLimitsMeaning
usernamestringyesbaker- and four characters, such as baker-7f3k.
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/auth/demo' \
  -H 'Idempotency-Key: <a new ULID>' \
  -H 'Content-Type: application/json' \
  -d '{"password":"<string>","turnstile_token":"<string>"}'

POST /v1/auth/demo/signin

Sign in to a demo account

Anyone; a session, if present, is read but not required. Carries an Idempotency-Key.

Operation demo-signin · added in PR 3. Public: no session needed. No token: the cookie or nothing.

Parameters
NameInRequiredTypeMeaning
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Request body, DemoSigninRequest
NameTypeRequiredLimitsMeaning
passwordstringyes
usernamestringyes
Answers
  • 200 SessionResponse: the session, and the cookie
  • 400 Problem (problems: idempotency-key, body): idempotency-key: no Idempotency-Key, or not a ULID; body: a body that is not the contract's JSON
  • 401 Problem (problem: credentials): credentials: no such username, or the wrong password
  • 403 Problem (problem: origin): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site
  • 422 Problem (problem: idempotency-key): idempotency-key: the same key again with a different body
  • 429 Problem (problems: locked, rate-limit): locked: 10 failures in 15 minutes; with Retry-After; rate-limit: the session's or IP hash's limit, with Retry-After
The answer, SessionResponse
NameTypeRequiredLimitsMeaning
accountstring or nullnoThe account id, 64 hex characters; null for a guest.
expires_atstringyesWhen the cookie stops being accepted, a wall time such as 2026-11-05T12:00:00.000Z.
kindSessionKindyesone of guest, google, github, demo
sandboxstring or nullnoThe current sandbox's ULID, if any.
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/auth/demo/signin' \
  -H 'Idempotency-Key: <a new ULID>' \
  -H 'Content-Type: application/json' \
  -d '{"username":"<string>","password":"<string>"}'

GET /v1/auth/github/callback

GitHub's redirect back: the numeric user id is all it keeps

Anyone.

Operation github-callback · added in PR 3. Public: no session needed. No token: the cookie or nothing.

Parameters
NameInRequiredTypeMeaning
codequerynostringGitHub's authorization code
statequerynostringthe value __Host-oauth holds
Answers
  • 303: to the storefront's path, setting __Host-session; to /signin/?error=state, exchange, token, provider or oauth
curl
curl 'https://api.bakery.ihaveasegway.com/v1/auth/github/callback'

GET /v1/auth/github/start

Start a GitHub sign-in

Anyone.

Operation github-start · added in PR 3. Public: no session needed. No token: the cookie or nothing.

Parameters
NameInRequiredTypeMeaning
returnquerynostringthe storefront path to come back to, such as /app/
Answers
  • 302: to GitHub with no scope parameter; sets __Host-oauth
curl
curl 'https://api.bakery.ihaveasegway.com/v1/auth/github/start'

GET /v1/auth/google/callback

Google's redirect back: the `__Host-oauth` cookie and `state` are the check

Anyone.

Operation google-callback · added in PR 3. Public: no session needed. No token: the cookie or nothing.

Parameters
NameInRequiredTypeMeaning
codequerynostringGoogle's authorization code
statequerynostringthe value __Host-oauth holds
Answers
  • 303: to the storefront's path, setting __Host-session; to /signin/?error=state, exchange, token, provider or oauth
curl
curl 'https://api.bakery.ihaveasegway.com/v1/auth/google/callback'

GET /v1/auth/google/start

Start a Google sign-in

Anyone.

Operation google-start · added in PR 3. Public: no session needed. No token: the cookie or nothing.

Parameters
NameInRequiredTypeMeaning
returnquerynostringthe storefront path to come back to, such as /app/
Answers
  • 302: to Google with scope=openid alone; sets __Host-oauth
curl
curl 'https://api.bakery.ihaveasegway.com/v1/auth/google/start'

POST /v1/auth/signout

Sign out: the cookie is cleared and an account's session id revoked

Any session, a guest's included. Carries an Idempotency-Key.

Operation signout · added in PR 3. Any session, a guest’s included. No token: the cookie or nothing.

Parameters
NameInRequiredTypeMeaning
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Answers
  • 204: the cookie cleared
  • 400 Problem (problems: idempotency-key, token): idempotency-key: no Idempotency-Key, or not a ULID; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: an API token: this route takes the session cookie only (cookie-only); session: no session, or one that does not verify
  • 403 Problem (problem: origin): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site
  • 422 Problem (problem: idempotency-key): idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, with Retry-After

This route takes the console’s cookie only, so a token cannot call it: a token never mints its successor, and a browser session is the only way to sign in.

GET /v1/session

What the cookie says, read without an object call

Any session, a guest's included.

Operation session · added in PR 3. Any session, a guest’s included. No token: the cookie or nothing.

Answers
  • 200 SessionResponse: the cookie, decoded
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: an API token: this route takes the session cookie only (cookie-only); session: no session, or one that does not verify
The answer, SessionResponse
NameTypeRequiredLimitsMeaning
accountstring or nullnoThe account id, 64 hex characters; null for a guest.
expires_atstringyesWhen the cookie stops being accepted, a wall time such as 2026-11-05T12:00:00.000Z.
kindSessionKindyesone of guest, google, github, demo
sandboxstring or nullnoThe current sandbox's ULID, if any.

This route takes the console’s cookie only, so a token cannot call it: a token never mints its successor, and a browser session is the only way to sign in.

Sandbox

POST /v1/sandboxes

A sandbox: Turnstile for a guest, admitted by Control, seeded

Anyone; a session, if present, is read but not required. Carries an Idempotency-Key.

Operation create-sandbox · added in PR 3. Public: no session needed. No token: the cookie or nothing.

Parameters
NameInRequiredTypeMeaning
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Request body, CreateSandboxRequest
NameTypeRequiredLimitsMeaning
replacebooleanno
turnstile_tokenstring or nullno
Answers
  • 200 SandboxResponse: an account's live sandbox, unless replace
  • 201 SandboxResponse: a new sandbox; the cookie is re-issued with sbx
  • 400 Problem (problems: idempotency-key, body): idempotency-key: no Idempotency-Key, or not a ULID; body: a body that is not the contract's JSON
  • 403 Problem (problems: turnstile, origin): turnstile: a guest without a good Turnstile token; origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site
  • 422 Problem (problem: idempotency-key): idempotency-key: the same key again with a different body
  • 429 Problem (problems: rate-limit, account-sandbox-cap): rate-limit: this IP hash's creations this minute; account-sandbox-cap: 3 sandboxes an account a day
  • 503 Problem (problems: sandbox-cap, guest-cap, read-only, off): sandbox-cap: 300 sandboxes a UTC day; guest-cap: 100 guest sandboxes a UTC day; read-only: the demo's flag is read-only; off: the demo's flag is off
The answer, SandboxResponse
NameTypeRequiredLimitsMeaning
bornstringyesA wall time.
childrenintegeryesat least 0Registered child objects: the Catalog, the Kitchen and the 12 seeded orders once seeding is done, then one more for each order placed.
clock_offset_sintegeryesBusiness time minus wall time, in seconds (docs/architecture.md, "Time travel").
clock_vintegeryesat least 0Bumped by every clock advance.
faultsFaultsyes
idstringyesThe sandbox's ULID.
kindSandboxKindyesone of guest, account
personastringyesThe visitor's customer persona: the seed's first customer's ULID. Orders the visitor places are this customer's.
persona_addressPersonaAddressyesThe persona's synthetic address, from the seed: the Payment Element's billing address, so the visitor types none (ADR 0007, decision 6).
persona_namestringyesThe persona's synthetic display name, from the seed: the Payment Element's billing name.
seedintegeryesat least 0The seed the sandbox was generated from. A JSON number: JavaScript reads it exactly only below 2^53, so the router mints seeds in that range.
stateSandboxStateyes
token_vintegeryesat least 0Bumped by every API token rotation.
wipe_atstringyesThe first 10:00 UTC after born, a wall time.
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes' \
  -H 'Idempotency-Key: <a new ULID>' \
  -H 'Content-Type: application/json' \
  -d '{}'

DELETE /v1/sandboxes/{id}

Wipe now, finished in the request

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie.

Operation wipe-sandbox · added in PR 3. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Answers
  • 200 WipedResponse: {id, state: wiped}; an account's cookie loses sbx, a guest's is cleared
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problem: origin): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site
  • 404 Problem: another sandbox's id, or one naming nothing
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
The answer, WipedResponse
NameTypeRequiredLimitsMeaning
idstringyes
stateSandboxStateyesAlways wiped.
curl
curl -X DELETE 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>' \
  -H 'Authorization: Bearer <your token>'

GET /v1/sandboxes/{id}

The sandbox's state

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie.

Operation get-sandbox · added in PR 3. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Answers
  • 200 SandboxResponse: the Sandbox's state
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 404 Problem: another sandbox's id, or one naming nothing
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: with a token, the token's limit: every request it makes counts, with Retry-After
The answer, SandboxResponse
NameTypeRequiredLimitsMeaning
bornstringyesA wall time.
childrenintegeryesat least 0Registered child objects: the Catalog, the Kitchen and the 12 seeded orders once seeding is done, then one more for each order placed.
clock_offset_sintegeryesBusiness time minus wall time, in seconds (docs/architecture.md, "Time travel").
clock_vintegeryesat least 0Bumped by every clock advance.
faultsFaultsyes
idstringyesThe sandbox's ULID.
kindSandboxKindyesone of guest, account
personastringyesThe visitor's customer persona: the seed's first customer's ULID. Orders the visitor places are this customer's.
persona_addressPersonaAddressyesThe persona's synthetic address, from the seed: the Payment Element's billing address, so the visitor types none (ADR 0007, decision 6).
persona_namestringyesThe persona's synthetic display name, from the seed: the Payment Element's billing name.
seedintegeryesat least 0The seed the sandbox was generated from. A JSON number: JavaScript reads it exactly only below 2^53, so the router mints seeds in that range.
stateSandboxStateyes
token_vintegeryesat least 0Bumped by every API token rotation.
wipe_atstringyesThe first 10:00 UTC after born, a wall time.
curl
curl 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>' \
  -H 'Authorization: Bearer <your token>'

POST /v1/sandboxes/{id}/clock

Move business time forward, at most 14 days in all

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation advance-clock · added in PR 4. A signed-in session of this sandbox; a guest gets 403 guest-scope. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Request body, ClockRequest
NameTypeRequiredLimitsMeaning
minutesinteger or nullnoat least 0The compatible older form: minutes to advance, 1 or more.
stepClockStep or nullno15m, 1h, 1d or next-opening.
Answers
  • 200 ClockResponse: the clock, with step; the same key again: the first answer, and nothing moves again
  • 400 Problem (problems: idempotency-key, body, token): idempotency-key: no Idempotency-Key, or not a ULID; body: a body that is not the contract's JSON; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problems: origin, guest-scope): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site; guest-scope: a guest's session: sign in for this
  • 404 Problem: another sandbox's id, or one naming nothing
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problems: clock, idempotency-key): clock: naming offset-cap or not-positive (also for both or neither); idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
The answer, ClockResponse
NameTypeRequiredLimitsMeaning
business_nowstringyesBusiness time now, such as 2026-11-07T09:42:10-07:00, shown at America/Denver's offset at that instant (-06:00 in summer).
clock_offset_sintegeryesSeconds of business time ahead of wall time, 0 to 14 days.
clock_vintegeryesat least 0Counts the advances; a child that has not heard of the latest one is retried.
stepstring or nullnoThe step applied, in its wire form: 15m, 1h, 1d, next-opening, or <n>m for the older minutes form.
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/clock' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>' \
  -H 'Content-Type: application/json' \
  -d '{}'

GET /v1/sandboxes/{id}/dead-letters

The messages the Queue gave up on, oldest first

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie.

Operation list-dead-letters · added in PR 4. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Answers
  • 200: an array of dead letters
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 404 Problem: another sandbox's id, or one naming nothing
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: with a token, the token's limit: every request it makes counts, with Retry-After
curl
curl 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/dead-letters' \
  -H 'Authorization: Bearer <your token>'

POST /v1/sandboxes/{id}/dead-letters/{eventId}/redrive

Send a dead letter again with its own id

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation redrive · added in PR 4. A signed-in session of this sandbox; a guest gets 403 guest-scope. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
eventIdpathyesstringThe dead-lettered event's ULID.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Answers
  • 200 DeadLetter: the dead letter sent again, its row removed
  • 400 Problem (problems: idempotency-key, token): idempotency-key: no Idempotency-Key, or not a ULID; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problems: origin, guest-scope): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site; guest-scope: a guest's session: sign in for this
  • 404 Problem: an id this sandbox has not registered, another's included
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problem: idempotency-key): idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
The answer, DeadLetter
NameTypeRequiredLimitsMeaning
atstringyesWhen it was dead-lettered, a wall time.
errorstringyesThe last delivery error, as a reason code such as Kitchen answered 503.
idstringyesThe event's ULID: the key a redrive sends it again with.
seqintegeryesat least 0
streamstringyesThe producing object's name.
typestringyesThe event type, such as OrderPlaced.
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/dead-letters/<eventId>/redrive' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>'

POST /v1/sandboxes/{id}/faults

Switch the Kitchen's or the Inbox's broken-subscriber fault

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation set-fault · added in PR 3. A signed-in session of this sandbox; a guest gets 403 guest-scope. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Request body, FaultRequest
NameTypeRequiredLimitsMeaning
faultFaultNameyesone of inbox, kitchen
onbooleanyes
Answers
  • 200 FaultsResponse: every switch as it now stands
  • 400 Problem (problems: idempotency-key, body, token): idempotency-key: no Idempotency-Key, or not a ULID; body: a body that is not the contract's JSON; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problems: origin, guest-scope): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site; guest-scope: a guest's session: sign in for this
  • 404 Problem: another sandbox's id, or one naming nothing
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problem: idempotency-key): idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
The answer, FaultsResponse
NameTypeRequiredLimitsMeaning
faultsFaultsyes
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/faults' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>' \
  -H 'Content-Type: application/json' \
  -d '{"fault":"inbox","on":false}'

Menu

GET /v1/sandboxes/{id}/menu

The published menu, with `ETag: "v<n>"`

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie.

Operation get-menu · added in PR 4. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Answers
  • 200 MenuResponse: the Catalog's published menu
  • 304: a matching If-None-Match
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 404 Problem: another sandbox's id, or one naming nothing
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: with a token, the token's limit: every request it makes counts, with Retry-After
  • 503 Problem (problem: seeding): seeding: the sandbox is still being seeded
The answer, MenuResponse
NameTypeRequiredLimitsMeaning
categoriesarray of MenuCategoryyes
versionintegeryesat least 0
curl
curl 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/menu' \
  -H 'Authorization: Bearer <your token>'

PATCH /v1/sandboxes/{id}/menu/items/{slug}

A draft price on every variant, or a pause or resume published at once

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The sandbox's API token may stand in for the cookie.

Operation patch-menu-item · added in PR 4. A signed-in session of this sandbox; a guest gets 403 guest-scope. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
slugpathyesstringA menu item's slug, such as croissant.
Idempotency-KeyheadernostringOptional on a PATCH, which is idempotent by its body; a malformed one is still refused with 400 idempotency-key.
Request body, MenuItemPatch
NameTypeRequiredLimitsMeaning
price_centsinteger or nullno
statusMenuItemStatus or nullno
Answers
  • 200 MenuEditResponse: {version, pending}
  • 400 Problem (problems: body, token): body: a body that is not the contract's JSON; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problems: origin, guest-scope): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site; guest-scope: a guest's session: sign in for this
  • 404 Problem: no such item
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problem: menu): menu: the rule is named in detail
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
The answer, MenuEditResponse
NameTypeRequiredLimitsMeaning
pendingarray of stringyes
versionintegeryesat least 0
curl
curl -X PATCH 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/menu/items/<slug>' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Content-Type: application/json' \
  -d '{}'

POST /v1/sandboxes/{id}/menu/publish

Publish the drafts: `MenuPublished`, the version plus one

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation publish-menu · added in PR 4. A signed-in session of this sandbox; a guest gets 403 guest-scope. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Answers
  • 200 MenuEditResponse: {version, pending}
  • 400 Problem (problems: idempotency-key, token): idempotency-key: no Idempotency-Key, or not a ULID; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problems: origin, guest-scope): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site; guest-scope: a guest's session: sign in for this
  • 404 Problem: another sandbox's id, or one naming nothing
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problems: menu, idempotency-key): menu: no drafts to publish; idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
The answer, MenuEditResponse
NameTypeRequiredLimitsMeaning
pendingarray of stringyes
versionintegeryesat least 0
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/menu/publish' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>'

Orders

POST /v1/sandboxes/{id}/orders

Place an order as the sandbox's persona; its slot is held until it is paid

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation place-order · added in PR 4. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Request body, OrderRequest
NameTypeRequiredLimitsMeaning
kindOrderKindyesone of pickup, delivery
linesarray of OrderLineRequestyes1 to 20 lines.
notestring or nullnoAt most 140 characters, with no email address, phone number, card number or URL.
slotstringyesThe pickup slot's or delivery window's start, a business time such as 2026-11-07T09:45:00-07:00.
zonestring or nullnoz1, z2 or z3 for a delivery; absent for a pickup.
Answers
  • 200 OrderResponse: the same Idempotency-Key again: the first answer, replayed
  • 201 OrderResponse: the order, with Location
  • 400 Problem (problems: idempotency-key, body, token): idempotency-key: no Idempotency-Key, or not a ULID; body: a body that is not the contract's JSON; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problem: origin): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site
  • 404 Problem: another sandbox's id, or one naming nothing
  • 409 Problem (problems: slot-full, item-cap, lead-time, hold-lost): slot-full: with next_free, the next free start that day; item-cap: an item's daily cap is reached; lead-time: naming the hours, such as a 48 h cake for tomorrow; hold-lost: a retry whose place was given back before any order was placed, with no room left; with next_free
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problems: no-such-slot, closed-day, order, idempotency-key): no-such-slot: not a slot or window start, or a day outside the sandbox's; closed-day: the bakery is closed that day; order: naming the rule (note-<class> for the note); idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
  • 503 Problem (problem: seeding): seeding: the sandbox is still being seeded
The answer, OrderResponse
NameTypeRequiredLimitsMeaning
events_seqintegeryesat least 0The seq of the order's latest event.
held_untilstringyesWhen an unpaid order expires, 15 minutes of business time after placed_at.
idstringyesThe order's ULID.
kindOrderKindyesone of pickup, delivery
linesarray of OrderLineResponseyes
menu_versionintegeryesat least 0The published menu version the prices came from.
notestring or nullno
placed_atstringyesWhen the order was placed, a business time.
slotstringyesA business time.
statusOrderStatusyesone of placed, confirmed, expired, cancelled, refund_pending, in_production, ready, picked_up, out_for_delivery, delivered, closed
subtotal_centsintegeryes
tax_centsintegeryes
total_centsintegeryes
zonestring or nullno
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/orders' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>' \
  -H 'Content-Type: application/json' \
  -d '{"kind":"pickup","slot":"<string>","lines":[{"variant":"<string>","qty":1}]}'

GET /v1/sandboxes/{id}/orders/{oid}

One order, once the Sandbox's registry holds it

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie.

Operation get-order · added in PR 4. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
oidpathyesstringAn order's ULID.
Answers
  • 200 OrderResponse: the order
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 404 Problem: an id this sandbox has not registered, another's included
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: with a token, the token's limit: every request it makes counts, with Retry-After
The answer, OrderResponse
NameTypeRequiredLimitsMeaning
events_seqintegeryesat least 0The seq of the order's latest event.
held_untilstringyesWhen an unpaid order expires, 15 minutes of business time after placed_at.
idstringyesThe order's ULID.
kindOrderKindyesone of pickup, delivery
linesarray of OrderLineResponseyes
menu_versionintegeryesat least 0The published menu version the prices came from.
notestring or nullno
placed_atstringyesWhen the order was placed, a business time.
slotstringyesA business time.
statusOrderStatusyesone of placed, confirmed, expired, cancelled, refund_pending, in_production, ready, picked_up, out_for_delivery, delivered, closed
subtotal_centsintegeryes
tax_centsintegeryes
total_centsintegeryes
zonestring or nullno
curl
curl 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/orders/<oid>' \
  -H 'Authorization: Bearer <your token>'

POST /v1/sandboxes/{id}/orders/{oid}/cancel

The customer cancels

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation cancel-order · added in PR 4. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
oidpathyesstringAn order's ULID.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Answers
  • 200 OrderResponse: the order, cancelled
  • 400 Problem (problems: idempotency-key, token): idempotency-key: no Idempotency-Key, or not a ULID; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problem: origin): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site
  • 404 Problem: an id this sandbox has not registered, another's included
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problems: order, idempotency-key): order: naming the rule; idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
The answer, OrderResponse
NameTypeRequiredLimitsMeaning
events_seqintegeryesat least 0The seq of the order's latest event.
held_untilstringyesWhen an unpaid order expires, 15 minutes of business time after placed_at.
idstringyesThe order's ULID.
kindOrderKindyesone of pickup, delivery
linesarray of OrderLineResponseyes
menu_versionintegeryesat least 0The published menu version the prices came from.
notestring or nullno
placed_atstringyesWhen the order was placed, a business time.
slotstringyesA business time.
statusOrderStatusyesone of placed, confirmed, expired, cancelled, refund_pending, in_production, ready, picked_up, out_for_delivery, delivered, closed
subtotal_centsintegeryes
tax_centsintegeryes
total_centsintegeryes
zonestring or nullno
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/orders/<oid>/cancel' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>'

POST /v1/sandboxes/{id}/orders/{oid}/payment

What the browser needs to mount Stripe's Payment Element; only the webhook marks it paid

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation pay · added in PR 5. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
oidpathyesstringAn order's ULID.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Answers
  • 200 PaymentResponse: the payment and its one-time client_secret
  • 400 Problem (problems: idempotency-key, token): idempotency-key: no Idempotency-Key, or not a ULID; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problem: origin): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site
  • 404 Problem: an unknown order
  • 409 Problem (problem: order): order: unless the order is placed
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problem: idempotency-key): idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
  • 503 Problem (problem: stripe-key): stripe-key: the API has no sandbox Stripe key
The answer, PaymentResponse
NameTypeRequiredLimitsMeaning
amount_centsintegeryes
client_secretstringyesStripe's pi_..._secret_..., for <Elements options={{ clientSecret }}>.
paymentstringyesBilling's id for the payment, a ULID.
publishable_keystring or nullnoThe sandbox's pk_test_ key, when the API has one to give; the storefront's own build value otherwise.
statusPaymentStatusyes
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/orders/<oid>/payment' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>'

POST /v1/sandboxes/{id}/orders/{oid}/refund

Staff refund a part or all that is left; Billing refunds at Stripe from the Queue

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation refund · added in PR 5. A signed-in session of this sandbox; a guest gets 403 guest-scope. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
oidpathyesstringAn order's ULID.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Request body, RefundRequest
NameTypeRequiredLimitsMeaning
amount_centsinteger or nullno
reasonRefundReasonyesone of missing_item, quality, goodwill
Answers
  • 200 RefundResponse: the refund, on its way to Stripe; the same Idempotency-Key again: the first answer, replayed
  • 400 Problem (problems: idempotency-key, body, token): idempotency-key: no Idempotency-Key, or not a ULID; body: a body that is not the contract's JSON; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problems: origin, guest-scope): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site; guest-scope: a guest's session: sign in for this
  • 404 Problem: another sandbox's id, or one naming nothing
  • 409 Problem (problem: order): order: no-stripe-payment: a seeded order Stripe never charged
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problems: order, idempotency-key): order: refund-exceeds-paid or out-of-order; idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
  • 503 Problem (problem: stripe-key): stripe-key: the API has no sandbox Stripe key
The answer, RefundResponse
NameTypeRequiredLimitsMeaning
amount_centsintegeryes
refundstringyesThe refund's ULID: the id of the order's OrderRefundRequested event, which Billing keeps the refund under.
statusOrderStatusyesone of placed, confirmed, expired, cancelled, refund_pending, in_production, ready, picked_up, out_for_delivery, delivered, closedThe order's status after the request: unchanged for a part, refund_pending for the whole of an order not yet closed.
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/orders/<oid>/refund' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>' \
  -H 'Content-Type: application/json' \
  -d '{"reason":"missing_item"}'

Kitchen

POST /v1/sandboxes/{id}/heartbeat

The console is open with no socket: the Kitchen keeps the time for the Simulator

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation heartbeat · added in PR 4. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Answers
  • 204: noted
  • 400 Problem (problems: idempotency-key, token): idempotency-key: no Idempotency-Key, or not a ULID; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problem: origin): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site
  • 404 Problem: another sandbox's id
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problem: idempotency-key): idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/heartbeat' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>'

GET /v1/sandboxes/{id}/kitchen/ws

The kitchen board's socket, protocol v1 (`x-websocket`)

A session of sandbox {id}, by the cookie only, with the storefront's exact Origin.

Operation kitchen-socket · added in PR 4. The session cookie only. No token: the cookie or nothing.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Answers
  • 101: the socket; frames in x-websocket
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: an API token: this route takes the session cookie only (cookie-only); session: no session, or one that does not verify
  • 403 Problem (problem: origin): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site
  • 404 Problem: another sandbox's id
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 426 Problem (problem: upgrade): upgrade: without Upgrade: websocket
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, with Retry-After
  • 503 Problem (problem: seeding): seeding: the sandbox is still being seeded

A WebSocket takes the console’s cookie only; see the kitchen socket below.

Simulated traffic

DELETE /v1/sandboxes/{id}/traffic

Stop simulated orders; the unused minutes go back to Control

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The sandbox's API token may stand in for the cookie.

Operation stop-traffic · added in PR 4. A signed-in session of this sandbox; a guest gets 403 guest-scope. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Answers
  • 200 TrafficResponse: the Simulator, stopped
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problems: origin, guest-scope): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site; guest-scope: a guest's session: sign in for this
  • 404 Problem: another sandbox's id, or one naming nothing
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
The answer, TrafficResponse
NameTypeRequiredLimitsMeaning
minutes_leftintegeryes0 to 65535What is left of the day's cap, 30 minutes (docs/cost.md, "Guardrails").
minutes_usedintegeryes0 to 65535Minutes of traffic this sandbox used today, UTC.
ordersintegeryes0 to 65535Orders the simulator placed today.
rateintegeryes0 to 255Orders a minute of the current or last run; 0 before the first start.
runningbooleanyes
curl
curl -X DELETE 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/traffic' \
  -H 'Authorization: Bearer <your token>'

GET /v1/sandboxes/{id}/traffic

The Simulator's state, today's

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie.

Operation get-traffic · added in PR 4. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Answers
  • 200 TrafficResponse: {running, rate, minutes_used, minutes_left, orders}
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 404 Problem: another sandbox's id, or one naming nothing
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: with a token, the token's limit: every request it makes counts, with Retry-After
The answer, TrafficResponse
NameTypeRequiredLimitsMeaning
minutes_leftintegeryes0 to 65535What is left of the day's cap, 30 minutes (docs/cost.md, "Guardrails").
minutes_usedintegeryes0 to 65535Minutes of traffic this sandbox used today, UTC.
ordersintegeryes0 to 65535Orders the simulator placed today.
rateintegeryes0 to 255Orders a minute of the current or last run; 0 before the first start.
runningbooleanyes
curl
curl 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/traffic' \
  -H 'Authorization: Bearer <your token>'

POST /v1/sandboxes/{id}/traffic

Start simulated orders, 1 to 6 a minute, within today's 30 minutes

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation start-traffic · added in PR 4. A signed-in session of this sandbox; a guest gets 403 guest-scope. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Request body, TrafficRequest
NameTypeRequiredLimitsMeaning
rateintegeryes0 to 2551 to 6.
Answers
  • 200 TrafficResponse: the Simulator, running; the same Idempotency-Key again: the first answer, replayed
  • 400 Problem (problems: idempotency-key, body, token): idempotency-key: no Idempotency-Key, or not a ULID; body: a body that is not the contract's JSON; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problems: origin, guest-scope): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site; guest-scope: a guest's session: sign in for this
  • 404 Problem: another sandbox's id, or one naming nothing
  • 409 Problem (problem: traffic-running): traffic-running: a run is already going
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problems: traffic, idempotency-key): traffic: naming the rule; idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
  • 503 Problem (problem: traffic-cap): traffic-cap: today's minutes are used; with Retry-After
The answer, TrafficResponse
NameTypeRequiredLimitsMeaning
minutes_leftintegeryes0 to 65535What is left of the day's cap, 30 minutes (docs/cost.md, "Guardrails").
minutes_usedintegeryes0 to 65535Minutes of traffic this sandbox used today, UTC.
ordersintegeryes0 to 65535Orders the simulator placed today.
rateintegeryes0 to 255Orders a minute of the current or last run; 0 before the first start.
runningbooleanyes
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/traffic' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>' \
  -H 'Content-Type: application/json' \
  -d '{"rate":1}'

Payments

GET /v1/sandboxes/{id}/billing/ledger

Every payment and refund Billing recorded, and the totals

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie.

Operation ledger · added in PR 5. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Answers
  • 200 LedgerResponse: the ledger
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 404 Problem: another sandbox's id, or one naming nothing
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: with a token, the token's limit: every request it makes counts, with Retry-After
  • 503 Problem (problem: stripe-key): stripe-key: the API has no sandbox Stripe key
The answer, LedgerResponse
NameTypeRequiredLimitsMeaning
entriesarray of LedgerEntryyes
net_centsintegeryespaid_cents - refunded_cents.
paid_centsintegeryes
refunded_centsintegeryesA positive amount.
curl
curl 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/billing/ledger' \
  -H 'Authorization: Bearer <your token>'

POST /v1/stripe/webhook

Stripe's events, checked against `Stripe-Signature`, test mode only; no CORS, Origin, session, key or rate limit

Stripe only: its signature is the check.

Operation stripe-webhook · added in PR 5. Stripe only, by its signature. No token: the cookie or nothing.

Answers
  • 200: an event applied, replayed or ignored, and for a wiped or unknown sandbox
  • 400 Problem (problems: signature, timestamp, body, livemode): signature: the signature does not verify; timestamp: the signature's timestamp is outside the tolerance; body: not a Stripe event; livemode: a live-mode event
  • 405 Problem: OPTIONS, with Allow: POST: no preflight
  • 409 Problem (problem: webhook-later): webhook-later: an event Billing cannot apply yet
  • 503 Problem (problem: stripe-key): stripe-key: the API has no sandbox Stripe key

This route takes the console’s cookie only, so a token cannot call it: a token never mints its successor, and a browser session is the only way to sign in.

Schedule

GET /v1/sandboxes/{id}/schedule/{date}

One bakery day: slots, zones, item caps, catering and the bake list

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie.

Operation get-schedule · added in PR 6. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
datepathyesstringA bakery day, YYYY-MM-DD.
Answers
  • 200 ScheduleResponse: the day
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 404 Problem (problem: no-such-day): no-such-day: a date outside the sandbox's days
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: with a token, the token's limit: every request it makes counts, with Retry-After
  • 503 Problem (problem: seeding): seeding: the sandbox is still being seeded
The answer, ScheduleResponse
NameTypeRequiredLimitsMeaning
bake_listBakeListViewyes
cateringCateringViewyes
datestringyesThe bakery day, YYYY-MM-DD.
itemsarray of ItemCapViewyesThe items with a cap or a use that day, by slug; an item with no entry is uncapped.
menu_versionintegeryesat least 0The menu version the item caps came from; 0 before the first refresh.
openbooleanyesWhether the bakery opens that day (Tuesday to Sunday); a closed day refuses every hold.
slotsarray of SlotViewyesThe 32 pickup slots, 07:00 to 14:45, in time order.
zonesarray of ZoneViewyesThe three delivery zones, z1 to z3.
curl
curl 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/schedule/<date>' \
  -H 'Authorization: Bearer <your token>'

PATCH /v1/sandboxes/{id}/schedule/{date}/capacity

Set one capacity for the day; places already taken stay

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The sandbox's API token may stand in for the cookie.

Operation set-capacity · added in PR 6. A signed-in session of this sandbox; a guest gets 403 guest-scope. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
datepathyesstringA bakery day, YYYY-MM-DD.
Idempotency-KeyheadernostringOptional on a PATCH, which is idempotent by its body; a malformed one is still refused with 400 idempotency-key.
Request body, CapacityRequest
NameTypeRequiredLimitsMeaning
capacityintegeryes0 to 65535The new capacity. Places already taken stay; a cut below them only stops new ones.
targetstringyesWhat to change, in the domain's grammar: slot:HH:MM (a slot start on the quarter hour, such as slot:14:45), zone:z1 (to z3), item:<slug> (such as item:croissant) or catering.
Answers
  • 200 ScheduleResponse: the day
  • 400 Problem (problems: body, token): body: a body that is not the contract's JSON; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problems: origin, guest-scope): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site; guest-scope: a guest's session: sign in for this
  • 404 Problem (problem: no-such-day): no-such-day: a date outside the sandbox's days
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problems: bad-target, unknown-item): bad-target: not slot:HH:MM, zone:z1 to z3, item:<slug> or catering; unknown-item: an item:<slug> the day's published menu does not list
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
The answer, ScheduleResponse
NameTypeRequiredLimitsMeaning
bake_listBakeListViewyes
cateringCateringViewyes
datestringyesThe bakery day, YYYY-MM-DD.
itemsarray of ItemCapViewyesThe items with a cap or a use that day, by slug; an item with no entry is uncapped.
menu_versionintegeryesat least 0The menu version the item caps came from; 0 before the first refresh.
openbooleanyesWhether the bakery opens that day (Tuesday to Sunday); a closed day refuses every hold.
slotsarray of SlotViewyesThe 32 pickup slots, 07:00 to 14:45, in time order.
zonesarray of ZoneViewyesThe three delivery zones, z1 to z3.
curl
curl -X PATCH 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/schedule/<date>/capacity' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Content-Type: application/json' \
  -d '{"target":"<string>","capacity":1}'

Catering

GET /v1/sandboxes/{id}/catering

Every registered catering event, newest inquiry first

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie.

Operation list-catering · added in PR 7. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Answers
  • 200 CateringListResponse: the events, the seeded eight included
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 404 Problem: another sandbox's id, or one naming nothing
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: with a token, the token's limit: every request it makes counts, with Retry-After
The answer, CateringListResponse
NameTypeRequiredLimitsMeaning
eventsarray of CateringResponseyes
curl
curl 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/catering' \
  -H 'Authorization: Bearer <your token>'

POST /v1/sandboxes/{id}/catering

A catering inquiry, as the sandbox's persona

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation create-catering · added in PR 7. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Request body, CateringInquiryRequest
NameTypeRequiredLimitsMeaning
briefstring or nullnoAt most 500 characters, with no email address, phone number, card number or URL.
datestringyesThe event day, YYYY-MM-DD: an open day (Tuesday to Sunday) at least 7 business days after today.
headcountintegeryes0 to 6553510 to 200 people.
packagestringyesA package slug, such as breakfast-spread (pastry-platter, breakfast-spread, lunch-boxes, celebration-cake-and-coffee).
venuestringyesA venue, v1 to v6.
Answers
  • 200 CateringResponse: the same Idempotency-Key again: the first answer, replayed
  • 201 CateringResponse: the inquiry, with Location
  • 400 Problem (problems: idempotency-key, body, token): idempotency-key: no Idempotency-Key, or not a ULID; body: a body that is not the contract's JSON; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problem: origin): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site
  • 404 Problem: another sandbox's id, or one naming nothing
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problems: catering, idempotency-key): catering: detail ends The rule is <slug>.; never echoes the brief; idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
The answer, CateringResponse
NameTypeRequiredLimitsMeaning
balance_centsinteger or nullnototal_cents - deposit_cents.
briefstring or nullnoThe brief, shown back to its own sandbox only, as text.
cateringstringyesThe catering event's ULID.
created_atstringyesThe inquiry's business time.
datestringyesThe event day, YYYY-MM-DD.
deposit_centsinteger or nullno25% of the total, rounded half-up.
deposit_paymentstring or nullnoThe deposit's payment ULID, once paid.
headcountintegeryes0 to 65535
invoicestring or nullnoThe balance invoice's ULID, once issued.
invoice_duestring or nullnoThe balance invoice's due date, YYYY-MM-DD, 7 days after delivery.
packagestringyes
refund_depositboolean or nullnoSet on a cancellation: whether the deposit goes back.
remindersarray of CateringReminderyesThe reminders still to come, earliest first; empty once the event ends.
stateCateringStateyes
total_centsinteger or nullnoFrom the quote on.
updated_atstringyesThe business time of the latest change.
valid_untilstring or nullnoWhen the quote stops being accepted, a business time: 07:00 on the 14th business day after the quote.
venuestringyes
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/catering' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>' \
  -H 'Content-Type: application/json' \
  -d '{"date":"<string>","headcount":1,"package":"<string>","venue":"<string>"}'

GET /v1/sandboxes/{id}/catering/{cid}

One catering event, with the reminders still to come

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie.

Operation get-catering · added in PR 7. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
cidpathyesstringA catering event's, a customer's or a campaign's ULID, as the path says.
Answers
  • 200 CateringResponse: the event
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 404 Problem: an id this sandbox has not registered, another's included
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: with a token, the token's limit: every request it makes counts, with Retry-After
The answer, CateringResponse
NameTypeRequiredLimitsMeaning
balance_centsinteger or nullnototal_cents - deposit_cents.
briefstring or nullnoThe brief, shown back to its own sandbox only, as text.
cateringstringyesThe catering event's ULID.
created_atstringyesThe inquiry's business time.
datestringyesThe event day, YYYY-MM-DD.
deposit_centsinteger or nullno25% of the total, rounded half-up.
deposit_paymentstring or nullnoThe deposit's payment ULID, once paid.
headcountintegeryes0 to 65535
invoicestring or nullnoThe balance invoice's ULID, once issued.
invoice_duestring or nullnoThe balance invoice's due date, YYYY-MM-DD, 7 days after delivery.
packagestringyes
refund_depositboolean or nullnoSet on a cancellation: whether the deposit goes back.
remindersarray of CateringReminderyesThe reminders still to come, earliest first; empty once the event ends.
stateCateringStateyes
total_centsinteger or nullnoFrom the quote on.
updated_atstringyesThe business time of the latest change.
valid_untilstring or nullnoWhen the quote stops being accepted, a business time: 07:00 on the 14th business day after the quote.
venuestringyes
curl
curl 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/catering/<cid>' \
  -H 'Authorization: Bearer <your token>'

POST /v1/sandboxes/{id}/catering/{cid}/accept

Accept the quote: the day's catering place is held while the deposit is paid

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation accept-quote · added in PR 7. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
cidpathyesstringA catering event's, a customer's or a campaign's ULID, as the path says.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Answers
  • 200 CateringResponse: accepted
  • 400 Problem (problems: idempotency-key, token): idempotency-key: no Idempotency-Key, or not a ULID; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problem: origin): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site
  • 404 Problem: another sandbox's id, or one naming nothing
  • 409 Problem (problems: slot-full, catering): slot-full: both of the day's catering places are taken; nothing recorded; catering: out-of-order or quote-expired
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problem: idempotency-key): idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
The answer, CateringResponse
NameTypeRequiredLimitsMeaning
balance_centsinteger or nullnototal_cents - deposit_cents.
briefstring or nullnoThe brief, shown back to its own sandbox only, as text.
cateringstringyesThe catering event's ULID.
created_atstringyesThe inquiry's business time.
datestringyesThe event day, YYYY-MM-DD.
deposit_centsinteger or nullno25% of the total, rounded half-up.
deposit_paymentstring or nullnoThe deposit's payment ULID, once paid.
headcountintegeryes0 to 65535
invoicestring or nullnoThe balance invoice's ULID, once issued.
invoice_duestring or nullnoThe balance invoice's due date, YYYY-MM-DD, 7 days after delivery.
packagestringyes
refund_depositboolean or nullnoSet on a cancellation: whether the deposit goes back.
remindersarray of CateringReminderyesThe reminders still to come, earliest first; empty once the event ends.
stateCateringStateyes
total_centsinteger or nullnoFrom the quote on.
updated_atstringyesThe business time of the latest change.
valid_untilstring or nullnoWhen the quote stops being accepted, a business time: 07:00 on the 14th business day after the quote.
venuestringyes
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/catering/<cid>/accept' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>'

POST /v1/sandboxes/{id}/catering/{cid}/cancel

The customer cancels a confirmed event; more than 7 days out the deposit is refunded

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation cancel-catering · added in PR 7. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
cidpathyesstringA catering event's, a customer's or a campaign's ULID, as the path says.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Request body, CateringCancelRequest
NameTypeRequiredLimitsMeaning
reasonCateringCancelReasonyesone of customer_change, venue_lost, other
Answers
  • 200 CateringResponse: cancelled, with refund_deposit
  • 400 Problem (problems: idempotency-key, body, token): idempotency-key: no Idempotency-Key, or not a ULID; body: a body that is not the contract's JSON; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problem: origin): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site
  • 404 Problem: another sandbox's id, or one naming nothing
  • 409 Problem (problem: catering): catering: out-of-order
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problem: idempotency-key): idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
The answer, CateringResponse
NameTypeRequiredLimitsMeaning
balance_centsinteger or nullnototal_cents - deposit_cents.
briefstring or nullnoThe brief, shown back to its own sandbox only, as text.
cateringstringyesThe catering event's ULID.
created_atstringyesThe inquiry's business time.
datestringyesThe event day, YYYY-MM-DD.
deposit_centsinteger or nullno25% of the total, rounded half-up.
deposit_paymentstring or nullnoThe deposit's payment ULID, once paid.
headcountintegeryes0 to 65535
invoicestring or nullnoThe balance invoice's ULID, once issued.
invoice_duestring or nullnoThe balance invoice's due date, YYYY-MM-DD, 7 days after delivery.
packagestringyes
refund_depositboolean or nullnoSet on a cancellation: whether the deposit goes back.
remindersarray of CateringReminderyesThe reminders still to come, earliest first; empty once the event ends.
stateCateringStateyes
total_centsinteger or nullnoFrom the quote on.
updated_atstringyesThe business time of the latest change.
valid_untilstring or nullnoWhen the quote stops being accepted, a business time: 07:00 on the 14th business day after the quote.
venuestringyes
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/catering/<cid>/cancel' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>' \
  -H 'Content-Type: application/json' \
  -d '{"reason":"customer_change"}'

POST /v1/sandboxes/{id}/catering/{cid}/decline

Staff decline an inquiry

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation decline-catering · added in PR 7. A signed-in session of this sandbox; a guest gets 403 guest-scope. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
cidpathyesstringA catering event's, a customer's or a campaign's ULID, as the path says.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Request body, DeclineRequest
NameTypeRequiredLimitsMeaning
reasonDeclineReasonyesone of too_soon, over_capacity, other
Answers
  • 200 CateringResponse: declined
  • 400 Problem (problems: idempotency-key, body, token): idempotency-key: no Idempotency-Key, or not a ULID; body: a body that is not the contract's JSON; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problems: origin, guest-scope): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site; guest-scope: a guest's session: sign in for this
  • 404 Problem: another sandbox's id, or one naming nothing
  • 409 Problem (problem: catering): catering: past inquiry
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problem: idempotency-key): idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
The answer, CateringResponse
NameTypeRequiredLimitsMeaning
balance_centsinteger or nullnototal_cents - deposit_cents.
briefstring or nullnoThe brief, shown back to its own sandbox only, as text.
cateringstringyesThe catering event's ULID.
created_atstringyesThe inquiry's business time.
datestringyesThe event day, YYYY-MM-DD.
deposit_centsinteger or nullno25% of the total, rounded half-up.
deposit_paymentstring or nullnoThe deposit's payment ULID, once paid.
headcountintegeryes0 to 65535
invoicestring or nullnoThe balance invoice's ULID, once issued.
invoice_duestring or nullnoThe balance invoice's due date, YYYY-MM-DD, 7 days after delivery.
packagestringyes
refund_depositboolean or nullnoSet on a cancellation: whether the deposit goes back.
remindersarray of CateringReminderyesThe reminders still to come, earliest first; empty once the event ends.
stateCateringStateyes
total_centsinteger or nullnoFrom the quote on.
updated_atstringyesThe business time of the latest change.
valid_untilstring or nullnoWhen the quote stops being accepted, a business time: 07:00 on the 14th business day after the quote.
venuestringyes
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/catering/<cid>/decline' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>' \
  -H 'Content-Type: application/json' \
  -d '{"reason":"too_soon"}'

POST /v1/sandboxes/{id}/catering/{cid}/delivered

Staff mark it delivered; Billing raises the balance invoice

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation mark-catering-delivered · added in PR 7. A signed-in session of this sandbox; a guest gets 403 guest-scope. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
cidpathyesstringA catering event's, a customer's or a campaign's ULID, as the path says.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Answers
  • 200 CateringResponse: delivered; poll for invoice
  • 400 Problem (problems: idempotency-key, token): idempotency-key: no Idempotency-Key, or not a ULID; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problems: origin, guest-scope): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site; guest-scope: a guest's session: sign in for this
  • 404 Problem: another sandbox's id, or one naming nothing
  • 409 Problem (problem: catering): catering: out-of-order; too-early before the event day
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problem: idempotency-key): idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
The answer, CateringResponse
NameTypeRequiredLimitsMeaning
balance_centsinteger or nullnototal_cents - deposit_cents.
briefstring or nullnoThe brief, shown back to its own sandbox only, as text.
cateringstringyesThe catering event's ULID.
created_atstringyesThe inquiry's business time.
datestringyesThe event day, YYYY-MM-DD.
deposit_centsinteger or nullno25% of the total, rounded half-up.
deposit_paymentstring or nullnoThe deposit's payment ULID, once paid.
headcountintegeryes0 to 65535
invoicestring or nullnoThe balance invoice's ULID, once issued.
invoice_duestring or nullnoThe balance invoice's due date, YYYY-MM-DD, 7 days after delivery.
packagestringyes
refund_depositboolean or nullnoSet on a cancellation: whether the deposit goes back.
remindersarray of CateringReminderyesThe reminders still to come, earliest first; empty once the event ends.
stateCateringStateyes
total_centsinteger or nullnoFrom the quote on.
updated_atstringyesThe business time of the latest change.
valid_untilstring or nullnoWhen the quote stops being accepted, a business time: 07:00 on the 14th business day after the quote.
venuestringyes
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/catering/<cid>/delivered' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>'

POST /v1/sandboxes/{id}/catering/{cid}/deposit

The deposit's payment, 25% of the quote, as an order's `/payment`

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation catering-deposit · added in PR 7. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
cidpathyesstringA catering event's, a customer's or a campaign's ULID, as the path says.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Answers
  • 200 PaymentResponse: the payment and its one-time client_secret
  • 400 Problem (problems: idempotency-key, token): idempotency-key: no Idempotency-Key, or not a ULID; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problem: origin): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site
  • 404 Problem: another sandbox's id, or one naming nothing
  • 409 Problem (problem: catering): catering: unless accepted
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problem: idempotency-key): idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
  • 503 Problem (problem: stripe-key): stripe-key: the API has no sandbox Stripe key
The answer, PaymentResponse
NameTypeRequiredLimitsMeaning
amount_centsintegeryes
client_secretstringyesStripe's pi_..._secret_..., for <Elements options={{ clientSecret }}>.
paymentstringyesBilling's id for the payment, a ULID.
publishable_keystring or nullnoThe sandbox's pk_test_ key, when the API has one to give; the storefront's own build value otherwise.
statusPaymentStatusyes
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/catering/<cid>/deposit' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>'

POST /v1/sandboxes/{id}/catering/{cid}/pay-balance

Pay the balance invoice on Stripe's test path; the webhook closes the event

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation pay-balance · added in PR 7. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
cidpathyesstringA catering event's, a customer's or a campaign's ULID, as the path says.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Answers
  • 202 CateringResponse: the event as it stands: poll until closed
  • 400 Problem (problems: idempotency-key, token): idempotency-key: no Idempotency-Key, or not a ULID; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problem: origin): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site
  • 404 Problem: another sandbox's id, or one naming nothing
  • 409 Problem (problem: catering): catering: unless invoiced
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problem: idempotency-key): idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
The answer, CateringResponse
NameTypeRequiredLimitsMeaning
balance_centsinteger or nullnototal_cents - deposit_cents.
briefstring or nullnoThe brief, shown back to its own sandbox only, as text.
cateringstringyesThe catering event's ULID.
created_atstringyesThe inquiry's business time.
datestringyesThe event day, YYYY-MM-DD.
deposit_centsinteger or nullno25% of the total, rounded half-up.
deposit_paymentstring or nullnoThe deposit's payment ULID, once paid.
headcountintegeryes0 to 65535
invoicestring or nullnoThe balance invoice's ULID, once issued.
invoice_duestring or nullnoThe balance invoice's due date, YYYY-MM-DD, 7 days after delivery.
packagestringyes
refund_depositboolean or nullnoSet on a cancellation: whether the deposit goes back.
remindersarray of CateringReminderyesThe reminders still to come, earliest first; empty once the event ends.
stateCateringStateyes
total_centsinteger or nullnoFrom the quote on.
updated_atstringyesThe business time of the latest change.
valid_untilstring or nullnoWhen the quote stops being accepted, a business time: 07:00 on the 14th business day after the quote.
venuestringyes
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/catering/<cid>/pay-balance' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>'

POST /v1/sandboxes/{id}/catering/{cid}/quote

Staff quote an inquiry

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation send-quote · added in PR 7. A signed-in session of this sandbox; a guest gets 403 guest-scope. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
cidpathyesstringA catering event's, a customer's or a campaign's ULID, as the path says.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Request body, QuoteRequest
NameTypeRequiredLimitsMeaning
total_centsinteger or nullnoA staff override of the total, in cents, at least 1. Absent: the package's price per head times the headcount. The deposit is 25% of the total, rounded half-up.
Answers
  • 200 CateringResponse: quoted
  • 400 Problem (problems: idempotency-key, body, token): idempotency-key: no Idempotency-Key, or not a ULID; body: a body that is not the contract's JSON; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problems: origin, guest-scope): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site; guest-scope: a guest's session: sign in for this
  • 404 Problem: another sandbox's id, or one naming nothing
  • 409 Problem (problem: catering): catering: out-of-order, or quote-expired once the day before the event has begun
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problems: catering, idempotency-key): catering: quote-total, a total under 1 cent; idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
The answer, CateringResponse
NameTypeRequiredLimitsMeaning
balance_centsinteger or nullnototal_cents - deposit_cents.
briefstring or nullnoThe brief, shown back to its own sandbox only, as text.
cateringstringyesThe catering event's ULID.
created_atstringyesThe inquiry's business time.
datestringyesThe event day, YYYY-MM-DD.
deposit_centsinteger or nullno25% of the total, rounded half-up.
deposit_paymentstring or nullnoThe deposit's payment ULID, once paid.
headcountintegeryes0 to 65535
invoicestring or nullnoThe balance invoice's ULID, once issued.
invoice_duestring or nullnoThe balance invoice's due date, YYYY-MM-DD, 7 days after delivery.
packagestringyes
refund_depositboolean or nullnoSet on a cancellation: whether the deposit goes back.
remindersarray of CateringReminderyesThe reminders still to come, earliest first; empty once the event ends.
stateCateringStateyes
total_centsinteger or nullnoFrom the quote on.
updated_atstringyesThe business time of the latest change.
valid_untilstring or nullnoWhen the quote stops being accepted, a business time: 07:00 on the 14th business day after the quote.
venuestringyes
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/catering/<cid>/quote' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>' \
  -H 'Content-Type: application/json' \
  -d '{}'

POST /v1/sandboxes/{id}/catering/{cid}/staff-cancel

Staff cancel a confirmed event

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation staff-cancel-catering · added in PR 7. A signed-in session of this sandbox; a guest gets 403 guest-scope. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
cidpathyesstringA catering event's, a customer's or a campaign's ULID, as the path says.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Request body, CateringCancelRequest
NameTypeRequiredLimitsMeaning
reasonCateringCancelReasonyesone of customer_change, venue_lost, other
Answers
  • 200 CateringResponse: cancelled
  • 400 Problem (problems: idempotency-key, body, token): idempotency-key: no Idempotency-Key, or not a ULID; body: a body that is not the contract's JSON; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problems: origin, guest-scope): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site; guest-scope: a guest's session: sign in for this
  • 404 Problem: another sandbox's id, or one naming nothing
  • 409 Problem (problem: catering): catering: out of order
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problem: idempotency-key): idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
The answer, CateringResponse
NameTypeRequiredLimitsMeaning
balance_centsinteger or nullnototal_cents - deposit_cents.
briefstring or nullnoThe brief, shown back to its own sandbox only, as text.
cateringstringyesThe catering event's ULID.
created_atstringyesThe inquiry's business time.
datestringyesThe event day, YYYY-MM-DD.
deposit_centsinteger or nullno25% of the total, rounded half-up.
deposit_paymentstring or nullnoThe deposit's payment ULID, once paid.
headcountintegeryes0 to 65535
invoicestring or nullnoThe balance invoice's ULID, once issued.
invoice_duestring or nullnoThe balance invoice's due date, YYYY-MM-DD, 7 days after delivery.
packagestringyes
refund_depositboolean or nullnoSet on a cancellation: whether the deposit goes back.
remindersarray of CateringReminderyesThe reminders still to come, earliest first; empty once the event ends.
stateCateringStateyes
total_centsinteger or nullnoFrom the quote on.
updated_atstringyesThe business time of the latest change.
valid_untilstring or nullnoWhen the quote stops being accepted, a business time: 07:00 on the 14th business day after the quote.
venuestringyes
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/catering/<cid>/staff-cancel' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>' \
  -H 'Content-Type: application/json' \
  -d '{"reason":"customer_change"}'

POST /v1/sandboxes/{id}/catering/{cid}/start-production

Staff start production of a confirmed event

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation start-catering-production · added in PR 7. A signed-in session of this sandbox; a guest gets 403 guest-scope. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
cidpathyesstringA catering event's, a customer's or a campaign's ULID, as the path says.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Answers
  • 200 CateringResponse: in_production
  • 400 Problem (problems: idempotency-key, token): idempotency-key: no Idempotency-Key, or not a ULID; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problems: origin, guest-scope): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site; guest-scope: a guest's session: sign in for this
  • 404 Problem: another sandbox's id, or one naming nothing
  • 409 Problem (problem: catering): catering: out-of-order unless confirmed; too-early before the day before the event
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problem: idempotency-key): idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
The answer, CateringResponse
NameTypeRequiredLimitsMeaning
balance_centsinteger or nullnototal_cents - deposit_cents.
briefstring or nullnoThe brief, shown back to its own sandbox only, as text.
cateringstringyesThe catering event's ULID.
created_atstringyesThe inquiry's business time.
datestringyesThe event day, YYYY-MM-DD.
deposit_centsinteger or nullno25% of the total, rounded half-up.
deposit_paymentstring or nullnoThe deposit's payment ULID, once paid.
headcountintegeryes0 to 65535
invoicestring or nullnoThe balance invoice's ULID, once issued.
invoice_duestring or nullnoThe balance invoice's due date, YYYY-MM-DD, 7 days after delivery.
packagestringyes
refund_depositboolean or nullnoSet on a cancellation: whether the deposit goes back.
remindersarray of CateringReminderyesThe reminders still to come, earliest first; empty once the event ends.
stateCateringStateyes
total_centsinteger or nullnoFrom the quote on.
updated_atstringyesThe business time of the latest change.
valid_untilstring or nullnoWhen the quote stops being accepted, a business time: 07:00 on the 14th business day after the quote.
venuestringyes
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/catering/<cid>/start-production' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>'

CRM, inbox and activity

GET /v1/sandboxes/{id}/activity

The sandbox's logged events, newest first

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie.

Operation get-activity · added in PR 8. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
limitquerynointegerhow many entries
Answers
  • 200 ActivityResponse: the activity feed
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 404 Problem: another sandbox's id, or one naming nothing
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: with a token, the token's limit: every request it makes counts, with Retry-After
The answer, ActivityResponse
NameTypeRequiredLimitsMeaning
entriesarray of ActivityEntryyes
curl
curl 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/activity' \
  -H 'Authorization: Bearer <your token>'

GET /v1/sandboxes/{id}/campaigns

Every campaign, by id

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie.

Operation list-campaigns · added in PR 8. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Answers
  • 200 CampaignListResponse: the campaigns
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 404 Problem: another sandbox's id, or one naming nothing
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: with a token, the token's limit: every request it makes counts, with Retry-After
The answer, CampaignListResponse
NameTypeRequiredLimitsMeaning
campaignsarray of CampaignResponseyes
curl
curl 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/campaigns' \
  -H 'Authorization: Bearer <your token>'

POST /v1/sandboxes/{id}/campaigns

A draft campaign, from the fixed templates

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation create-campaign · added in PR 8. A signed-in session of this sandbox; a guest gets 403 guest-scope. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Request body, CampaignRequest
NameTypeRequiredLimitsMeaning
bodyBodyTemplateyesone of announcement, fresh-bread, cake-orders, seasonal-menu, come-back, catering-invite
segmentSegmentyesone of everyone, cake-last-30-days, delivery-customers, lapsed-60-days, catering-leads
subjectSubjectTemplateyesone of weekend-bake, cake-week, new-season, we-miss-you, catering-season, thank-you
Answers
  • 200 CampaignResponse: the same Idempotency-Key again: the first answer, replayed
  • 201 CampaignResponse: the draft, with Location
  • 400 Problem (problems: idempotency-key, body, token): idempotency-key: no Idempotency-Key, or not a ULID; body: a body that is not the contract's JSON; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problems: origin, guest-scope): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site; guest-scope: a guest's session: sign in for this
  • 404 Problem: another sandbox's id, or one naming nothing
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problem: idempotency-key): idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
The answer, CampaignResponse
NameTypeRequiredLimitsMeaning
bodyBodyTemplateyesone of announcement, fresh-bread, cake-orders, seasonal-menu, come-back, catering-invite
campaignstringyesThe campaign's ULID.
clickedintegeryesat least 0
deliveredintegeryesat least 0Messages the Inbox delivered, opened and clicked, from its events.
openedintegeryesat least 0
recipientsintegeryesat least 0The frozen recipients' count: the segment's members with marketing consent at the send.
segmentSegmentyesone of everyone, cake-last-30-days, delivery-customers, lapsed-60-days, catering-leads
send_atstring or nullnoWhile scheduled, and after the send: the business time it is due or was sent.
stateCampaignStateyesone of draft, scheduled, sent, cancelled
subjectSubjectTemplateyesone of weekend-bake, cake-week, new-season, we-miss-you, catering-season, thank-you
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/campaigns' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>' \
  -H 'Content-Type: application/json' \
  -d '{"subject":"weekend-bake","body":"announcement","segment":"everyone"}'

GET /v1/sandboxes/{id}/campaigns/{cid}

One campaign

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie.

Operation get-campaign · added in PR 8. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
cidpathyesstringA catering event's, a customer's or a campaign's ULID, as the path says.
Answers
  • 200 CampaignResponse: the campaign
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 404 Problem: an unknown campaign
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: with a token, the token's limit: every request it makes counts, with Retry-After
The answer, CampaignResponse
NameTypeRequiredLimitsMeaning
bodyBodyTemplateyesone of announcement, fresh-bread, cake-orders, seasonal-menu, come-back, catering-invite
campaignstringyesThe campaign's ULID.
clickedintegeryesat least 0
deliveredintegeryesat least 0Messages the Inbox delivered, opened and clicked, from its events.
openedintegeryesat least 0
recipientsintegeryesat least 0The frozen recipients' count: the segment's members with marketing consent at the send.
segmentSegmentyesone of everyone, cake-last-30-days, delivery-customers, lapsed-60-days, catering-leads
send_atstring or nullnoWhile scheduled, and after the send: the business time it is due or was sent.
stateCampaignStateyesone of draft, scheduled, sent, cancelled
subjectSubjectTemplateyesone of weekend-bake, cake-week, new-season, we-miss-you, catering-season, thank-you
curl
curl 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/campaigns/<cid>' \
  -H 'Authorization: Bearer <your token>'

PATCH /v1/sandboxes/{id}/campaigns/{cid}

Edit a draft campaign

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The sandbox's API token may stand in for the cookie.

Operation edit-campaign · added in PR 8. A signed-in session of this sandbox; a guest gets 403 guest-scope. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
cidpathyesstringA catering event's, a customer's or a campaign's ULID, as the path says.
Idempotency-KeyheadernostringOptional on a PATCH, which is idempotent by its body; a malformed one is still refused with 400 idempotency-key.
Request body, CampaignRequest
NameTypeRequiredLimitsMeaning
bodyBodyTemplateyesone of announcement, fresh-bread, cake-orders, seasonal-menu, come-back, catering-invite
segmentSegmentyesone of everyone, cake-last-30-days, delivery-customers, lapsed-60-days, catering-leads
subjectSubjectTemplateyesone of weekend-bake, cake-week, new-season, we-miss-you, catering-season, thank-you
Answers
  • 200 CampaignResponse: the draft; the same optional Idempotency-Key again replays it
  • 400 Problem (problems: body, token): body: a body that is not the contract's JSON; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problems: origin, guest-scope): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site; guest-scope: a guest's session: sign in for this
  • 404 Problem: an unknown campaign
  • 409 Problem (problem: campaign): campaign: out-of-order: scheduled, sent or cancelled
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
The answer, CampaignResponse
NameTypeRequiredLimitsMeaning
bodyBodyTemplateyesone of announcement, fresh-bread, cake-orders, seasonal-menu, come-back, catering-invite
campaignstringyesThe campaign's ULID.
clickedintegeryesat least 0
deliveredintegeryesat least 0Messages the Inbox delivered, opened and clicked, from its events.
openedintegeryesat least 0
recipientsintegeryesat least 0The frozen recipients' count: the segment's members with marketing consent at the send.
segmentSegmentyesone of everyone, cake-last-30-days, delivery-customers, lapsed-60-days, catering-leads
send_atstring or nullnoWhile scheduled, and after the send: the business time it is due or was sent.
stateCampaignStateyesone of draft, scheduled, sent, cancelled
subjectSubjectTemplateyesone of weekend-bake, cake-week, new-season, we-miss-you, catering-season, thank-you
curl
curl -X PATCH 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/campaigns/<cid>' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Content-Type: application/json' \
  -d '{"subject":"weekend-bake","body":"announcement","segment":"everyone"}'

POST /v1/sandboxes/{id}/campaigns/{cid}/cancel

A scheduled campaign back to draft, or a draft cancelled

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation cancel-campaign · added in PR 8. A signed-in session of this sandbox; a guest gets 403 guest-scope. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
cidpathyesstringA catering event's, a customer's or a campaign's ULID, as the path says.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Answers
  • 200 CampaignResponse: the campaign
  • 400 Problem (problems: idempotency-key, token): idempotency-key: no Idempotency-Key, or not a ULID; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problems: origin, guest-scope): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site; guest-scope: a guest's session: sign in for this
  • 404 Problem: an unknown campaign
  • 409 Problem (problem: campaign): campaign: out-of-order: already sent
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problem: idempotency-key): idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
The answer, CampaignResponse
NameTypeRequiredLimitsMeaning
bodyBodyTemplateyesone of announcement, fresh-bread, cake-orders, seasonal-menu, come-back, catering-invite
campaignstringyesThe campaign's ULID.
clickedintegeryesat least 0
deliveredintegeryesat least 0Messages the Inbox delivered, opened and clicked, from its events.
openedintegeryesat least 0
recipientsintegeryesat least 0The frozen recipients' count: the segment's members with marketing consent at the send.
segmentSegmentyesone of everyone, cake-last-30-days, delivery-customers, lapsed-60-days, catering-leads
send_atstring or nullnoWhile scheduled, and after the send: the business time it is due or was sent.
stateCampaignStateyesone of draft, scheduled, sent, cancelled
subjectSubjectTemplateyesone of weekend-bake, cake-week, new-season, we-miss-you, catering-season, thank-you
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/campaigns/<cid>/cancel' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>'

POST /v1/sandboxes/{id}/campaigns/{cid}/schedule

Schedule a draft; the send is the object's timer, never a route

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation schedule-campaign · added in PR 8. A signed-in session of this sandbox; a guest gets 403 guest-scope. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
cidpathyesstringA catering event's, a customer's or a campaign's ULID, as the path says.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Request body, ScheduleRequest
NameTypeRequiredLimitsMeaning
send_atstringyesA business time, such as 2026-11-08T09:00:00-07:00.
Answers
  • 200 CampaignResponse: scheduled; the same key again replays it
  • 400 Problem (problems: idempotency-key, body, token): idempotency-key: no Idempotency-Key, or not a ULID; body: a body that is not the contract's JSON; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problems: origin, guest-scope): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site; guest-scope: a guest's session: sign in for this
  • 404 Problem: an unknown campaign
  • 409 Problem (problem: campaign): campaign: out-of-order: not a draft
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problems: campaign, idempotency-key): campaign: schedule-too-soon, its detail the earliest time allowed; idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
The answer, CampaignResponse
NameTypeRequiredLimitsMeaning
bodyBodyTemplateyesone of announcement, fresh-bread, cake-orders, seasonal-menu, come-back, catering-invite
campaignstringyesThe campaign's ULID.
clickedintegeryesat least 0
deliveredintegeryesat least 0Messages the Inbox delivered, opened and clicked, from its events.
openedintegeryesat least 0
recipientsintegeryesat least 0The frozen recipients' count: the segment's members with marketing consent at the send.
segmentSegmentyesone of everyone, cake-last-30-days, delivery-customers, lapsed-60-days, catering-leads
send_atstring or nullnoWhile scheduled, and after the send: the business time it is due or was sent.
stateCampaignStateyesone of draft, scheduled, sent, cancelled
subjectSubjectTemplateyesone of weekend-bake, cake-week, new-season, we-miss-you, catering-season, thank-you
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/campaigns/<cid>/schedule' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>' \
  -H 'Content-Type: application/json' \
  -d '{"send_at":"<string>"}'

GET /v1/sandboxes/{id}/customers

The sandbox's 60 synthetic customers

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie.

Operation list-customers · added in PR 8. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Answers
  • 200 CustomerListResponse: the customers, by id
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 404 Problem: another sandbox's id, or one naming nothing
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: with a token, the token's limit: every request it makes counts, with Retry-After
The answer, CustomerListResponse
NameTypeRequiredLimitsMeaning
customersarray of CustomerResponseyes
curl
curl 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/customers' \
  -H 'Authorization: Bearer <your token>'

GET /v1/sandboxes/{id}/customers/{cid}

One customer as Crm's projection holds it

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie.

Operation get-customer · added in PR 8. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
cidpathyesstringA catering event's, a customer's or a campaign's ULID, as the path says.
Answers
  • 200 CustomerResponse: the customer
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 404 Problem: an unknown customer
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: with a token, the token's limit: every request it makes counts, with Retry-After
The answer, CustomerResponse
NameTypeRequiredLimitsMeaning
cateringintegeryesat least 0Catering inquiries.
customerstringyesThe customer's ULID.
display_namestringyesTwo generic words, such as Drizzle Trowel.
home_zonestringyesz1 to z3.
last_order_atstring or nullnoThe latest such order's slot, a business time.
loyalty_pointsintegeryesLoyalty points: one per whole dollar paid, less refunds; never below zero.
marketingbooleanyesConsent to marketing: a campaign goes only to customers for whom it is true at the send.
ordersintegeryesat least 0Orders paid, or closed in the seed's history, and not cancelled.
placestringyesp1 to p64, a map label: the only address there is.
preferencesarray of stringyesOne to three item slugs.
curl
curl 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/customers/<cid>' \
  -H 'Authorization: Bearer <your token>'

GET /v1/sandboxes/{id}/customers/{cid}/timeline

A customer's events in business-time order

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie.

Operation get-timeline · added in PR 8. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
cidpathyesstringA catering event's, a customer's or a campaign's ULID, as the path says.
Answers
  • 200 TimelineResponse: the timeline; partial while a rebuild runs
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 404 Problem: an unknown customer
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: with a token, the token's limit: every request it makes counts, with Retry-After
The answer, TimelineResponse
NameTypeRequiredLimitsMeaning
customerstringyes
entriesarray of TimelineEntryyes
curl
curl 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/customers/<cid>/timeline' \
  -H 'Authorization: Bearer <your token>'

GET /v1/sandboxes/{id}/inbox

The sandbox's inbox, newest first

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie.

Operation list-inbox · added in PR 8. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
kindquerynostringone message kind, such as receipt
toquerynostringone customer's ULID
limitquerynointegerhow many, 50 when left out
Answers
  • 200 InboxResponse: the messages and the filters' total
  • 400 Problem (problems: filter, token): filter: a filter the inbox refuses; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 404 Problem: another sandbox's id, or one naming nothing
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: with a token, the token's limit: every request it makes counts, with Retry-After
The answer, InboxResponse
NameTypeRequiredLimitsMeaning
messagesarray of MessageResponseyes
totalintegeryesat least 0
curl
curl 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/inbox' \
  -H 'Authorization: Bearer <your token>'

GET /v1/sandboxes/{id}/inbox/{mid}

One message, with its opens and clicks as the Inbox simulated them

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie.

Operation get-message · added in PR 8. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
midpathyesstringA message's ULID.
Answers
  • 200 MessageResponse: the message
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 404 Problem: an unknown message
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: with a token, the token's limit: every request it makes counts, with Retry-After
The answer, MessageResponse
NameTypeRequiredLimitsMeaning
bodystringyes
clicked_atstring or nullno
delivered_atstringyesBusiness times.
kindMessageKindyesone of receipt, ready, on_its_way, refund, declined, acknowledgement, quote, notice, reminder, invoice, campaign
messagestringyesThe message's ULID.
opened_atstring or nullno
refstringyesThe ULID of the order, catering event or campaign it is about.
subjectstringyesFrom a fixed template, filled with the event's own figures; never free text.
tostringyesThe customer's ULID: the only recipient there is.
curl
curl 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/inbox/<mid>' \
  -H 'Authorization: Bearer <your token>'

GET /v1/sandboxes/{id}/leads

The catering lead pipeline, by stage

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie.

Operation get-leads · added in PR 8. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Answers
  • 200 LeadPipelineResponse: every stage, in order
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 404 Problem: another sandbox's id, or one naming nothing
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: with a token, the token's limit: every request it makes counts, with Retry-After
The answer, LeadPipelineResponse
NameTypeRequiredLimitsMeaning
stagesarray of LeadStageViewyes
curl
curl 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/leads' \
  -H 'Authorization: Bearer <your token>'

GET /v1/sandboxes/{id}/rebuild

The rebuild's progress; ask every second while `rebuilding`

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie.

Operation get-rebuild · added in PR 8. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Answers
  • 200 RebuildResponse: the cursor
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 404 Problem: another sandbox's id, or one naming nothing
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: with a token, the token's limit: every request it makes counts, with Retry-After
The answer, RebuildResponse
NameTypeRequiredLimitsMeaning
appliedintegeryesat least 0Log entries replayed so far.
msinteger or nullnoat least 0Once finished: the wall-clock milliseconds the rebuild took.
stateRebuildStateyesone of idle, rebuilding, finished
totalintegeryesat least 0The log's length.
curl
curl 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/rebuild' \
  -H 'Authorization: Bearer <your token>'

POST /v1/sandboxes/{id}/rebuild

Rebuild projections: drop Crm's projections and replay its log

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation start-rebuild · added in PR 8. A signed-in session of this sandbox; a guest gets 403 guest-scope. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Answers
  • 202 RebuildResponse: the rebuild, started
  • 400 Problem (problems: idempotency-key, token): idempotency-key: no Idempotency-Key, or not a ULID; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problems: origin, guest-scope): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site; guest-scope: a guest's session: sign in for this
  • 404 Problem: another sandbox's id, or one naming nothing
  • 409 Problem (problem: rebuild): rebuild: a second start while one is running; other commands still run
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problem: idempotency-key): idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
The answer, RebuildResponse
NameTypeRequiredLimitsMeaning
appliedintegeryesat least 0Log entries replayed so far.
msinteger or nullnoat least 0Once finished: the wall-clock milliseconds the rebuild took.
stateRebuildStateyesone of idle, rebuilding, finished
totalintegeryesat least 0The log's length.
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/rebuild' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>'

GET /v1/sandboxes/{id}/segments

Every segment, with its member count and a sample

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie.

Operation list-segments · added in PR 8. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Answers
  • 200 SegmentListResponse: the five segments, in the fixed order
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 404 Problem: another sandbox's id, or one naming nothing
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: with a token, the token's limit: every request it makes counts, with Retry-After
The answer, SegmentListResponse
NameTypeRequiredLimitsMeaning
segmentsarray of SegmentResponseyes
curl
curl 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/segments' \
  -H 'Authorization: Bearer <your token>'

GET /v1/sandboxes/{id}/segments/{segment}

How many customers a segment holds now

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie.

Operation get-segment · added in PR 8. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
segmentpathyesstringA segment's slug, such as lapsed-60-days.
Answers
  • 200 SegmentResponse: the segment
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 404 Problem: an unknown segment
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: with a token, the token's limit: every request it makes counts, with Retry-After
The answer, SegmentResponse
NameTypeRequiredLimitsMeaning
membersintegeryesat least 0
samplearray of stringyesUp to five member ULIDs, smallest first.
segmentSegmentyesone of everyone, cake-last-30-days, delivery-customers, lapsed-60-days, catering-leads
curl
curl 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/segments/<segment>' \
  -H 'Authorization: Bearer <your token>'

Dispatch

GET /v1/sandboxes/{id}/dispatch/{date}

One bakery day's delivery stops and plans

A session of sandbox {id}, a guest's included. The sandbox's API token may stand in for the cookie.

Operation get-dispatch · added in PR 9. A session of this sandbox, a guest’s included. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
datepathyesstringA bakery day, YYYY-MM-DD.
Answers
  • 200 DispatchResponse: the day
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 404 Problem (problem: no-such-day): no-such-day: a date outside the sandbox's days
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: with a token, the token's limit: every request it makes counts, with Retry-After
  • 503 Problem (problem: seeding): seeding: the sandbox is still being seeded
The answer, DispatchResponse
NameTypeRequiredLimitsMeaning
capacityintegeryesat least 0Units a van carries: the last proposal's, 250 before any.
comparisonComparisonView or nullnoBoth plans' totals, when there are both; null while the browser's solver has not run or cannot (the page then shows the server's plan alone and says why).
datestringyesThe bakery day, YYYY-MM-DD.
plan_vintegeryes0 to 65535The published plan's version, 0 before the first publish.
proposedPlanView or nullnoThe server's plan from the last propose.
publishedPlanView or nullnoThe record: what drivers, the kitchen board and the order status read.
stopsarray of StopViewyesEvery stop the day has seen, removed ones included, by order ULID.
submittedPlanView or nullnoThe browser's plan from the last plan, validated.
vansintegeryes0 to 255The day's vans, 1 to 4: the last proposal's, 4 before any.
curl
curl 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/dispatch/<date>' \
  -H 'Authorization: Bearer <your token>'

POST /v1/sandboxes/{id}/dispatch/{date}/complete

A stop of the published plan is delivered

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation complete-stop · added in PR 9. A signed-in session of this sandbox; a guest gets 403 guest-scope. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
datepathyesstringA bakery day, YYYY-MM-DD.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Request body, CompleteStopRequest
NameTypeRequiredLimitsMeaning
stopintegeryes0 to 65535The stop's number in the published plan, from 1.
Answers
  • 200 DispatchResponse: the day; again for a delivered stop changes nothing
  • 400 Problem (problems: idempotency-key, body, token): idempotency-key: no Idempotency-Key, or not a ULID; body: a body that is not the contract's JSON; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problems: origin, guest-scope): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site; guest-scope: a guest's session: sign in for this
  • 404 Problem (problem: no-such-day): no-such-day: a date outside the sandbox's days
  • 409 Problem (problem: dispatch): dispatch: no-plan, or not-ready (the stop's order is not ready yet)
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problems: dispatch, idempotency-key): dispatch: stop-unknown; idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
  • 503 Problem (problem: seeding): seeding: the sandbox is still being seeded
The answer, DispatchResponse
NameTypeRequiredLimitsMeaning
capacityintegeryesat least 0Units a van carries: the last proposal's, 250 before any.
comparisonComparisonView or nullnoBoth plans' totals, when there are both; null while the browser's solver has not run or cannot (the page then shows the server's plan alone and says why).
datestringyesThe bakery day, YYYY-MM-DD.
plan_vintegeryes0 to 65535The published plan's version, 0 before the first publish.
proposedPlanView or nullnoThe server's plan from the last propose.
publishedPlanView or nullnoThe record: what drivers, the kitchen board and the order status read.
stopsarray of StopViewyesEvery stop the day has seen, removed ones included, by order ULID.
submittedPlanView or nullnoThe browser's plan from the last plan, validated.
vansintegeryes0 to 255The day's vans, 1 to 4: the last proposal's, 4 before any.
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/dispatch/<date>/complete' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>' \
  -H 'Content-Type: application/json' \
  -d '{"stop":1}'

POST /v1/sandboxes/{id}/dispatch/{date}/plan

The plan VROOM made in the browser; the server computes its minutes

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation submit-plan · added in PR 9. A signed-in session of this sandbox; a guest gets 403 guest-scope. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
datepathyesstringA bakery day, YYYY-MM-DD.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Request body, PlanRequest
NameTypeRequiredLimitsMeaning
vansarray of PlanRouteRequestyes
Answers
  • 200 DispatchResponse: the day, with submitted and comparison
  • 400 Problem (problems: idempotency-key, body, token): idempotency-key: no Idempotency-Key, or not a ULID; body: a body that is not the contract's JSON; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problems: origin, guest-scope): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site; guest-scope: a guest's session: sign in for this
  • 404 Problem (problem: no-such-day): no-such-day: a date outside the sandbox's days
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problems: dispatch, idempotency-key): dispatch: route-ends, vans, stop-unknown, stop-twice, over-capacity, stop-missing, window (a stop reached after its delivery window closes) or no-stops; idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
  • 503 Problem (problem: seeding): seeding: the sandbox is still being seeded
The answer, DispatchResponse
NameTypeRequiredLimitsMeaning
capacityintegeryesat least 0Units a van carries: the last proposal's, 250 before any.
comparisonComparisonView or nullnoBoth plans' totals, when there are both; null while the browser's solver has not run or cannot (the page then shows the server's plan alone and says why).
datestringyesThe bakery day, YYYY-MM-DD.
plan_vintegeryes0 to 65535The published plan's version, 0 before the first publish.
proposedPlanView or nullnoThe server's plan from the last propose.
publishedPlanView or nullnoThe record: what drivers, the kitchen board and the order status read.
stopsarray of StopViewyesEvery stop the day has seen, removed ones included, by order ULID.
submittedPlanView or nullnoThe browser's plan from the last plan, validated.
vansintegeryes0 to 255The day's vans, 1 to 4: the last proposal's, 4 before any.
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/dispatch/<date>/plan' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>' \
  -H 'Content-Type: application/json' \
  -d '{"vans":[{"van":1,"stops":["<string>"]}]}'

POST /v1/sandboxes/{id}/dispatch/{date}/propose

The server's savings plan for the confirmed stops

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation propose-dispatch · added in PR 9. A signed-in session of this sandbox; a guest gets 403 guest-scope. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
datepathyesstringA bakery day, YYYY-MM-DD.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Request body, ProposeRequest
NameTypeRequiredLimitsMeaning
capacityintegeryesat least 0Units a van carries, at least 1.
vansintegeryes0 to 2551 to 4.
Answers
  • 200 DispatchResponse: the day, with proposed
  • 400 Problem (problems: idempotency-key, body, token): idempotency-key: no Idempotency-Key, or not a ULID; body: a body that is not the contract's JSON; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problems: origin, guest-scope): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site; guest-scope: a guest's session: sign in for this
  • 404 Problem (problem: no-such-day): no-such-day: a date outside the sandbox's days
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problems: dispatch, idempotency-key): dispatch: vans, capacity, no-stops or place-unknown; idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
  • 503 Problem (problem: seeding): seeding: the sandbox is still being seeded
The answer, DispatchResponse
NameTypeRequiredLimitsMeaning
capacityintegeryesat least 0Units a van carries: the last proposal's, 250 before any.
comparisonComparisonView or nullnoBoth plans' totals, when there are both; null while the browser's solver has not run or cannot (the page then shows the server's plan alone and says why).
datestringyesThe bakery day, YYYY-MM-DD.
plan_vintegeryes0 to 65535The published plan's version, 0 before the first publish.
proposedPlanView or nullnoThe server's plan from the last propose.
publishedPlanView or nullnoThe record: what drivers, the kitchen board and the order status read.
stopsarray of StopViewyesEvery stop the day has seen, removed ones included, by order ULID.
submittedPlanView or nullnoThe browser's plan from the last plan, validated.
vansintegeryes0 to 255The day's vans, 1 to 4: the last proposal's, 4 before any.
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/dispatch/<date>/propose' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>' \
  -H 'Content-Type: application/json' \
  -d '{"vans":1,"capacity":1}'

POST /v1/sandboxes/{id}/dispatch/{date}/publish

Make a stored plan the record

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The sandbox's API token may stand in for the cookie. Carries an Idempotency-Key.

Operation publish-dispatch · added in PR 9. A signed-in session of this sandbox; a guest gets 403 guest-scope. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
datepathyesstringA bakery day, YYYY-MM-DD.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Request body, PublishRequest
NameTypeRequiredLimitsMeaning
planPlanChoiceyesone of proposed, submitted
Answers
  • 200 DispatchResponse: the day, with published; the same Idempotency-Key again: the first answer, replayed
  • 400 Problem (problems: idempotency-key, body, token): idempotency-key: no Idempotency-Key, or not a ULID; body: a body that is not the contract's JSON; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problems: origin, guest-scope): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site; guest-scope: a guest's session: sign in for this
  • 404 Problem (problem: no-such-day): no-such-day: a date outside the sandbox's days
  • 409 Problem (problem: dispatch): dispatch: no-plan, stop-missing, published (a van has left) or the plan's own rules again
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problem: idempotency-key): idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, or with a token the token's, with Retry-After
  • 503 Problem (problem: seeding): seeding: the sandbox is still being seeded
The answer, DispatchResponse
NameTypeRequiredLimitsMeaning
capacityintegeryesat least 0Units a van carries: the last proposal's, 250 before any.
comparisonComparisonView or nullnoBoth plans' totals, when there are both; null while the browser's solver has not run or cannot (the page then shows the server's plan alone and says why).
datestringyesThe bakery day, YYYY-MM-DD.
plan_vintegeryes0 to 65535The published plan's version, 0 before the first publish.
proposedPlanView or nullnoThe server's plan from the last propose.
publishedPlanView or nullnoThe record: what drivers, the kitchen board and the order status read.
stopsarray of StopViewyesEvery stop the day has seen, removed ones included, by order ULID.
submittedPlanView or nullnoThe browser's plan from the last plan, validated.
vansintegeryes0 to 255The day's vans, 1 to 4: the last proposal's, 4 before any.
curl
curl -X POST 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/dispatch/<date>/publish' \
  -H 'Authorization: Bearer <your token>' \
  -H 'Idempotency-Key: <a new ULID>' \
  -H 'Content-Type: application/json' \
  -d '{"plan":"proposed"}'

API token

GET /v1/sandboxes/{id}/token

The token's version and times, and the last issued one masked; never the token

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The sandbox's API token may stand in for the cookie.

Operation get-token · added in PR 10. A signed-in session of this sandbox; a guest gets 403 guest-scope. A token is accepted.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Answers
  • 200 TokenInfoResponse: the token's info
  • 400 Problem (problem: token): token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: a token that is malformed, expired, rotated or not an API token (token, expired, rotated, purpose); session: no session, or one that does not verify
  • 403 Problem (problem: guest-scope): guest-scope: a guest's session: sign in for this
  • 404 Problem: another sandbox's id, or one naming nothing
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 429 Problem (problem: rate-limit): rate-limit: with a token, the token's limit: every request it makes counts, with Retry-After
The answer, TokenInfoResponse
NameTypeRequiredLimitsMeaning
expires_atstring or nullnoWhen that token stops being accepted, a wall time.
issued_atstring or nullnoWhen the last token at this version was issued, a wall time; absent before the first issue and after a rotation until the next.
maskedstring or nullnoThat token's first 6 and last 4 characters around …, such as eyJ2Ij…x7Qk: enough to tell two tokens apart, never enough to use one.
token_vintegeryesat least 0The Sandbox's token version: a token naming an older one is refused.
curl
curl 'https://api.bakery.ihaveasegway.com/v1/sandboxes/<sandbox id>/token' \
  -H 'Authorization: Bearer <your token>'

POST /v1/sandboxes/{id}/token

Issue the sandbox's API token at its current version, or issue it again; shown once

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The cookie only: an API token never mints its successor. Carries an Idempotency-Key.

Operation issue-token · added in PR 10. A signed-in session of this sandbox; a guest gets 403 guest-scope. No token: the cookie or nothing.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Answers
  • 200 TokenResponse: the token, shown once; the same Idempotency-Key again: the first answer, replayed
  • 400 Problem (problems: idempotency-key, token): idempotency-key: no Idempotency-Key, or not a ULID; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: an API token: this route takes the session cookie only (cookie-only); session: no session, or one that does not verify
  • 403 Problem (problems: origin, guest-scope): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site; guest-scope: a guest's session: sign in for this
  • 404 Problem: another sandbox's id, or one naming nothing
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problem: idempotency-key): idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, with Retry-After
The answer, TokenResponse
NameTypeRequiredLimitsMeaning
expires_atstringyes24 h after issued_at, a wall time; the sandbox's wipe ends it sooner.
issued_atstringyesA wall time, such as 2026-11-05T12:00:00.000Z.
tokenstringyesFor Authorization: Bearer <token>: <payload>.<mac>, both base64url, signed like the session cookie. Never in a URL: a token in a query string is refused with 400.
token_vintegeryesat least 0The Sandbox's token version the token names.

This route takes the console’s cookie only, so a token cannot call it: a token never mints its successor, and a browser session is the only way to sign in.

POST /v1/sandboxes/{id}/token/rotate

Rotate: the Sandbox's token version goes up (`ApiTokenRotated`) and every older token stops

A signed-in session of sandbox {id}; a guest gets 403 guest-scope. The cookie only: an API token never mints its successor. Carries an Idempotency-Key.

Operation rotate-token · added in PR 10. A signed-in session of this sandbox; a guest gets 403 guest-scope. No token: the cookie or nothing.

Parameters
NameInRequiredTypeMeaning
idpathyesstringThe sandbox's ULID, from the session or the token.
Idempotency-KeyheaderyesstringA ULID, new for each intent and the same for its retries. The command's result is stored with it: the same key again answers the first result with Idempotent-Replayed: true, and the same key with a different body is 422 idempotency-key.
Answers
  • 200 TokenResponse: the new token, shown once; the same Idempotency-Key again: the first answer, replayed
  • 400 Problem (problems: idempotency-key, token): idempotency-key: no Idempotency-Key, or not a ULID; token: a cookie and a token both (both), or a token in the query string (query)
  • 401 Problem (problems: token, session): token: an API token: this route takes the session cookie only (cookie-only); session: no session, or one that does not verify
  • 403 Problem (problems: origin, guest-scope): origin: a cookie request without the storefront's exact Origin, or Sec-Fetch-Site: cross-site; guest-scope: a guest's session: sign in for this
  • 404 Problem: another sandbox's id, or one naming nothing
  • 410 Problem (problem: wiped): wiped: the sandbox is being wiped or is gone
  • 422 Problem (problem: idempotency-key): idempotency-key: the same key again with a different body
  • 429 Problem (problem: rate-limit): rate-limit: the session's or IP hash's limit, with Retry-After
The answer, TokenResponse
NameTypeRequiredLimitsMeaning
expires_atstringyes24 h after issued_at, a wall time; the sandbox's wipe ends it sooner.
issued_atstringyesA wall time, such as 2026-11-05T12:00:00.000Z.
tokenstringyesFor Authorization: Bearer <token>: <payload>.<mac>, both base64url, signed like the session cookie. Never in a URL: a token in a query string is refused with 400.
token_vintegeryesat least 0The Sandbox's token version the token names.

This route takes the console’s cookie only, so a token cannot call it: a token never mints its successor, and a browser session is the only way to sign in.

The kitchen socket

GET /v1/sandboxes/{id}/kitchen/ws upgrades to a WebSocket (operation kitchen-socket). It takes the session cookie only, with the storefront’s exact Origin.

Frames are JSON text, each with "v": 1. Limits: 4 open sockets a sandbox (a fifth is told bye limit and closed with 1008), frames of at most 4 KB (1009), 10 frames a second a socket (1008), text frames only (1003). The storefront sends no ping and reconnects instead.

FrameDirectionShapeWhen
snapshotserver-to-client{t: "snapshot", v: 1, board: {cards, bake_lists}, seq}on open; `seq` counts the events that changed the board; `bake_lists` is one entry per bakery day from business today on, `[{date, version, lines: [{item, qty, start, catering?}], reserved}]`, `catering: true` on a catering event's line, whose `qty` is its headcount
evtserver-to-client{t: "evt", v: 1, env, bake_list?}each event that changed the board, the envelope whole, the note with it; and the Sandbox's `ClockAdvanced`. A `BakeListChanged`, `SlotReserved` or `SlotReleased` frame also carries `bake_list`, that day as the board holds it after the event, `{date, version, lines: [{item, qty, start, catering?}], reserved}` (since 2026-11-06; `catering` since 2026-11-08): set the day from it, do not adjust a count
presenceserver-to-client{t: "presence", v: 1, sockets}a socket opened or closed
byeserver-to-client{t: "bye", v: 1, reason: "wiped" | "limit"}before the server closes: 1001 at the wipe; 1008, 1009 or 1003 at a limit
cmdclient-to-server{t: "cmd", v: 1, id: <ULID>, order: <ULID>, action: "start_production" | "mark_ready" | "mark_picked_up"}a staff action, sent to the Order with `id` as the idempotency key
ackserver-to-client{t: "ack", v: 1, id}the command was applied; a repeated `id` gets the first answer
nackserver-to-client{t: "nack", v: 1, id, rule}the command was refused, naming the rule; a guest gets `guest-scope`
pingclient-to-server{t: "ping", v: 1}answered `{t: "pong", v: 1}` by the platform's auto-response, which does not wake the object
pongserver-to-client{t: "pong", v: 1}the answer to a `ping`

Schemas

117 types, each once. A type in a table above links here.

ActivityEntry

One line of the activity log.

NameTypeRequiredLimitsMeaning
atstringyes
summarystringyesA short fixed sentence from the event's named fields.
typestringyes

ActivityResponse

GET /v1/sandboxes/{id}/activity: the sandbox's logged events (clock moves, faults, menu and capacity changes, payment intents, orders paid, catering steps, routes, traffic, token rotations, campaigns sent and rebuilds finished), newest first by business time (ties by event id); ?limit= keeps the newest that many (50 when left out, at most 500). Any session of the sandbox reads it.

NameTypeRequiredLimitsMeaning
entriesarray of ActivityEntryyes

BakeLineView

One line of the bake list.

NameTypeRequiredLimitsMeaning
cateringbooleannoTrue for a catering event's line: item is the package's slug and qty the people it feeds, not a count of items. Absent for an order's line.
itemstringyesThe item's slug.
qtyintegeryesat least 0Quantity to bake for this start, variants summed.
startstringyesThe slot or window start the quantity is for; an item ordered for two starts has two lines.

BakeListView

The day's production plan, from the reserved and fulfilled places only; a held place waiting for payment is not on it.

NameTypeRequiredLimitsMeaning
linesarray of BakeLineViewyes
versionintegeryesat least 0Counts the plan's changes; 0 before the first reservation.

BodyTemplate

A campaign's body, chosen from a fixed list.

One of: announcement, fresh-bread, cake-orders, seasonal-menu, come-back, catering-invite.

BreakerState

The breaker's state, in the order it rises.

One of: unconfigured, armed, read-only, off.

BreakerStatus

The 15-minute cron breaker, as Control keeps it (kill switch L0; docs/cost.md, "Guardrails"). Every property is sent, null or empty when there is nothing.

NameTypeRequiredLimitsMeaning
last_runstring or nullyesWhen the last run's window was handed to Control, an RFC 3339 UTC time.
last_usdstring or nullyesThe last window's usage priced in dollars with four decimals, such as "0.0123".
sincestring or nullyesWhen the state last changed, an RFC 3339 UTC time; null while unconfigured.
stateBreakerStateyesone of unconfigured, armed, read-only, offunconfigured until the breaker's first configured run, then armed, read-only or off. It only rises; a person resets it.
unreadarray of stringyesThe meters the last run could not read, counted as 0, such as ["rows_written"].

CampaignListResponse

GET /v1/sandboxes/{id}/campaigns: every campaign, by id.

NameTypeRequiredLimitsMeaning
campaignsarray of CampaignResponseyes

CampaignRequest

POST /v1/sandboxes/{id}/campaigns, staff only: a draft, 201 with the CampaignResponse (200 with Idempotent-Replayed: true for the same key again). PATCH /v1/sandboxes/{id}/campaigns/{cid} takes the same body and edits a draft: 200, or 409 problem of type campaign naming the rule (out-of-order) once it is scheduled, sent or cancelled. A guest gets 403 guest-scope on both.

NameTypeRequiredLimitsMeaning
bodyBodyTemplateyesone of announcement, fresh-bread, cake-orders, seasonal-menu, come-back, catering-invite
segmentSegmentyesone of everyone, cake-last-30-days, delivery-customers, lapsed-60-days, catering-leads
subjectSubjectTemplateyesone of weekend-bake, cake-week, new-season, we-miss-you, catering-season, thank-you

CampaignResponse

GET /v1/sandboxes/{id}/campaigns/{cid}, and the answer to every campaign command.

NameTypeRequiredLimitsMeaning
bodyBodyTemplateyesone of announcement, fresh-bread, cake-orders, seasonal-menu, come-back, catering-invite
campaignstringyesThe campaign's ULID.
clickedintegeryesat least 0
deliveredintegeryesat least 0Messages the Inbox delivered, opened and clicked, from its events.
openedintegeryesat least 0
recipientsintegeryesat least 0The frozen recipients' count: the segment's members with marketing consent at the send.
segmentSegmentyesone of everyone, cake-last-30-days, delivery-customers, lapsed-60-days, catering-leads
send_atstring or nullnoWhile scheduled, and after the send: the business time it is due or was sent.
stateCampaignStateyesone of draft, scheduled, sent, cancelled
subjectSubjectTemplateyesone of weekend-bake, cake-week, new-season, we-miss-you, catering-season, thank-you

CampaignState

Where a campaign is (docs/domain.md, "Campaign", the diagram).

One of: draft, scheduled, sent, cancelled.

CapacityRequest

PATCH /v1/sandboxes/{id}/schedule/{date}/capacity, signed-in sessions only: set one capacity for the day. The answer is the day's ScheduleResponse.

NameTypeRequiredLimitsMeaning
capacityintegeryes0 to 65535The new capacity. Places already taken stay; a cut below them only stops new ones.
targetstringyesWhat to change, in the domain's grammar: slot:HH:MM (a slot start on the quarter hour, such as slot:14:45), zone:z1 (to z3), item:<slug> (such as item:croissant) or catering.

CateringCancelReason

Why a confirmed catering event is cancelled: a choice from a list, never free text.

One of: customer_change, venue_lost, other.

CateringCancelRequest

POST /v1/sandboxes/{id}/catering/{cid}/cancel, by the customer or staff, as the session is. Only a confirmed event is cancelled: 200 with the CateringResponse, cancelled, its refund_deposit true when the event day is more than 7 days away; 409 problem of type catering naming the rule (out-of-order) in any other state.

NameTypeRequiredLimitsMeaning
reasonCateringCancelReasonyesone of customer_change, venue_lost, other

CateringInquiryRequest

POST /v1/sandboxes/{id}/catering, the customer's inquiry; the customer is the sandbox's persona. 201 with the CateringResponse (200 with Idempotent-Replayed: true for the same key again). A refused inquiry is 422 problem+json naming its rule: headcount, unknown-package, unknown-venue, closed-day, catering-lead (its detail names the 7-business-day rule and the earliest date allowed), or brief-too-long, brief-email, brief-phone, brief-card-number or brief-url, which never echo the brief.

NameTypeRequiredLimitsMeaning
briefstring or nullnoAt most 500 characters, with no email address, phone number, card number or URL.
datestringyesThe event day, YYYY-MM-DD: an open day (Tuesday to Sunday) at least 7 business days after today.
headcountintegeryes0 to 6553510 to 200 people.
packagestringyesA package slug, such as breakfast-spread (pastry-platter, breakfast-spread, lunch-boxes, celebration-cake-and-coffee).
venuestringyesA venue, v1 to v6.

CateringListResponse

GET /v1/sandboxes/{id}/catering: the sandbox's catering events, the seeded eight and any since, by date.

NameTypeRequiredLimitsMeaning
eventsarray of CateringResponseyes

CateringReminder

A reminder still to come, and when.

NameTypeRequiredLimitsMeaning
atstringyesA business time, such as 2026-11-19T07:00:00-07:00.
kindReminderKindyesone of follow_up, event_soon, invoice_due

CateringResponse

GET /v1/sandboxes/{id}/catering/{cid}, and the answer to every catering command: the inquiry, quote, decline, accept, cancel, and staff's production and delivered. It mirrors the CateringEvent aggregate's state. A refused step is 409 problem of type catering naming the rule (out-of-order, quote-expired); an accept when the day's two catering places are taken is 409 slot-full.

NameTypeRequiredLimitsMeaning
balance_centsinteger or nullnototal_cents - deposit_cents.
briefstring or nullnoThe brief, shown back to its own sandbox only, as text.
cateringstringyesThe catering event's ULID.
created_atstringyesThe inquiry's business time.
datestringyesThe event day, YYYY-MM-DD.
deposit_centsinteger or nullno25% of the total, rounded half-up.
deposit_paymentstring or nullnoThe deposit's payment ULID, once paid.
headcountintegeryes0 to 65535
invoicestring or nullnoThe balance invoice's ULID, once issued.
invoice_duestring or nullnoThe balance invoice's due date, YYYY-MM-DD, 7 days after delivery.
packagestringyes
refund_depositboolean or nullnoSet on a cancellation: whether the deposit goes back.
remindersarray of CateringReminderyesThe reminders still to come, earliest first; empty once the event ends.
stateCateringStateyes
total_centsinteger or nullnoFrom the quote on.
updated_atstringyesThe business time of the latest change.
valid_untilstring or nullnoWhen the quote stops being accepted, a business time: 07:00 on the 14th business day after the quote.
venuestringyes

CateringState

A catering event's state (docs/domain.md, "CateringEvent", the diagram). paid is passed through in the same step as closed, so a response never shows it today; it is listed because the diagram has it.

CateringView

The day's catering capacity.

NameTypeRequiredLimitsMeaning
capacityintegeryes0 to 65535Events the bakery takes in a day.
takenintegeryes0 to 65535Events held, reserved or fulfilled.

ClockRequest

POST /v1/sandboxes/{id}/clock, signed-in sessions only: move business time forward. The offset only grows, at most 14 days in total. Give exactly one of step and minutes: step is the named step, minutes the older form (a positive count). The Worker answers 422 clock with rule not-positive for no advance or for both fields, and rule offset-cap when the 14 days would be passed.

NameTypeRequiredLimitsMeaning
minutesinteger or nullnoat least 0The compatible older form: minutes to advance, 1 or more.
stepClockStep or nullno15m, 1h, 1d or next-opening.

ClockResponse

The business clock after an advance.

NameTypeRequiredLimitsMeaning
business_nowstringyesBusiness time now, such as 2026-11-07T09:42:10-07:00, shown at America/Denver's offset at that instant (-06:00 in summer).
clock_offset_sintegeryesSeconds of business time ahead of wall time, 0 to 14 days.
clock_vintegeryesat least 0Counts the advances; a child that has not heard of the latest one is retried.
stepstring or nullnoThe step applied, in its wire form: 15m, 1h, 1d, next-opening, or <n>m for the older minutes form.

ClockStep

How far one clock advance moves business time.

One of: 15m, 1h, 1d, next-opening.

ComparisonView

The two plans' totals side by side.

NameTypeRequiredLimitsMeaning
browser_minutesintegeryesat least 0
gap_percentnumber or nullnodouble(server − browser) / browser × 100, to one decimal: positive when the heuristic drives longer. null when the browser's plan drives 0 minutes and the server's does not.
server_minutesintegeryesat least 0

CompleteStopRequest

POST /v1/sandboxes/{id}/dispatch/{date}/complete, staff only: a stop of the published plan is delivered. Again for a delivered stop changes nothing. Refused 409 not-ready while the stop's order is still confirmed or in production: the kitchen marks it ready first.

NameTypeRequiredLimitsMeaning
stopintegeryes0 to 65535The stop's number in the published plan, from 1.

ConsentRequest

POST /v1/sandboxes/{id}/customers/{cid}/consent, by that customer's persona or by staff: 200 with the CustomerResponse; the same value again changes nothing and answers 200.

NameTypeRequiredLimitsMeaning
marketingbooleanyes

CountersState

How fresh counters are.

One of: live, stale, unavailable.

CreateSandboxRequest

POST /v1/sandboxes. A guest needs turnstile_token; an account that already has a live sandbox gets it back unless replace is true.

NameTypeRequiredLimitsMeaning
replacebooleanno
turnstile_tokenstring or nullno

CustomerListResponse

GET /v1/sandboxes/{id}/customers: the 60 personas, by id.

NameTypeRequiredLimitsMeaning
customersarray of CustomerResponseyes

CustomerResponse

GET /v1/sandboxes/{id}/customers/{cid}, and the answer to a consent change: one persona as Crm's projection holds it. Every field is synthetic; there is no contact of any kind.

NameTypeRequiredLimitsMeaning
cateringintegeryesat least 0Catering inquiries.
customerstringyesThe customer's ULID.
display_namestringyesTwo generic words, such as Drizzle Trowel.
home_zonestringyesz1 to z3.
last_order_atstring or nullnoThe latest such order's slot, a business time.
loyalty_pointsintegeryesLoyalty points: one per whole dollar paid, less refunds; never below zero.
marketingbooleanyesConsent to marketing: a campaign goes only to customers for whom it is true at the send.
ordersintegeryesat least 0Orders paid, or closed in the seed's history, and not cancelled.
placestringyesp1 to p64, a map label: the only address there is.
preferencesarray of stringyesOne to three item slugs.

DeadLetter

One message the Queue gave up on after 3 retries, from GET /v1/sandboxes/{id}/dead-letters (an array of these), and the answer to a redrive.

NameTypeRequiredLimitsMeaning
atstringyesWhen it was dead-lettered, a wall time.
errorstringyesThe last delivery error, as a reason code such as Kitchen answered 503.
idstringyesThe event's ULID: the key a redrive sends it again with.
seqintegeryesat least 0
streamstringyesThe producing object's name.
typestringyesThe event type, such as OrderPlaced.

DeclineReason

Why staff decline an inquiry: a choice from a list, never free text.

One of: too_soon, over_capacity, other.

DeclineRequest

POST /v1/sandboxes/{id}/catering/{cid}/decline, staff only, while the inquiry is an inquiry. 200 with the CateringResponse, declined; 409 problem of type catering naming the rule (out-of-order) once it is quoted.

NameTypeRequiredLimitsMeaning
reasonDeclineReasonyesone of too_soon, over_capacity, other

DemoSigninRequest

POST /v1/auth/demo/signin.

NameTypeRequiredLimitsMeaning
passwordstringyes
usernamestringyes

DemoSignupRequest

POST /v1/auth/demo. The password never reaches an object, a log or this type's Debug.

NameTypeRequiredLimitsMeaning
passwordstringyes12 to 128 characters, no composition rules.
turnstile_tokenstringyes

DemoSignupResponse

201 from POST /v1/auth/demo: the generated username, shown once.

NameTypeRequiredLimitsMeaning
usernamestringyesbaker- and four characters, such as baker-7f3k.

DispatchResponse

GET /v1/sandboxes/{id}/dispatch/{date}, which any session of the sandbox reads, and the answer to every dispatch command, which are staff only: one bakery day's stops and plans. It mirrors the Dispatch aggregate's state.

NameTypeRequiredLimitsMeaning
capacityintegeryesat least 0Units a van carries: the last proposal's, 250 before any.
comparisonComparisonView or nullnoBoth plans' totals, when there are both; null while the browser's solver has not run or cannot (the page then shows the server's plan alone and says why).
datestringyesThe bakery day, YYYY-MM-DD.
plan_vintegeryes0 to 65535The published plan's version, 0 before the first publish.
proposedPlanView or nullnoThe server's plan from the last propose.
publishedPlanView or nullnoThe record: what drivers, the kitchen board and the order status read.
stopsarray of StopViewyesEvery stop the day has seen, removed ones included, by order ULID.
submittedPlanView or nullnoThe browser's plan from the last plan, validated.
vansintegeryes0 to 255The day's vans, 1 to 4: the last proposal's, 4 before any.

FaultName

The faults a visitor may set.

One of: inbox, kitchen.

FaultRequest

POST /v1/sandboxes/{id}/faults, signed-in sessions only.

NameTypeRequiredLimitsMeaning
faultFaultNameyesone of inbox, kitchen
onbooleanyes

Faults

The sandbox's fault switches: the Queue Lab's broken subscriber, live. kitchen makes the Kitchen refuse every delivery with 503, so a message retries three times and lands in the dead-letter queue (PR 4); inbox does the same to the Inbox when it arrives (PR 8).

NameTypeRequiredLimitsMeaning
inboxbooleanyes
kitchenbooleanyes

FaultsResponse

The answer to a fault change: every switch as it now stands.

NameTypeRequiredLimitsMeaning
faultsFaultsyes

Flag

The demo's mode, set in Control (kill switch L1).

One of: live, read-only, off.

HealthResponse

GET /v1/health: the Worker answers, and which build it is. Touches no object.

NameTypeRequiredLimitsMeaning
commitstringyesThe commit the bundle was built from; dev on a laptop.
statusHealthStatusyesone of ok
versionstringyesThe API's version.

HealthStatus

One of: ok.

InboxResponse

GET /v1/sandboxes/{id}/inbox: the newest messages first, and how many there are in all. ?kind= keeps one kind, ?to= one customer's, ?limit= the newest that many (50 when left out, at most 500). Any session of the sandbox, a guest's included, sees every customer's messages: the persona is not filtered to its own, and ?to= is how a reader narrows to one.

NameTypeRequiredLimitsMeaning
messagesarray of MessageResponseyes
totalintegeryesat least 0

ItemCapView

One item's daily cap and use.

NameTypeRequiredLimitsMeaning
daily_capinteger or nullnoat least 0The most the kitchen bakes of it in a day; null when the item has no cap.
itemstringyesThe item's slug.
usedintegeryesat least 0Quantity on the day's places that count (held, reserved or fulfilled), variants summed.

LastWipe

One nightly wipe, as its wipe point recorded it.

NameTypeRequiredLimitsMeaning
atstringyesWhen the point was written, at the wipe's end: an RFC 3339 UTC time such as 2026-11-06T10:00:04Z.
msintegeryesat least 0Milliseconds from the 10:00 UTC cron to the wipe's last run.
objectsintegeryesat least 0Objects those sandboxes emptied, each Sandbox and its children.
sandboxesintegeryesat least 0Sandboxes the wipe ended.

LeadPipelineResponse

GET /v1/sandboxes/{id}/leads: every stage, in order, with its catering events.

NameTypeRequiredLimitsMeaning
stagesarray of LeadStageViewyes

LeadStage

A catering lead's stage in Crm's pipeline.

LeadStageView

NameTypeRequiredLimitsMeaning
countintegeryesat least 0
eventsarray of stringyesThe catering events' ULIDs.
stageLeadStageyes

LedgerEntry

One line of the ledger: a payment's success or a refund issued.

NameTypeRequiredLimitsMeaning
amount_centsintegeryesSigned: positive for a payment, negative for a refund.
atstringyesThe business time of the event that made it.
kindLedgerKindyesone of payment, refund
purposePaymentPurposeyesone of order, deposit, balance
refstringyesThe order or catering job's ULID.
stripe_idstringyesStripe's id: the intent's pi_... for a payment, the refund's re_... for a refund; seed:<payment> for a seeded order's payment, which the seed made and Stripe never saw.

LedgerKind

One of: payment, refund.

LedgerResponse

GET /v1/sandboxes/{id}/billing/ledger: every entry in order, and the totals.

NameTypeRequiredLimitsMeaning
entriesarray of LedgerEntryyes
net_centsintegeryespaid_cents - refunded_cents.
paid_centsintegeryes
refunded_centsintegeryesA positive amount.

MatrixPlace

One place of the travel-time matrix.

NameTypeRequiredLimitsMeaning
indexintegeryes0 to 2550 to the places' count less one, in order.
labelstringyesThe map label, p1 to p64; no name, coordinate or street.

MatrixResponse

The travel-time matrix the dispatch screen loads to run VROOM. It is not an API response: it is a static asset of the web Worker, the same JSON as the committed fixture crates/seed/data/<city>/matrix.json (ADR 0012, decision 7), and this type only fixes its shape.

NameTypeRequiredLimitsMeaning
builtstringyesWhen the build ran, or seed:<n> for the synthetic city.
citystringyesThe city the build is for, or synthetic.
depotintegerno0 to 255The bakery's place, where every route starts and ends; 0 when left out.
minutesarray of array of integeryesminutes[i][j]: whole minutes driving from place i to place j, 0 on the diagonal. Not necessarily symmetric.
placesarray of MatrixPlaceyes
sourcestringyesWhere the travel times come from, with the attribution the data's licence asks for.

MenuBulkLead

More than over_qty of one variant needs lead_minutes.

NameTypeRequiredLimitsMeaning
lead_minutesintegeryesat least 0
over_qtyintegeryesat least 0

MenuCategory

NameTypeRequiredLimitsMeaning
itemsarray of MenuItemyes
namestringyes
slugstringyes

MenuEditResponse

The answer to a staff edit or a publish: the published version and the items with drafts still waiting for a publish.

NameTypeRequiredLimitsMeaning
pendingarray of stringyes
versionintegeryesat least 0

MenuItem

NameTypeRequiredLimitsMeaning
allergensarray of stringyesFrom the nine major US allergens, such as wheat and tree-nuts.
bulk_leadMenuBulkLead or nullno
daily_capintegeryesat least 0
days_bakedarray of stringyesmon to sun.
namestringyes
pickup_onlybooleanyes
seasonMenuSeason or nullno
slugstringyes
statusMenuItemStateyesone of draft, available, paused, retired
variantsarray of MenuVariantyes

MenuItemPatch

PATCH /v1/sandboxes/{id}/menu/items/{slug}, signed-in sessions only. A price is a draft, set on every variant of the item, until POST .../menu/publish; a pause or a resume publishes that one item at once.

NameTypeRequiredLimitsMeaning
price_centsinteger or nullno
statusMenuItemStatus or nullno

MenuItemState

An item's stored lifecycle; the menu lists available and paused items.

One of: draft, available, paused, retired.

MenuItemStatus

What a staff edit may set on a menu item's status.

One of: paused, available.

MenuResponse

GET /v1/sandboxes/{id}/menu: the published menu, with ETag: "v<version>".

NameTypeRequiredLimitsMeaning
categoriesarray of MenuCategoryyes
versionintegeryesat least 0

MenuSeason

A seasonal window, MM-DD to MM-DD inclusive, wrapping over the new year when from is after to.

NameTypeRequiredLimitsMeaning
fromstringyes
tostringyes

MenuVariant

NameTypeRequiredLimitsMeaning
labelstringyes
lead_minutesintegeryesat least 0
price_centsintegeryes
slugstringyes<item slug>/<suffix>, such as croissant/box-6.

MessageKind

What a message is.

One of: receipt, ready, on_its_way, refund, declined, acknowledgement, quote, notice, reminder, invoice, campaign.

MessageResponse

One message in the sandbox's inbox, and GET /v1/sandboxes/{id}/inbox/{mid}.

NameTypeRequiredLimitsMeaning
bodystringyes
clicked_atstring or nullno
delivered_atstringyesBusiness times.
kindMessageKindyesone of receipt, ready, on_its_way, refund, declined, acknowledgement, quote, notice, reminder, invoice, campaign
messagestringyesThe message's ULID.
opened_atstring or nullno
refstringyesThe ULID of the order, catering event or campaign it is about.
subjectstringyesFrom a fixed template, filled with the event's own figures; never free text.
tostringyesThe customer's ULID: the only recipient there is.

OrderKind

Pickup at the counter, or delivery to a zone.

One of: pickup, delivery.

OrderLineRequest

One line of an order as the customer asks for it.

NameTypeRequiredLimitsMeaning
qtyintegeryes0 to 655351 to 24.
variantstringyesA variant slug from the menu, such as croissant/box-6.

OrderLineResponse

A placed line, priced from the menu version the order names; never repriced.

NameTypeRequiredLimitsMeaning
itemstringyes
qtyintegeryes0 to 65535
unit_centsintegeryes
variantstringyes

OrderRequest

POST /v1/sandboxes/{id}/orders. The customer is the sandbox's persona; the prices come from the menu version the Catalog publishes when the order is placed.

NameTypeRequiredLimitsMeaning
kindOrderKindyesone of pickup, delivery
linesarray of OrderLineRequestyes1 to 20 lines.
notestring or nullnoAt most 140 characters, with no email address, phone number, card number or URL.
slotstringyesThe pickup slot's or delivery window's start, a business time such as 2026-11-07T09:45:00-07:00.
zonestring or nullnoz1, z2 or z3 for a delivery; absent for a pickup.

OrderResponse

201 from placing an order (200 with Idempotent-Replayed: true for the same key again), GET /v1/sandboxes/{id}/orders/{oid}, and the answer to a cancel.

NameTypeRequiredLimitsMeaning
events_seqintegeryesat least 0The seq of the order's latest event.
held_untilstringyesWhen an unpaid order expires, 15 minutes of business time after placed_at.
idstringyesThe order's ULID.
kindOrderKindyesone of pickup, delivery
linesarray of OrderLineResponseyes
menu_versionintegeryesat least 0The published menu version the prices came from.
notestring or nullno
placed_atstringyesWhen the order was placed, a business time.
slotstringyesA business time.
statusOrderStatusyesone of placed, confirmed, expired, cancelled, refund_pending, in_production, ready, picked_up, out_for_delivery, delivered, closed
subtotal_centsintegeryes
tax_centsintegeryes
total_centsintegeryes
zonestring or nullno

OrderStatus

An order's state (docs/domain.md, "Order", the diagram).

One of: placed, confirmed, expired, cancelled, refund_pending, in_production, ready, picked_up, out_for_delivery, delivered, closed.

PaymentPurpose

What a payment pays for.

One of: order, deposit, balance.

PaymentResponse

POST /v1/sandboxes/{id}/orders/{oid}/payment: what the browser needs to mount Stripe's Payment Element. client_secret is a one-time value for the browser: it is not stored, not logged, and left out of this type's Debug.

NameTypeRequiredLimitsMeaning
amount_centsintegeryes
client_secretstringyesStripe's pi_..._secret_..., for <Elements options={{ clientSecret }}>.
paymentstringyesBilling's id for the payment, a ULID.
publishable_keystring or nullnoThe sandbox's pk_test_ key, when the API has one to give; the storefront's own build value otherwise.
statusPaymentStatusyes

PaymentStatus

Where a payment stands (docs/domain.md, the Billing rows).

PersonaAddress

A made-up address, labelled synthetic in its first line, passed to Stripe's confirmPayment as the billing address.

NameTypeRequiredLimitsMeaning
citystringyes
countrystringyesAn ISO 3166-1 alpha-2 code.
line1stringyes<place label>, unit <n> (synthetic), such as p17, unit 3 (synthetic).
postal_codestringyes
statestringyesA two-letter state code.

PlanChoice

Which stored plan to publish.

One of: proposed, submitted.

PlanRequest

POST /v1/sandboxes/{id}/dispatch/{date}/plan, staff only: the plan VROOM made in the browser. It carries no minutes: the server computes them from the matrix. Refused with 422 stop-missing, stop-twice, stop-unknown, over-capacity, route-ends, vans, window or no-stops. window: the vans leave the bakery at 09:00, a van early at a door waits for the stop's window to open and spends 3 minutes there, and each stop must be reached by its window's close (StopView.window plus 120 minutes).

NameTypeRequiredLimitsMeaning
vansarray of PlanRouteRequestyes

PlanRouteRequest

One van of the browser's plan.

NameTypeRequiredLimitsMeaning
endinteger or nullno0 to 255The place the van comes back to; the same rule.
startinteger or nullno0 to 255The place the van leaves from, as the solver reports it; it must be the bakery, and is taken to be when left out.
stopsarray of stringyesOrder ULIDs, in driving order. An empty list is a van left at the bakery.
vanintegeryes0 to 2551 to the day's vans, each once.

PlanSource

Who made a plan: the server's savings heuristic or the browser's VROOM solver.

One of: server, browser.

PlanView

A plan as the dispatch screen shows it (ADR 0012, decision 5). Its minutes are the server's own arithmetic over the matrix, whoever made the plan.

NameTypeRequiredLimitsMeaning
last_backstringyesWhen the last van is back, a business time: the vans leave at 09:00, drive the matrix's minutes, spend 3 minutes at each door and wait at a door they reach before its window opens.
minutesintegeryesat least 0Every van's drive summed.
plan_vintegeryes0 to 65535A published plan's version. A proposal or submission carries the version it would be published as when it was made; one at or below the response's plan_v is stale.
sourcePlanSourceyesone of server, browser
unassignedarray of stringyesOrder ULIDs no van could take. A plan with any cannot be published.
vansarray of VanPlanViewyesThe vans used, by van; a plan may use fewer than the day has.

Problem

An error body, application/problem+json (RFC 9457).

NameTypeRequiredLimitsMeaning
detailstring or nullnoWhat went wrong this time, for a person to read.
instancestring or nullnoThe request path this occurrence concerns.
next_freestring or nullnoAn extension member (RFC 9457, section 3.2), set only on the slot-full and hold-lost problems: the start of the next pickup slot with a place free that day, a business time such as 2026-11-07T10:15:00-07:00. Absent when nothing later that day is free, and on every other problem.
statusintegeryes0 to 65535The HTTP status code.
titlestringyesA short summary that does not change between occurrences.
typestringyesA URI reference naming the problem type; about:blank when the status says it all.

ProposeRequest

POST /v1/sandboxes/{id}/dispatch/{date}/propose, staff only: the server's plan for the confirmed stops. The vans and capacity become the day's.

NameTypeRequiredLimitsMeaning
capacityintegeryesat least 0Units a van carries, at least 1.
vansintegeryes0 to 2551 to 4.

PublishRequest

POST /v1/sandboxes/{id}/dispatch/{date}/publish, staff only: make a stored plan the record. Checked again against the day's stops; a repeat with the same Idempotency-Key publishes nothing more.

NameTypeRequiredLimitsMeaning
planPlanChoiceyesone of proposed, submitted

QuoteRequest

POST /v1/sandboxes/{id}/catering/{cid}/quote, staff only. 200 with the CateringResponse, now quoted; 409 problem of type catering naming the rule (out-of-order) once the inquiry is past inquiry; 422 quote-total for a total under 1 cent.

NameTypeRequiredLimitsMeaning
total_centsinteger or nullnoA staff override of the total, in cents, at least 1. Absent: the package's price per head times the headcount. The deposit is 25% of the total, rounded half-up.

RebuildResponse

GET /v1/sandboxes/{id}/rebuild, which any session of the sandbox reads, and 202 from POST to it, staff only (a guest gets 403 guest-scope): "Rebuild projections" drops Crm's projections and replays its log, 200 events per alarm. A second POST (with a new Idempotency-Key) while one runs is 409 problem of type rebuild and starts nothing; the same key again replays the first 202. Nothing else is refused during a rebuild: other commands run, reads answer the partial projection with Projection-State: rebuilding, and a campaign send that falls due is deferred (logged as sends_deferred) until it ends. GET says how far the running one is, and finished with its ms until the next start.

NameTypeRequiredLimitsMeaning
appliedintegeryesat least 0Log entries replayed so far.
msinteger or nullnoat least 0Once finished: the wall-clock milliseconds the rebuild took.
stateRebuildStateyesone of idle, rebuilding, finished
totalintegeryesat least 0The log's length.

RebuildState

One of: idle, rebuilding, finished.

RefundReason

Why staff give money back: a choice from a list, never free text (docs/domain.md, "Free text and personal data"). The other two reasons the domain knows, cancelled and payment_late, are the system's own.

One of: missing_item, quality, goodwill.

RefundRequest

POST /v1/sandboxes/{id}/orders/{oid}/refund, staff only. No amount_cents refunds all that is left; refunding all that is left of an order not yet closed cancels it.

NameTypeRequiredLimitsMeaning
amount_centsinteger or nullno
reasonRefundReasonyesone of missing_item, quality, goodwill

RefundResponse

The answer to a refund request: accepted, and on its way to Stripe.

NameTypeRequiredLimitsMeaning
amount_centsintegeryes
refundstringyesThe refund's ULID: the id of the order's OrderRefundRequested event, which Billing keeps the refund under.
statusOrderStatusyesone of placed, confirmed, expired, cancelled, refund_pending, in_production, ready, picked_up, out_for_delivery, delivered, closedThe order's status after the request: unchanged for a part, refund_pending for the whole of an order not yet closed.

ReminderKind

A catering reminder: what it is for.

One of: follow_up, event_soon, invoice_due.

SandboxKind

Whose a sandbox is: a guest's, behind Turnstile, or a signed-in account's.

One of: guest, account.

SandboxResponse

POST /v1/sandboxes (201 or 200) and GET /v1/sandboxes/{id}.

NameTypeRequiredLimitsMeaning
bornstringyesA wall time.
childrenintegeryesat least 0Registered child objects: the Catalog, the Kitchen and the 12 seeded orders once seeding is done, then one more for each order placed.
clock_offset_sintegeryesBusiness time minus wall time, in seconds (docs/architecture.md, "Time travel").
clock_vintegeryesat least 0Bumped by every clock advance.
faultsFaultsyes
idstringyesThe sandbox's ULID.
kindSandboxKindyesone of guest, account
personastringyesThe visitor's customer persona: the seed's first customer's ULID. Orders the visitor places are this customer's.
persona_addressPersonaAddressyesThe persona's synthetic address, from the seed: the Payment Element's billing address, so the visitor types none (ADR 0007, decision 6).
persona_namestringyesThe persona's synthetic display name, from the seed: the Payment Element's billing name.
seedintegeryesat least 0The seed the sandbox was generated from. A JSON number: JavaScript reads it exactly only below 2^53, so the router mints seeds in that range.
stateSandboxStateyes
token_vintegeryesat least 0Bumped by every API token rotation.
wipe_atstringyesThe first 10:00 UTC after born, a wall time.

SandboxState

A sandbox's lifecycle (docs/domain.md, "Sandbox").

ScheduleRequest

POST /v1/sandboxes/{id}/campaigns/{cid}/schedule, staff only: 200 with the CampaignResponse, now scheduled. A send_at less than 15 minutes of business time after now is 422 problem naming schedule-too-soon, its detail the earliest time allowed; a campaign not in draft is 409 campaign naming out-of-order. POST .../campaigns/{cid}/cancel (no body) takes a scheduled campaign back to draft and a draft to cancelled; a sent one is 409 campaign naming out-of-order.

NameTypeRequiredLimitsMeaning
send_atstringyesA business time, such as 2026-11-08T09:00:00-07:00.

ScheduleResponse

GET /v1/sandboxes/{id}/schedule/{date}, and the answer to a capacity change: one bakery day's places, caps and bake list. It mirrors the Schedule aggregate's state.

NameTypeRequiredLimitsMeaning
bake_listBakeListViewyes
cateringCateringViewyes
datestringyesThe bakery day, YYYY-MM-DD.
itemsarray of ItemCapViewyesThe items with a cap or a use that day, by slug; an item with no entry is uncapped.
menu_versionintegeryesat least 0The menu version the item caps came from; 0 before the first refresh.
openbooleanyesWhether the bakery opens that day (Tuesday to Sunday); a closed day refuses every hold.
slotsarray of SlotViewyesThe 32 pickup slots, 07:00 to 14:45, in time order.
zonesarray of ZoneViewyesThe three delivery zones, z1 to z3.

Segment

A segment: a fixed rule over Crm's projection, evaluated at the business time asked.

One of: everyone, cake-last-30-days, delivery-customers, lapsed-60-days, catering-leads.

SegmentListResponse

GET /v1/sandboxes/{id}/segments: every segment, in the fixed order.

NameTypeRequiredLimitsMeaning
segmentsarray of SegmentResponseyes

SegmentResponse

GET /v1/sandboxes/{id}/segments/{segment}: how many customers the rule holds now, and a few of their ids. Consent is not applied here; a send applies it. An unknown segment is 404.

NameTypeRequiredLimitsMeaning
membersintegeryesat least 0
samplearray of stringyesUp to five member ULIDs, smallest first.
segmentSegmentyesone of everyone, cake-last-30-days, delivery-customers, lapsed-60-days, catering-leads

SessionKind

How a session signed in. A guest has no account.

One of: guest, google, github, demo.

SessionResponse

GET /v1/session, and the answer to a demo sign-in: what the cookie says, read without an object call.

NameTypeRequiredLimitsMeaning
accountstring or nullnoThe account id, 64 hex characters; null for a guest.
expires_atstringyesWhen the cookie stops being accepted, a wall time such as 2026-11-05T12:00:00.000Z.
kindSessionKindyesone of guest, google, github, demo
sandboxstring or nullnoThe current sandbox's ULID, if any.

SlotView

One pickup slot of a day's schedule.

NameTypeRequiredLimitsMeaning
capacityintegeryes0 to 65535Places in the slot.
freeintegeryes0 to 65535capacity minus taken, 0 when over-booked after a capacity cut.
startstringyesThe slot's start, a business time such as 2026-07-14T14:45:00-06:00.
takenintegeryes0 to 65535Places held, reserved or fulfilled; released places do not count. Capped at 65,535.

StatusCounters

The status page's counters: the kept Analytics Engine queries over the last 24 hours, and the last nightly wipe (docs/operations.md, "Observability"). Counts are sums of the points' sample intervals, so they stay counts when Analytics Engine samples.

NameTypeRequiredLimitsMeaning
dlq_24hintegeryesat least 0dlq points in the last 24 hours: messages the dead-letter writer kept.
last_wipeLastWipe or nullyesThe latest wipe point; null when none is in the dataset.
orders_24hintegeryesat least 0order_placed points in the last 24 hours.
payment_failures_24hintegeryesat least 0payment_failed points in the last 24 hours.
payments_24hintegeryesat least 0payment_succeeded points in the last 24 hours.
refunds_24hintegeryesat least 0refund_issued points in the last 24 hours.
sandboxes_24hintegeryesat least 0sandbox_created points in the last 24 hours, to read against the daily cap.

StatusResponse

GET /v1/status: today's admission state, from the Control object. The field order is fixed and is the wire order: the day and the sandbox count and cap (PR 2), then the guest and demo-account counts and caps (PR 3), then the status page's counters with their time and state (PR 13), then the breaker's state (PR 17), then the flag. Readers go by name, so PR 2's smoke check still passes; a new field is added before flag. The counters are a snapshot of the kept Analytics Engine queries that the 15-minute cron refreshes, never a query per request (docs/operations.md, "Observability").

NameTypeRequiredLimitsMeaning
breakerBreakerStatusyesThe 15-minute cron breaker's state and its last run, kept in Control.
countersStatusCounters or nullyesThe last snapshot of the kept queries; null until the first refresh succeeds.
counters_as_ofstring or nullyesWhen that snapshot was taken, an RFC 3339 UTC time such as 2026-11-06T10:15:02.114Z; null with counters.
counters_stateCountersStateyesone of live, stale, unavailableWhether the counters are fresh: live, stale or unavailable.
daystringyesToday's UTC date, YYYY-MM-DD: the key of the daily ledger.
demo_account_capintegeryesat least 0The most demo accounts created in one UTC day.
demo_accounts_todayintegeryesat least 0Demo accounts created today.
flagFlagyesone of live, read-only, offThe demo's flag.
guest_capintegeryesat least 0The guests' share of the day's sandboxes.
guests_todayintegeryesat least 0Guest sandboxes admitted today.
sandbox_capintegeryesat least 0The most sandboxes admitted in one UTC day.
sandboxes_todayintegeryesat least 0Sandboxes admitted today, guests' included.

StopStatus

Where a delivery stop stands (ADR 0012; docs/domain.md, "Ubiquitous language": Stop).

One of: provisional, confirmed, removed, completed.

StopView

One stop of the day.

NameTypeRequiredLimitsMeaning
customerstringyesThe customer's ULID; the screen shows the persona's made-up address, never the place.
orderstringyesThe order's ULID: what every plan names the stop by.
placeintegeryes0 to 255The customer's place, an index into the travel-time matrix, 0 to 63.
place_labelstringyesThe place's label, p1 to p64.
statusStopStatusyesone of provisional, confirmed, removed, completed
stopinteger or nullno0 to 65535The stop's number in the published plan, counted from 1 across the vans, van 1's stops first; null when it is in no published plan.
unitsintegeryesat least 0The order's line quantities summed: what the stop takes of a van's capacity.
vaninteger or nullno0 to 255The van that carries it in the published plan; null when none does.
windowstringyesThe delivery window's start, a business time (RFC 3339 with the bakery's offset). The window is 2 hours long: it closes 120 minutes after this. A plan must reach the stop between the two, waiting at the door when it is early (rule window).
zonestringyesz1, z2 or z3.

SubjectTemplate

A campaign's subject, chosen from a fixed list; staff never type one (Q14).

One of: weekend-bake, cake-week, new-season, we-miss-you, catering-season, thank-you.

TimelineEntry

One line of a timeline.

NameTypeRequiredLimitsMeaning
atstringyesThe event's business time.
labelstringyesA short fixed sentence with the event's own figures, such as Paid $22.00.
refstringyesThe ULID of the order, catering event, message or customer the line is about.
typestringyesThe event's catalog name, such as OrderPaid.

TimelineResponse

GET /v1/sandboxes/{id}/customers/{cid}/timeline: the customer's events in business-time order. While a rebuild runs the timeline is partial; GET .../rebuild says so.

NameTypeRequiredLimitsMeaning
customerstringyes
entriesarray of TimelineEntryyes

TokenInfoResponse

GET /v1/sandboxes/{id}/token: what the console shows about the token without the token.

NameTypeRequiredLimitsMeaning
expires_atstring or nullnoWhen that token stops being accepted, a wall time.
issued_atstring or nullnoWhen the last token at this version was issued, a wall time; absent before the first issue and after a rotation until the next.
maskedstring or nullnoThat token's first 6 and last 4 characters around …, such as eyJ2Ij…x7Qk: enough to tell two tokens apart, never enough to use one.
token_vintegeryesat least 0The Sandbox's token version: a token naming an older one is refused.

TokenResponse

POST /v1/sandboxes/{id}/token (issue, or issue again at the same version) and POST /v1/sandboxes/{id}/token/rotate (the Sandbox's token version goes up, ApiTokenRotated is appended, and every token naming an older version stops at once): the token, shown this once. It is never stored, logged, put in an event or printed by this type's Debug; the console holds it in memory to copy it.

NameTypeRequiredLimitsMeaning
expires_atstringyes24 h after issued_at, a wall time; the sandbox's wipe ends it sooner.
issued_atstringyesA wall time, such as 2026-11-05T12:00:00.000Z.
tokenstringyesFor Authorization: Bearer <token>: <payload>.<mac>, both base64url, signed like the session cookie. Never in a URL: a token in a query string is refused with 400.
token_vintegeryesat least 0The Sandbox's token version the token names.

TrafficRequest

POST /v1/sandboxes/{id}/traffic, signed-in sessions only: simulated orders a minute.

NameTypeRequiredLimitsMeaning
rateintegeryes0 to 2551 to 6.

TrafficResponse

GET /v1/sandboxes/{id}/traffic, and the answer to starting or stopping it.

NameTypeRequiredLimitsMeaning
minutes_leftintegeryes0 to 65535What is left of the day's cap, 30 minutes (docs/cost.md, "Guardrails").
minutes_usedintegeryes0 to 65535Minutes of traffic this sandbox used today, UTC.
ordersintegeryes0 to 65535Orders the simulator placed today.
rateintegeryes0 to 255Orders a minute of the current or last run; 0 before the first start.
runningbooleanyes

VanPlanView

One van of a plan.

NameTypeRequiredLimitsMeaning
minutesintegeryesat least 0The drive, bakery to bakery, from the travel-time matrix.
stopsarray of stringyesOrder ULIDs, in driving order.
unitsintegeryesat least 0The stops' units summed; never over the day's capacity.
vanintegeryes0 to 2551 to the day's vans.

WipedResponse

DELETE /v1/sandboxes/{id}, "Wipe now": the sandbox is gone when this answers.

NameTypeRequiredLimitsMeaning
idstringyes
stateSandboxStateyesAlways wiped.

ZoneView

One delivery zone of a day's schedule. A zone's stops are shared by its three windows.

NameTypeRequiredLimitsMeaning
capacityintegeryes0 to 65535Stops in the zone for the day.
freeintegeryes0 to 65535capacity minus taken, 0 when over-booked.
takenintegeryes0 to 65535Stops held, reserved or fulfilled.
windowsarray of stringyesThe zone's window starts, 09:00, 11:00 and 13:00, business times.
zonestringyesz1, z2 or z3.