Jump to an endpoint
- GET/
locations/ {locationId} - GET/
locations/ {locationId}/ menu - GET/
locations/ {locationId}/ menu/ items/ {itemId} - GET/
locations/ {locationId}/ schedule - POST/
locations/ {locationId}/ delivery/ quote - POST/
orders/ quote - POST/
orders - GET/
orders/ {orderId} - POST/
orders/ {orderId}/ arrival - POST/
locations/ {locationId}/ promotions/ validate - POST/
locations/ {locationId}/ giftcards/ balance - GET/
customers/ me/ loyalty - POST/
customers/ token - GET/
customers/ me/ orders - GET/
customers/ me/ payment-methods - POSTorder.updated
Overview
One API to show a Real Time Ordering restaurant's menu, price a cart, place an order and follow it to the kitchen. It is built for self-order kiosks, branded apps and ordering partners.
Availability. Endpoints marked Live are running in production today. They accept either a partner API token or an RTO kiosk device token; a partner token reaches every location its account manages, and we issue one on request. Endpoints marked Planned are the agreed contract and may still change in detail.
Together the Live endpoints are enough to build a complete ordering client: read the menu, price a cart, place the order, and follow it to the kitchen. Card payment today is pay-at-counter or a card taken on RTO hardware; a card-not-present flow for your own app is planned. Gift cards can pay all or part of an order, and a failed order never consumes a gift card balance.
Conventions
- Money is always an integer number of cents (
1299= $12.99). - Rates are decimal strings (
"0.0825"= 8.25%). - Timestamps are ISO-8601 with an offset (
2026-09-21T18:30:00-07:00); time zones are IANA names. - IDs are opaque strings.
locationIdis the restaurant's short name in its ordering URL. - Every order write needs an
Idempotency-Keyheader. Retrying with the same key returns the first result, so a retry can never place a second order. - The server sets the price. Show your own math if you like, but only the totals from
POST /orders/quoteandPOST /orderscount. A payment that does not equal the server total is refused before anything reaches the kitchen. - Errors share one envelope with a stable machine
code.
Placing an order
POST /orders/quote (validate and price) → take payment for exactly that total → POST /orders → follow GET /orders/{orderId} or the order.updated webhook. If the kitchen refuses an order after a card payment, the server voids the payment itself and says so in the response.
Endpoints at a glance
| Endpoint | What it does | Status |
|---|---|---|
GET /locations/{locationId} | Location profile, order types, fees, tipping and payment options | Live |
GET /locations/{locationId}/menu | Full orderable menu for a location and order type | Live |
GET /locations/{locationId}/menu/items/{itemId} | One item with its full modifier tree (for lazy-loading large menus) | Live |
GET /locations/{locationId}/schedule | Open hours and orderable time slots per order type | Live |
POST /locations/{locationId}/delivery/quote | Is this address deliverable, and at what fee, minimum and ETA? | Planned |
POST /orders/quote | Validate and price a cart exactly as POST /orders would, without placing it | Live |
POST /orders | Place an order | Live |
GET /orders/{orderId} | Order status and receipt | Live |
POST /orders/{orderId}/arrival | Curbside "I'm here" | Planned |
POST /locations/{locationId}/promotions/validate | Check a coupon code against a cart without placing an order | Planned |
POST /locations/{locationId}/giftcards/balance | Live gift card balance at a location | Live |
GET /customers/me/loyalty | Points balance, punch cards and redeemable rewards at a location | Planned |
POST /customers/token | Exchange customer credentials (or an SMS one-time code) for a customer bearer token | Planned |
GET /customers/me/orders | Order history (reorder source) | Planned |
GET /customers/me/payment-methods | Saved cards (tokens only; brand + last4) | Planned |
POST order.updated | Sent to the client's registered URL whenever an order's status changes | Planned |
/locations/{locationId}LiveLocation profile, order types, fees, tipping and payment options
Parameters
| Parameter | In | Type | Notes |
|---|---|---|---|
locationId required | path | string | Restaurant orderUrl |
Responses
{
"locationId": "nancyjosburgers",
"name": "2pc Snack",
"phone": "(503) 555-0142",
"address": {
"line1": "2651 E Chapman Ave",
"line2": "line2",
"city": "Fullerton",
"state": "CA",
"postalCode": "92831",
"country": "US",
"lat": 33.87,
"lng": 33.87,
"instructions": "instructions"
},
"timeZone": "America/Los_Angeles",
"imageUrl": "imageUrl",
"logoUrl": "logoUrl",
"brandColor": "brandColor",
"acceptingOrders": true,
"openNow": true,
"taxRate": "0.0825",
"currency": "USD",
"orderTypes": [
{
"type": "pickup",
"enabled": true,
"asapAllowed": true,
"futureOrdersAllowed": true,
"maxDaysAhead": 1,
"prepTimeMinutes": 15,
"minimumOrder": 0,
"maximumOrder": 0,
"deliveryProvider": "merchant",
"vehicleInfoRequired": true,
"instructions": "instructions",
"paymentMethods": [
"card"
]
}
],
"tipping": {
"enabled": true,
"suggestions": [
{
"percent": "\u2026",
"isDefault": "\u2026"
}
]
},
"fees": [
{
"type": "service",
"label": "Service fee",
"flat": 1,
"percent": "percent",
"taxable": true
}
],
"guestCheckout": {
"allowed": true,
"emailRequired": true,
"phoneRequired": true
},
"cardTokenization": {
"provider": "shift4I4go",
"publicConfig": {}
},
"features": {
"specialInstructions": true,
"coupons": true,
"giftCards": true,
"loyalty": true,
"languages": [
"en"
]
}
}/locations/{locationId}/scheduleLiveOpen hours and orderable time slots per order type
Parameters
| Parameter | In | Type | Notes |
|---|---|---|---|
locationId required | path | string | Restaurant orderUrl |
orderType required | query | pickup | curbside | delivery | dineIn | |
days | query | integer |
Responses
{
"locationId": "nancyjosburgers",
"orderType": "pickup",
"timeZone": "America/Los_Angeles",
"now": "2026-09-21T18:30:00-07:00",
"asapAvailable": true,
"asapEstimate": "2026-09-21T18:30:00-07:00",
"nextOpen": "2026-09-21T18:30:00-07:00",
"days": [
{
"date": "2026-09-21",
"closed": true,
"closedReason": "closed",
"hours": [
{
"open": "\u2026",
"close": "\u2026"
}
],
"slots": [
{
"time": "\u2026",
"available": "\u2026"
}
]
}
]
}/locations/{locationId}/delivery/quotePlannedIs this address deliverable, and at what fee, minimum and ETA?
Stateless: checks an address without touching any cart or order.
Parameters
| Parameter | In | Type | Notes |
|---|---|---|---|
locationId required | path | string | Restaurant orderUrl |
Request body
{
"address": {
"line1": "2651 E Chapman Ave",
"line2": "line2",
"city": "Fullerton",
"state": "CA",
"postalCode": "92831",
"country": "US",
"lat": 33.87,
"lng": 33.87,
"instructions": "instructions"
},
"subtotal": 2340,
"requestedTime": "2026-09-21T18:30:00-07:00"
}
Responses
{
"deliverable": true,
"reason": "outsideZone",
"fee": 399,
"minimumOrder": 0,
"estimatedDeliveryTime": "2026-09-21T18:30:00-07:00",
"provider": "provider",
"zoneId": "zoneId"
}error.details.issues lists every problem./orders/quoteLiveValidate and price a cart exactly as POST /orders would, without placing it
Runs the full order pipeline in dry-run: item/modifier resolution, 86 check, hours/slot check, min order, delivery zone, discounts, tax, fees, tip and (for POS-priced stores) the POS price check. Returns every problem at once in issues[] (HTTP 200) so a client can fix the cart in one round trip. A quote still prices the cart when the location is paused, not yet live or closed: orderable is false and issues[] says why, while lines and totals are real, so an integration can be built before go-live. The returned quoteId may be passed to POST /orders within expiresAt; if the server total has changed by then, the order is rejected with PRICE_CHANGED and the new totals.
Send payment.gifts to check a split: totals.giftCardApplied and totals.amountDue come back, and a split that does not add up is listed in issues. A quote does not look up balances; use the balance endpoint for that. POST /orders checks every card again before spending any of them.
Request body
{
"locationId": "nancyjosburgers",
"lines": [
{
"lineId": "0",
"itemId": "282413",
"sizeId": "White",
"quantity": 2,
"modifiers": [
{
"groupId": "\u2026",
"modifierId": "\u2026"
}
],
"specialInstructions": "extra napkins",
"forName": "forName"
}
],
"orderType": "pickup",
"requestedTime": "2026-09-21T18:30:00-07:00",
"deliveryAddress": {
"line1": "2651 E Chapman Ave",
"line2": "line2",
"city": "Fullerton",
"state": "CA",
"postalCode": "92831",
"country": "US",
"lat": 33.87,
"lng": 33.87,
"instructions": "instructions"
},
"vehicle": {
"make": "make",
"model": "model",
"color": "color",
"plate": "plate"
},
"tableNumber": "tableNumber",
"tip": {
"amount": 2716,
"percent": 18
},
"promoCode": "promoCode",
"notes": "notes",
"customer": {
"firstName": "Sam",
"lastName": "Rivera",
"email": "sam@example.com",
"phone": "(503) 555-0142",
"marketingOptIn": true
},
"payment": {
"method": "cardToken",
"prepaid": {
"provider": "stripe",
"reference": "pi_3QxExample",
"amount": 2716
},
"cardToken": {
"token": "tok_example",
"postalCode": "92831"
},
"wallet": {
"type": "applePay",
"token": "tok_example"
},
"cardPresent": {
"paymentId": "paymentId"
},
"softPos": {
"recordNo": "recordNo",
"authCode": "04711A",
"refNo": "refNo",
"approvedAmount": "approvedAmount",
"cardType": "cardType",
"acctNoMasked": "acctNoMasked"
},
"savedCardId": "savedCardId",
"gifts": [
{
"number": "\u2026",
"pin": "\u2026"
}
],
"loyaltyRewardIds": [
"loyaltyRewardIds"
]
},
"source": {
"channel": "kiosk",
"deviceId": "deviceId",
"partnerOrderId": "partnerOrderId"
}
}
Responses
issues — a quote can price and still be unorderable){
"quoteId": "q_7f3a",
"expiresAt": "2026-09-21T18:30:00-07:00",
"orderable": true,
"issues": [
{
"code": "ITEM_UNAVAILABLE",
"message": "2pc Snack is sold out.",
"lineId": "0",
"itemId": "282413",
"modifierGroupId": "modifierGroupId",
"nextAvailableTime": "2026-09-21T18:30:00-07:00"
}
],
"lines": [
{
"lineId": "0",
"itemId": "282413",
"name": "2pc Snack",
"sizeName": "sizeName",
"quantity": 2,
"unitPrice": 995,
"total": 2716,
"modifiers": [
{
"modifierId": "\u2026",
"name": "\u2026"
}
],
"specialInstructions": "extra napkins"
}
],
"totals": {
"subtotal": 2340,
"discount": 0,
"fees": [
{
"type": "\u2026",
"label": "\u2026"
}
],
"deliveryFee": 0,
"tax": 176,
"tip": 200,
"total": 2716,
"giftCardApplied": 0,
"amountDue": 2716
},
"estimatedReadyTime": "2026-09-21T18:30:00-07:00",
"upsells": [
{
"itemId": "282413",
"name": "2pc Snack",
"price": 995
}
]
}/ordersLivePlace an order
Re-validates and re-prices server-side. Payment is then taken/verified for exactly the server total, the order is persisted, and it is sent to the POS.
Payment is verified, never trusted. cardPresent must reference a /payments/v1 record that is approved, belongs to this location, is unused, and whose amount equals the order total. cardToken / wallet are charged by the server. A client can never mark an order paid by itself.
If POS submission fails after payment, the server voids/refunds and returns 502 POS_REJECTED with payment.status — the client does not need to void.
Gift cards. payment.gifts lists 1 to 5 cards, each with the amount to take from it. Use them alone (method: giftCard, cards must equal the total), with prepaid (cards + prepaid.amount must equal the total, e.g. a $75 order as a $50 gift card + $25 on your Stripe checkout), or with payAtStore (cards up to the total; the store collects the rest and the ticket says how much). Every balance is checked before any card is spent, and a card short of its amount refuses the order with 402 GIFT_INSUFFICIENT and that card's balance.
A failed order never consumes a balance. If a card fails partway, the cards already redeemed are voided and the order is refused with 402 GIFT_DECLINED; if the order cannot be saved you get 502 POS_REJECTED. error.details.payment.gifts says what happened to each card. If the POS refuses an order after it was accepted, its gift redemptions are voided and GET /orders/{orderId} shows payment.gifts[].status: voided and pos.status: rejected; any prepaid part is yours to refund. An order that is only held because the store's POS is offline keeps its redemptions: the store still gets it.
Retrying with the same Idempotency-Key (or the same prepaid.reference) returns the first order and spends nothing. A retry that overlaps a first attempt still running gets 409 IDEMPOTENCY_CONFLICT; retry it a few seconds later.
Parameters
| Parameter | In | Type | Notes |
|---|---|---|---|
Idempotency-Key required | header | string · uuid |
Request body
{
"locationId": "nancyjosburgers",
"lines": [
{
"lineId": "0",
"itemId": "282413",
"sizeId": "White",
"quantity": 2,
"modifiers": [
{}
],
"specialInstructions": "extra napkins",
"forName": "forName"
}
],
"orderType": "pickup",
"requestedTime": "2026-09-21T18:30:00-07:00",
"deliveryAddress": {
"line1": "2651 E Chapman Ave",
"line2": "line2",
"city": "Fullerton",
"state": "CA",
"postalCode": "92831",
"country": "US",
"lat": 33.87,
"lng": 33.87,
"instructions": "instructions"
},
"vehicle": {
"make": "make",
"model": "model",
"color": "color",
"plate": "plate"
},
"tableNumber": "tableNumber",
"tip": {
"amount": 2716,
"percent": 18
},
"promoCode": "promoCode",
"notes": "notes",
"customer": {
"firstName": "Sam",
"lastName": "Rivera",
"email": "sam@example.com",
"phone": "(503) 555-0142",
"marketingOptIn": true
},
"payment": {
"method": "cardToken",
"prepaid": {
"provider": "stripe",
"reference": "pi_3QxExample",
"amount": 2716
},
"cardToken": {
"token": "tok_example",
"postalCode": "92831"
},
"wallet": {
"type": "applePay",
"token": "tok_example"
},
"cardPresent": {
"paymentId": "paymentId"
},
"softPos": {
"recordNo": "recordNo",
"authCode": "04711A",
"refNo": "refNo",
"approvedAmount": "approvedAmount",
"cardType": "cardType",
"acctNoMasked": "acctNoMasked"
},
"savedCardId": "savedCardId",
"gifts": [
{
"number": "\u2026",
"pin": "\u2026"
}
],
"loyaltyRewardIds": [
"loyaltyRewardIds"
]
},
"source": {
"channel": "kiosk",
"deviceId": "deviceId",
"partnerOrderId": "partnerOrderId"
},
"quoteId": "q_7f3a",
"expectedTotal": 2716
}
Responses
{
"orderId": "21707901",
"orderNumber": "1093",
"accessToken": "accessToken",
"locationId": "nancyjosburgers",
"status": "received",
"statusReason": "statusReason",
"orderType": "pickup",
"placedAt": "2026-09-21T18:30:00-07:00",
"requestedTime": "2026-09-21T18:30:00-07:00",
"estimatedReadyTime": "2026-09-21T18:30:00-07:00",
"lines": [
{
"lineId": "0",
"itemId": "282413",
"name": "2pc Snack",
"sizeName": "sizeName",
"quantity": 2,
"unitPrice": 995,
"total": 2716,
"modifiers": [
{
"modifierId": "\u2026",
"name": "\u2026"
}
],
"specialInstructions": "extra napkins"
}
],
"removedLines": [
"removedLines"
],
"totals": {
"subtotal": 2340,
"discount": 0,
"fees": [
{
"type": "\u2026",
"label": "\u2026"
}
],
"deliveryFee": 0,
"tax": 176,
"tip": 200,
"total": 2716,
"giftCardApplied": 0,
"amountDue": 2716
},
"payment": {
"method": "method",
"status": "pending",
"amount": 2716,
"brand": "VISA",
"last4": "1111",
"authCode": "04711A",
"reference": "58",
"gifts": [
{
"last4": "\u2026",
"amount": "\u2026"
}
]
},
"customer": {
"firstName": "Sam",
"lastName": "Rivera",
"email": "sam@example.com",
"phone": "(503) 555-0142",
"marketingOptIn": true
},
"deliveryAddress": {
"line1": "2651 E Chapman Ave",
"line2": "line2",
"city": "Fullerton",
"state": "CA",
"postalCode": "92831",
"country": "US",
"lat": 33.87,
"lng": 33.87,
"instructions": "instructions"
},
"delivery": {
"provider": "provider",
"trackingUrl": "trackingUrl",
"driverName": "driverName"
},
"pos": {
"system": "skytab",
"checkNumber": "checkNumber",
"status": "pending"
},
"trackingUrl": "trackingUrl"
}{
"orderId": "21707901",
"orderNumber": "1093",
"accessToken": "accessToken",
"locationId": "nancyjosburgers",
"status": "received",
"statusReason": "statusReason",
"orderType": "pickup",
"placedAt": "2026-09-21T18:30:00-07:00",
"requestedTime": "2026-09-21T18:30:00-07:00",
"estimatedReadyTime": "2026-09-21T18:30:00-07:00",
"lines": [
{
"lineId": "0",
"itemId": "282413",
"name": "2pc Snack",
"sizeName": "sizeName",
"quantity": 2,
"unitPrice": 995,
"total": 2716,
"modifiers": [
{
"modifierId": "\u2026",
"name": "\u2026"
}
],
"specialInstructions": "extra napkins"
}
],
"removedLines": [
"removedLines"
],
"totals": {
"subtotal": 2340,
"discount": 0,
"fees": [
{
"type": "\u2026",
"label": "\u2026"
}
],
"deliveryFee": 0,
"tax": 176,
"tip": 200,
"total": 2716,
"giftCardApplied": 0,
"amountDue": 2716
},
"payment": {
"method": "method",
"status": "pending",
"amount": 2716,
"brand": "VISA",
"last4": "1111",
"authCode": "04711A",
"reference": "58",
"gifts": [
{
"last4": "\u2026",
"amount": "\u2026"
}
]
},
"customer": {
"firstName": "Sam",
"lastName": "Rivera",
"email": "sam@example.com",
"phone": "(503) 555-0142",
"marketingOptIn": true
},
"deliveryAddress": {
"line1": "2651 E Chapman Ave",
"line2": "line2",
"city": "Fullerton",
"state": "CA",
"postalCode": "92831",
"country": "US",
"lat": 33.87,
"lng": 33.87,
"instructions": "instructions"
},
"delivery": {
"provider": "provider",
"trackingUrl": "trackingUrl",
"driverName": "driverName"
},
"pos": {
"system": "skytab",
"checkNumber": "checkNumber",
"status": "pending"
},
"trackingUrl": "trackingUrl"
}error.details.issues lists every problem./orders/{orderId}LiveOrder status and receipt
Callable with the API key that created the order, or with the order's accessToken (returned at creation; safe to put in a customer-facing tracking link). Order ids are not sequential and are not the POS/restaurant order number.
Auth: apiKey, orderToken
Parameters
| Parameter | In | Type | Notes |
|---|---|---|---|
orderId required | path | string |
Responses
{
"orderId": "21707901",
"orderNumber": "1093",
"accessToken": "accessToken",
"locationId": "nancyjosburgers",
"status": "received",
"statusReason": "statusReason",
"orderType": "pickup",
"placedAt": "2026-09-21T18:30:00-07:00",
"requestedTime": "2026-09-21T18:30:00-07:00",
"estimatedReadyTime": "2026-09-21T18:30:00-07:00",
"lines": [
{
"lineId": "0",
"itemId": "282413",
"name": "2pc Snack",
"sizeName": "sizeName",
"quantity": 2,
"unitPrice": 995,
"total": 2716,
"modifiers": [
{
"modifierId": "\u2026",
"name": "\u2026"
}
],
"specialInstructions": "extra napkins"
}
],
"removedLines": [
"removedLines"
],
"totals": {
"subtotal": 2340,
"discount": 0,
"fees": [
{
"type": "\u2026",
"label": "\u2026"
}
],
"deliveryFee": 0,
"tax": 176,
"tip": 200,
"total": 2716,
"giftCardApplied": 0,
"amountDue": 2716
},
"payment": {
"method": "method",
"status": "pending",
"amount": 2716,
"brand": "VISA",
"last4": "1111",
"authCode": "04711A",
"reference": "58",
"gifts": [
{
"last4": "\u2026",
"amount": "\u2026"
}
]
},
"customer": {
"firstName": "Sam",
"lastName": "Rivera",
"email": "sam@example.com",
"phone": "(503) 555-0142",
"marketingOptIn": true
},
"deliveryAddress": {
"line1": "2651 E Chapman Ave",
"line2": "line2",
"city": "Fullerton",
"state": "CA",
"postalCode": "92831",
"country": "US",
"lat": 33.87,
"lng": 33.87,
"instructions": "instructions"
},
"delivery": {
"provider": "provider",
"trackingUrl": "trackingUrl",
"driverName": "driverName"
},
"pos": {
"system": "skytab",
"checkNumber": "checkNumber",
"status": "pending"
},
"trackingUrl": "trackingUrl"
}/orders/{orderId}/arrivalPlannedCurbside "I'm here"
Auth: apiKey, orderToken
Parameters
| Parameter | In | Type | Notes |
|---|---|---|---|
orderId required | path | string |
Request body
{
"vehicle": {
"make": "make",
"model": "model",
"color": "color",
"plate": "plate"
},
"parkingSpot": "parkingSpot"
}
Responses
/locations/{locationId}/promotions/validatePlannedCheck a coupon code against a cart without placing an order
Parameters
| Parameter | In | Type | Notes |
|---|---|---|---|
locationId required | path | string | Restaurant orderUrl |
Request body
{
"code": "ITEM_UNAVAILABLE",
"cart": {
"locationId": "nancyjosburgers",
"lines": [
{
"lineId": "\u2026",
"itemId": "\u2026"
}
]
},
"email": "sam@example.com"
}
Responses
{
"valid": true,
"discount": 0,
"description": "description",
"reason": "reason"
}/locations/{locationId}/giftcards/balanceLiveLive gift card balance at a location
Asks the location's own gift card system. approved: false (HTTP 200) means the system did not recognize the card. 409 GIFT_NOT_SUPPORTED means the location has no gift card system (features.giftCards on the location says so up front).
Gift card numbers are not payment card data, but treat them with care: the number alone can spend the card. Do not log it or show more than the last 4 digits. This endpoint is POST so the number stays out of URLs, and it is rate-limited to 10 lookups per 10 minutes per API key and per IP (429 RATE_LIMITED, honour Retry-After). Check a balance when the guest enters a card, not in a loop.
Parameters
| Parameter | In | Type | Notes |
|---|---|---|---|
locationId required | path | string | Restaurant orderUrl |
Request body
{
"number": "6036000000000001111",
"pin": "pin"
}
Responses
{
"approved": true,
"balance": 2500,
"currency": "USD",
"last4": "1111",
"provider": "shift4",
"message": "2pc Snack is sold out."
}error.details.issues lists every problem./customers/me/loyaltyPlannedPoints balance, punch cards and redeemable rewards at a location
Auth: customerToken
Parameters
| Parameter | In | Type | Notes |
|---|---|---|---|
locationId required | query | string |
Responses
{
"enrolled": true,
"points": 1,
"pointsValue": 1,
"punchCards": [
{
"id": "id",
"name": "2pc Snack",
"punches": 1,
"threshold": 1
}
],
"rewards": [
{
"id": "id",
"name": "2pc Snack",
"pointsCost": 1,
"value": 1,
"redeemable": true
}
]
}/customers/tokenPlannedExchange customer credentials (or an SMS one-time code) for a customer bearer token
Scoped to one merchant. Replaces the hash/username cookie login for API clients.
Request body
{
"locationId": "nancyjosburgers",
"email": "sam@example.com",
"password": "password"
}
Responses
{
"accessToken": "accessToken",
"expiresAt": "2026-09-21T18:30:00-07:00",
"customer": {
"firstName": "Sam",
"lastName": "Rivera",
"email": "sam@example.com",
"phone": "(503) 555-0142",
"marketingOptIn": true
}
}/customers/me/ordersPlannedOrder history (reorder source)
Auth: customerToken
Parameters
| Parameter | In | Type | Notes |
|---|---|---|---|
locationId | query | string |
Responses
{
"orders": [
{
"orderId": "21707901",
"orderNumber": "1093",
"accessToken": "accessToken",
"locationId": "nancyjosburgers",
"status": "received",
"statusReason": "statusReason",
"orderType": "pickup",
"placedAt": "2026-09-21T18:30:00-07:00",
"requestedTime": "2026-09-21T18:30:00-07:00",
"estimatedReadyTime": "2026-09-21T18:30:00-07:00",
"lines": [
{
"lineId": "\u2026",
"itemId": "\u2026"
}
],
"removedLines": [
"removedLines"
],
"totals": {
"subtotal": "\u2026",
"discount": "\u2026"
},
"payment": {
"method": "\u2026",
"status": "\u2026"
},
"customer": {
"firstName": "\u2026",
"lastName": "\u2026"
},
"deliveryAddress": {
"line1": "\u2026",
"line2": "\u2026"
},
"delivery": {
"provider": "\u2026",
"trackingUrl": "\u2026"
},
"pos": {
"system": "\u2026",
"checkNumber": "\u2026"
},
"trackingUrl": "trackingUrl"
}
]
}/customers/me/payment-methodsPlannedSaved cards (tokens only; brand + last4)
Auth: customerToken
Responses
[
{
"id": "id",
"brand": "VISA",
"last4": "1111",
"expMonth": 1,
"expYear": 1
}
]order.updatedPlannedSent to the client's registered URL whenever an order's status changes
Signed like our Shift4 loyalty inbound: header X-RTO-Signature: t=<unix>,v1=<hex> where v1 = HMAC-SHA256(secret, t + "." + rawBody). Reject if |now - t| > 300s. Retries with exponential backoff for 24h; eventId is stable across retries.
Request body
{
"eventId": "eventId",
"type": "order.updated",
"createdAt": "2026-09-21T18:30:00-07:00",
"order": {
"orderId": "21707901",
"orderNumber": "1093",
"accessToken": "accessToken",
"locationId": "nancyjosburgers",
"status": "received",
"statusReason": "statusReason",
"orderType": "pickup",
"placedAt": "2026-09-21T18:30:00-07:00",
"requestedTime": "2026-09-21T18:30:00-07:00",
"estimatedReadyTime": "2026-09-21T18:30:00-07:00",
"lines": [
{
"lineId": "\u2026",
"itemId": "\u2026"
}
],
"removedLines": [
"removedLines"
],
"totals": {
"subtotal": 2340,
"discount": 0,
"fees": [
{
"type": "\u2026",
"label": "\u2026"
}
],
"deliveryFee": 0,
"tax": 176,
"tip": 200,
"total": 2716,
"giftCardApplied": 0,
"amountDue": 2716
},
"payment": {
"method": "method",
"status": "pending",
"amount": 2716,
"brand": "VISA",
"last4": "1111",
"authCode": "04711A",
"reference": "58",
"gifts": [
{
"last4": "\u2026",
"amount": "\u2026"
}
]
},
"customer": {
"firstName": "Sam",
"lastName": "Rivera",
"email": "sam@example.com",
"phone": "(503) 555-0142",
"marketingOptIn": true
},
"deliveryAddress": {
"line1": "2651 E Chapman Ave",
"line2": "line2",
"city": "Fullerton",
"state": "CA",
"postalCode": "92831",
"country": "US",
"lat": 33.87,
"lng": 33.87,
"instructions": "instructions"
},
"delivery": {
"provider": "provider",
"trackingUrl": "trackingUrl",
"driverName": "driverName"
},
"pos": {
"system": "skytab",
"checkNumber": "checkNumber",
"status": "pending"
},
"trackingUrl": "trackingUrl"
}
}
Responses
Errors
Every error uses the same envelope: {"error": {"code", "message", "details"}}. The message is safe to show a customer; switch on code. When a cart can't be placed, details.issues lists every problem at once, and after a refused card payment details.payment says whether it was voided.
UNAUTHORIZEDFORBIDDENNOT_FOUNDVALIDATION_FAILEDLOCATION_UNAVAILABLELOCATION_CLOSEDORDER_TYPE_UNAVAILABLESLOT_UNAVAILABLEITEM_UNAVAILABLEITEM_NOT_ON_MENUINVALID_MODIFIERSBELOW_MINIMUMABOVE_MAXIMUMOUTSIDE_DELIVERY_ZONEPROMOTION_INVALIDPRICE_CHANGEDIDEMPOTENCY_CONFLICTPAYMENT_DECLINEDPAYMENT_AMOUNT_MISMATCHPAYMENT_NOT_APPROVEDPAYMENT_ALREADY_USEDPAYMENT_METHOD_NOT_ALLOWEDGIFT_NOT_SUPPORTEDGIFT_INSUFFICIENTGIFT_DECLINEDPOS_REJECTEDRATE_LIMITEDINTERNALOpenAPI file
The full OpenAPI 3.1 description of everything on this page, for code generators and API tools. Download openapi.yaml or copy it below.
Show OpenAPI 3.1 YAML
openapi: 3.1.0
info:
title: RTO Ordering API
version: 1.0.0
summary: Menu, pricing, ordering and order status for Real Time Ordering (GimmeGrub) locations.
description: "One API to show a Real Time Ordering restaurant's menu, price a cart, place an order and\n\
follow it to the kitchen. It is built for self-order kiosks, branded apps and ordering partners.\n\
\n**Availability.** Endpoints marked *Live* are running in production today. They accept either a\n\
partner API token or an RTO kiosk device token; a partner token reaches every location its account\n\
manages, and we issue one on request. Endpoints marked *Planned* are the agreed contract and may\n\
still change in detail.\n\nTogether the Live endpoints are enough to build a complete ordering client:\
\ read the menu, price a\ncart, place the order, and follow it to the kitchen. Card payment today\
\ is pay-at-counter or a card\ntaken on RTO hardware; a card-not-present flow for your own app is\
\ planned. Gift cards can pay all or\npart of an order, and a failed order never consumes a gift card\
\ balance.\n\n## Conventions\n- **Money** is always an integer number of **cents** (`1299` = $12.99).\n\
- **Rates** are decimal strings (`\"0.0825\"` = 8.25%).\n- **Timestamps** are ISO-8601 with an offset\
\ (`2026-09-21T18:30:00-07:00`); time zones are IANA names.\n- **IDs** are opaque strings. `locationId`\
\ is the restaurant's short name in its ordering URL.\n- **Every order write** needs an `Idempotency-Key`\
\ header. Retrying with the same key returns the first\n result, so a retry can never place a second\
\ order.\n- **The server sets the price.** Show your own math if you like, but only the totals from\n\
\ `POST /orders/quote` and `POST /orders` count. A payment that does not equal the server total is\
\ refused\n before anything reaches the kitchen.\n- **Errors** share one envelope with a stable machine\
\ `code`.\n\n## Placing an order\n`POST /orders/quote` (validate and price) → take payment for exactly\
\ that total → `POST /orders` →\nfollow `GET /orders/{orderId}` or the `order.updated` webhook. If\
\ the kitchen refuses an order after a card\npayment, the server voids the payment itself and says\
\ so in the response.\n"
contact:
name: Real Time Ordering
url: https://www.realtimeordering.com
servers:
- url: https://www.gimmegrub.com/api/v1
description: Production
security:
- apiKey: []
tags:
- name: Locations
- name: Menu
- name: Scheduling
- name: Delivery
- name: Orders
- name: Promotions
- name: GiftCards
- name: Loyalty
- name: Customers
- name: Devices
- name: Webhooks
paths:
/locations/{locationId}:
get:
tags:
- Locations
operationId: getLocation
summary: Location profile, order types, fees, tipping and payment options
parameters:
- $ref: '#/components/parameters/locationId'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Location'
'404':
$ref: '#/components/responses/NotFound'
x-status: live
/locations/{locationId}/menu:
get:
tags:
- Menu
operationId: getMenu
summary: Full orderable menu for a location and order type
description: 'Returns the active menu with sizes and nested modifier groups. Unavailable (86''d)
items
are **included** with `available: false` so clients can show "sold out" and reject stale
carts deterministically. Supports `ETag` / `If-None-Match`.
'
parameters:
- $ref: '#/components/parameters/locationId'
- name: orderType
in: query
schema:
$ref: '#/components/schemas/OrderType'
- name: at
in: query
description: Evaluate menu/category availability at this time (future orders). Defaults to now.
schema:
type: string
format: date-time
- name: If-None-Match
in: header
schema:
type: string
responses:
'200':
description: OK
headers:
ETag:
schema:
type: string
content:
application/json:
schema:
$ref: '#/components/schemas/Menu'
'304':
description: Not modified
'404':
$ref: '#/components/responses/NotFound'
x-status: live
/locations/{locationId}/menu/items/{itemId}:
get:
tags:
- Menu
operationId: getMenuItem
summary: One item with its full modifier tree (for lazy-loading large menus)
parameters:
- $ref: '#/components/parameters/locationId'
- name: itemId
in: path
required: true
schema:
type: string
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/MenuItem'
'404':
$ref: '#/components/responses/NotFound'
x-status: live
/locations/{locationId}/schedule:
get:
tags:
- Scheduling
operationId: getSchedule
summary: Open hours and orderable time slots per order type
parameters:
- $ref: '#/components/parameters/locationId'
- name: orderType
in: query
required: true
schema:
$ref: '#/components/schemas/OrderType'
- name: days
in: query
schema:
type: integer
minimum: 1
maximum: 31
default: 7
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Schedule'
x-status: live
/locations/{locationId}/delivery/quote:
post:
tags:
- Delivery
operationId: quoteDelivery
summary: Is this address deliverable, and at what fee, minimum and ETA?
description: 'Stateless: checks an address without touching any cart or order.'
parameters:
- $ref: '#/components/parameters/locationId'
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- address
properties:
address:
$ref: '#/components/schemas/Address'
subtotal:
type: integer
description: cents; affects min-order and %-based fees
requestedTime:
type: string
format: date-time
description: omit for ASAP
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/DeliveryQuote'
'422':
$ref: '#/components/responses/Unprocessable'
x-status: planned
/locations/{locationId}/promotions/validate:
post:
tags:
- Promotions
operationId: validatePromotion
summary: Check a coupon code against a cart without placing an order
parameters:
- $ref: '#/components/parameters/locationId'
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- code
- cart
properties:
code:
type: string
cart:
$ref: '#/components/schemas/Cart'
email:
type: string
description: for single-use-per-customer coupons
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
valid:
type: boolean
discount:
type: integer
description: cents
description:
type: string
reason:
type: string
description: present when valid=false (EXPIRED, MIN_NOT_MET, ALREADY_USED, NOT_FOUND)
x-status: planned
/locations/{locationId}/giftcards/balance:
post:
tags:
- GiftCards
operationId: giftCardBalance
summary: Live gift card balance at a location
description: 'Asks the location''s own gift card system. `approved: false` (HTTP 200) means the
system did not
recognize the card. `409 GIFT_NOT_SUPPORTED` means the location has no gift card system
(`features.giftCards` on the location says so up front).
**Gift card numbers are not payment card data, but treat them with care:** the number alone can
spend the card. Do not log it or show more than the last 4 digits. This endpoint is POST so the
number stays out of URLs, and it is rate-limited to 10 lookups per 10 minutes per API key and
per IP
(`429 RATE_LIMITED`, honour `Retry-After`). Check a balance when the guest enters a card, not
in a loop.
'
parameters:
- $ref: '#/components/parameters/locationId'
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- number
properties:
number:
type: string
description: The gift card number
example: '6036000000000001111'
pin:
type: string
description: Only if the card has one. Passed to the gift system, never stored.
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
approved:
type: boolean
balance:
type: integer
description: cents; present when approved
currency:
type: string
example: USD
last4:
type: string
provider:
type: string
description: the location's gift system
example: shift4
message:
type: string
description: why, when not approved
'400':
$ref: '#/components/responses/Unprocessable'
'409':
$ref: '#/components/responses/Conflict'
'429':
$ref: '#/components/responses/RateLimited'
x-status: live
/orders/quote:
post:
tags:
- Orders
operationId: quoteOrder
summary: Validate and price a cart exactly as POST /orders would, without placing it
description: 'Runs the full order pipeline in dry-run: item/modifier resolution, 86 check, hours/slot
check, min order, delivery zone, discounts, tax, fees, tip and (for POS-priced stores) the
POS price check. Returns every problem at once in `issues[]` (HTTP 200) so a client can
fix the cart in one round trip. A quote still prices the cart when the location is paused, not
yet live or closed: orderable is false and issues[] says why, while lines and totals are real,
so an integration can be built before go-live. The returned `quoteId` may be passed to `POST /orders`
within `expiresAt`; if the server total has changed by then, the order is rejected with
`PRICE_CHANGED` and the new totals.
Send `payment.gifts` to check a split: `totals.giftCardApplied` and `totals.amountDue` come back,
and a split that does not add up is listed in `issues`. A quote does not look up balances; use
the
balance endpoint for that. `POST /orders` checks every card again before spending any of them.
'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/OrderRequest'
responses:
'200':
description: Priced (check `issues` — a quote can price and still be unorderable)
content:
application/json:
schema:
$ref: '#/components/schemas/Quote'
'404':
$ref: '#/components/responses/NotFound'
x-status: live
/orders:
post:
tags:
- Orders
operationId: createOrder
summary: Place an order
description: 'Re-validates and re-prices server-side. Payment is then taken/verified for exactly
the
server total, the order is persisted, and it is sent to the POS.
**Payment is verified, never trusted.** `cardPresent` must reference a `/payments/v1`
record that is `approved`, belongs to this location, is unused, and whose amount equals
the order total. `cardToken` / `wallet` are charged by the server. A client can never
mark an order paid by itself.
If POS submission fails after payment, the server voids/refunds and returns
`502 POS_REJECTED` with `payment.status` — the client does not need to void.
**Gift cards.** `payment.gifts` lists 1 to 5 cards, each with the amount to take from it. Use
them
alone (`method: giftCard`, cards must equal the total), with `prepaid` (cards + `prepaid.amount`
must
equal the total, e.g. a $75 order as a $50 gift card + $25 on your Stripe checkout), or with
`payAtStore` (cards up to the total; the store collects the rest and the ticket says how much).
Every balance is checked before any card is spent, and a card short of its amount refuses the
order
with `402 GIFT_INSUFFICIENT` and that card''s balance.
**A failed order never consumes a balance.** If a card fails partway, the cards already redeemed
are
voided and the order is refused with `402 GIFT_DECLINED`; if the order cannot be saved you get
`502 POS_REJECTED`. `error.details.payment.gifts` says what happened to each card. If the POS
refuses an
order after it was accepted, its gift redemptions are voided and `GET /orders/{orderId}` shows
`payment.gifts[].status: voided` and `pos.status: rejected`; any `prepaid` part is yours to refund.
An
order that is only held because the store''s POS is offline keeps its redemptions: the store still
gets it.
Retrying with the same `Idempotency-Key` (or the same `prepaid.reference`) returns the first order
and
spends nothing. A retry that overlaps a first attempt still running gets `409 IDEMPOTENCY_CONFLICT`;
retry it a few seconds later.
'
parameters:
- $ref: '#/components/parameters/idempotencyKey'
requestBody:
required: true
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/OrderRequest'
- type: object
required:
- customer
- payment
properties:
quoteId:
type: string
expectedTotal:
type: integer
description: Cents. If given and ≠ server total, reject with PRICE_CHANGED before
charging.
responses:
'201':
description: Order accepted
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
'200':
description: Idempotent replay of an order already created with this key
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
'402':
$ref: '#/components/responses/PaymentFailed'
'409':
$ref: '#/components/responses/Conflict'
'422':
$ref: '#/components/responses/Unprocessable'
'502':
$ref: '#/components/responses/PosRejected'
x-status: live
/orders/{orderId}:
get:
tags:
- Orders
operationId: getOrder
summary: Order status and receipt
description: 'Callable with the API key that created the order, or with the order''s `accessToken`
(returned at creation; safe to put in a customer-facing tracking link). Order ids are
not sequential and are not the POS/restaurant order number.
'
security:
- apiKey: []
- orderToken: []
parameters:
- $ref: '#/components/parameters/orderId'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
'404':
$ref: '#/components/responses/NotFound'
x-status: live
/orders/{orderId}/arrival:
post:
tags:
- Orders
operationId: notifyArrival
summary: Curbside "I'm here"
security:
- apiKey: []
- orderToken: []
parameters:
- $ref: '#/components/parameters/orderId'
requestBody:
content:
application/json:
schema:
type: object
properties:
vehicle:
$ref: '#/components/schemas/Vehicle'
parkingSpot:
type: string
responses:
'204':
description: Merchant notified
x-status: planned
/customers/token:
post:
tags:
- Customers
operationId: customerLogin
summary: Exchange customer credentials (or an SMS one-time code) for a customer bearer token
description: Scoped to one merchant. Replaces the `hash`/`username` cookie login for API clients.
requestBody:
required: true
content:
application/json:
schema:
oneOf:
- type: object
required:
- locationId
- email
- password
properties:
locationId:
type: string
email:
type: string
password:
type: string
- type: object
required:
- locationId
- phone
- otp
properties:
locationId:
type: string
phone:
type: string
otp:
type: string
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
accessToken:
type: string
expiresAt:
type: string
format: date-time
customer:
$ref: '#/components/schemas/Customer'
'401':
$ref: '#/components/responses/Unauthorized'
x-status: planned
/customers/me/orders:
get:
tags:
- Customers
operationId: myOrders
summary: Order history (reorder source)
security:
- customerToken: []
parameters:
- name: locationId
in: query
schema:
type: string
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
orders:
type: array
items:
$ref: '#/components/schemas/Order'
x-status: planned
/customers/me/payment-methods:
get:
tags:
- Customers
operationId: myPaymentMethods
summary: Saved cards (tokens only; brand + last4)
security:
- customerToken: []
responses:
'200':
description: OK
content:
application/json:
schema:
type: array
items:
type: object
properties:
id:
type: string
brand:
type: string
last4:
type: string
expMonth:
type: integer
expYear:
type: integer
description: 4-digit
x-status: planned
/customers/me/loyalty:
get:
tags:
- Loyalty
operationId: myLoyalty
summary: Points balance, punch cards and redeemable rewards at a location
security:
- customerToken: []
parameters:
- name: locationId
in: query
required: true
schema:
type: string
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/LoyaltyAccount'
x-status: planned
webhooks:
order.updated:
post:
tags:
- Webhooks
summary: Sent to the client's registered URL whenever an order's status changes
description: 'Signed like our Shift4 loyalty inbound: header `X-RTO-Signature: t=<unix>,v1=<hex>`
where
v1 = HMAC-SHA256(secret, t + "." + rawBody). Reject if |now - t| > 300s. Retries with
exponential backoff for 24h; `eventId` is stable across retries.
'
requestBody:
content:
application/json:
schema:
type: object
properties:
eventId:
type: string
type:
type: string
const: order.updated
createdAt:
type: string
format: date-time
order:
$ref: '#/components/schemas/Order'
responses:
2XX:
description: Acknowledged
components:
securitySchemes:
apiKey:
type: http
scheme: bearer
description: '`Authorization: Bearer <key>`. Keys are issued per client (kiosk fleet, app or partner)
and scoped to named locations. RTO kiosks use their enrolled device token here, limited to their
own location.'
orderToken:
type: apiKey
in: query
name: token
description: Per-order read token returned as `Order.accessToken`.
customerToken:
type: http
scheme: bearer
description: From POST /customers/token. Must be sent together with the client's apiKey via `X-RTO-Api-Key`.
parameters:
locationId:
name: locationId
in: path
required: true
description: Restaurant orderUrl
schema:
type: string
orderId:
name: orderId
in: path
required: true
schema:
type: string
idempotencyKey:
name: Idempotency-Key
in: header
required: true
schema:
type: string
format: uuid
responses:
NotFound:
description: Unknown location, item or order
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Unauthorized:
description: Missing/invalid credentials
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Unprocessable:
description: Cart/order cannot be placed as sent. `error.details.issues` lists every problem.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Conflict:
description: PRICE_CHANGED (details.quote has new totals), IDEMPOTENCY_CONFLICT, PAYMENT_ALREADY_USED,
or GIFT_NOT_SUPPORTED
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
PaymentFailed:
description: PAYMENT_DECLINED / PAYMENT_AMOUNT_MISMATCH / PAYMENT_NOT_APPROVED / GIFT_INSUFFICIENT
/ GIFT_DECLINED. Nothing sent to the POS; any gift card already redeemed was voided (see details.payment.gifts).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
PosRejected:
description: POS refused the order; payment already voided/refunded (see details.payment).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
RateLimited:
description: Too many requests; honour Retry-After
headers:
Retry-After:
schema:
type: integer
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
schemas:
Error:
type: object
required:
- error
properties:
error:
type: object
required:
- code
- message
properties:
code:
type: string
enum:
- UNAUTHORIZED
- FORBIDDEN
- NOT_FOUND
- VALIDATION_FAILED
- LOCATION_UNAVAILABLE
- LOCATION_CLOSED
- ORDER_TYPE_UNAVAILABLE
- SLOT_UNAVAILABLE
- ITEM_UNAVAILABLE
- ITEM_NOT_ON_MENU
- INVALID_MODIFIERS
- BELOW_MINIMUM
- ABOVE_MAXIMUM
- OUTSIDE_DELIVERY_ZONE
- PROMOTION_INVALID
- PRICE_CHANGED
- IDEMPOTENCY_CONFLICT
- PAYMENT_DECLINED
- PAYMENT_AMOUNT_MISMATCH
- PAYMENT_NOT_APPROVED
- PAYMENT_ALREADY_USED
- PAYMENT_METHOD_NOT_ALLOWED
- GIFT_NOT_SUPPORTED
- GIFT_INSUFFICIENT
- GIFT_DECLINED
- POS_REJECTED
- RATE_LIMITED
- INTERNAL
message:
type: string
description: Human readable; safe to show a customer
details:
type: object
properties:
issues:
type: array
items:
$ref: '#/components/schemas/Issue'
quote:
$ref: '#/components/schemas/Quote'
payment:
$ref: '#/components/schemas/PaymentResult'
requestId:
type: string
Issue:
type: object
required:
- code
- message
properties:
code:
type: string
description: One of Error.code values
message:
type: string
lineId:
type: string
description: Cart line the issue refers to
itemId:
type: string
modifierGroupId:
type: string
nextAvailableTime:
type: string
format: date-time
OrderType:
type: string
enum:
- pickup
- curbside
- delivery
- dineIn
description: 'pickup = carry-out; curbside = pickup + vehicle; delivery = own fleet or DaaS (see
Location.orderTypes[].deliveryProvider); dineIn = table service / kiosk eat-in.
Catering is a pickup/delivery order with a future `requestedTime` (Location.catering rules).
'
Location:
type: object
properties:
locationId:
type: string
name:
type: string
phone:
type: string
address:
$ref: '#/components/schemas/Address'
timeZone:
type: string
example: America/Los_Angeles
imageUrl:
type: string
logoUrl:
type: string
brandColor:
type: string
acceptingOrders:
type: boolean
description: false when paused/unavailable (admin pause-for-today)
openNow:
type: boolean
taxRate:
type: string
example: '0.0825'
currency:
type: string
example: USD
orderTypes:
type: array
items:
type: object
properties:
type:
$ref: '#/components/schemas/OrderType'
enabled:
type: boolean
asapAllowed:
type: boolean
futureOrdersAllowed:
type: boolean
maxDaysAhead:
type: integer
prepTimeMinutes:
type: integer
minimumOrder:
type: integer
description: cents
maximumOrder:
type: integer
description: cents; 0 = none
deliveryProvider:
type: string
enum:
- merchant
- doordash
- uber
- firstDelivery
description: delivery only
vehicleInfoRequired:
type: boolean
instructions:
type: string
paymentMethods:
type: array
items:
type: string
enum:
- card
- applePay
- googlePay
- giftCard
- payAtStore
tipping:
type: object
properties:
enabled:
type: boolean
suggestions:
type: array
items:
type: object
properties:
percent:
type: integer
isDefault:
type: boolean
fees:
type: array
description: Service / convenience / CC fees that may appear on the quote
items:
type: object
properties:
type:
type: string
enum:
- service
- convenience
- creditCard
- bag
- deposit
label:
type: string
flat:
type: integer
description: cents
percent:
type: string
taxable:
type: boolean
guestCheckout:
type: object
properties:
allowed:
type: boolean
emailRequired:
type: boolean
phoneRequired:
type: boolean
cardTokenization:
type: object
description: How a client obtains a `cardToken` for this location
properties:
provider:
type: string
enum:
- shift4I4go
- stripe
- datacap
publicConfig:
type: object
additionalProperties: true
features:
type: object
properties:
specialInstructions:
type: boolean
coupons:
type: boolean
giftCards:
type: boolean
loyalty:
type: boolean
languages:
type: array
items:
type: string
example:
- en
- es
Menu:
type: object
properties:
menuId:
type: string
name:
type: string
currency:
type: string
updatedAt:
type: string
format: date-time
categories:
type: array
items:
$ref: '#/components/schemas/MenuCategory'
MenuCategory:
type: object
properties:
id:
type: string
name:
type: string
description:
type: string
imageUrl:
type: string
sortOrder:
type: integer
availability:
$ref: '#/components/schemas/Availability'
items:
type: array
items:
$ref: '#/components/schemas/MenuItem'
MenuItem:
type: object
properties:
id:
type: string
name:
type: string
description:
type: string
imageUrl:
type: string
price:
type: integer
description: cents; base price when there are no sizes
sizes:
type: array
description: Mutually exclusive priced variants (exactly one must be chosen when present)
items:
type: object
properties:
id:
type: string
name:
type: string
price:
type: integer
available:
type: boolean
isDefault:
type: boolean
modifierGroups:
type: array
items:
$ref: '#/components/schemas/ModifierGroup'
available:
type: boolean
description: false = 86'd / sold out
availability:
$ref: '#/components/schemas/Availability'
taxRate:
type: string
description: overrides location taxRate when present
tags:
type: array
items:
type: string
example:
- alcohol
- spicy
- vegetarian
minimumAge:
type: integer
maxQuantity:
type: integer
specialInstructionsAllowed:
type: boolean
posId:
type: string
description: POS PLU / item GUID, informational
ModifierGroup:
type: object
properties:
id:
type: string
name:
type: string
minSelections:
type: integer
maxSelections:
type: integer
description: 0 = unlimited
maxPerModifier:
type: integer
description: 'how many times one modifier may be chosen on a line. Currently always 1: a modifier
is applied once; the group''s minSelections/maxSelections are the POS rules. Order more of
a dish by raising the line quantity.'
style:
type: string
enum:
- standard
- pizzaTopping
description: pizzaTopping allows placement left/right/whole
modifiers:
type: array
items:
$ref: '#/components/schemas/Modifier'
Modifier:
type: object
properties:
id:
type: string
name:
type: string
price:
type: integer
description: cents
sizePrices:
type: object
additionalProperties:
type: integer
description: sizeId → price when price depends on the chosen size
halfPrice:
type: integer
description: cents when placed on one half (pizzaTopping)
isDefault:
type: boolean
description: pre-selected / included
available:
type: boolean
modifierGroups:
type: array
description: nested groups (e.g. "choose dressing" under a side)
items:
$ref: '#/components/schemas/ModifierGroup'
Availability:
type: object
description: When absent, available whenever the location is open.
properties:
windows:
type: array
items:
type: object
properties:
days:
type: array
items:
type: string
enum:
- mon
- tue
- wed
- thu
- fri
- sat
- sun
start:
type: string
example: '11:00'
end:
type: string
example: '15:00'
orderTypes:
type: array
items:
$ref: '#/components/schemas/OrderType'
Schedule:
type: object
properties:
locationId:
type: string
orderType:
$ref: '#/components/schemas/OrderType'
timeZone:
type: string
now:
type: string
format: date-time
asapAvailable:
type: boolean
asapEstimate:
type: string
format: date-time
nextOpen:
type: string
format: date-time
days:
type: array
items:
type: object
properties:
date:
type: string
format: date
closed:
type: boolean
closedReason:
type: string
enum:
- closed
- holiday
- paused
hours:
type: array
items:
type: object
properties:
open:
type: string
format: date-time
close:
type: string
format: date-time
slots:
type: array
items:
type: object
properties:
time:
type: string
format: date-time
available:
type: boolean
reason:
type: string
enum:
- full
- pastCutoff
- leadTime
Address:
type: object
properties:
line1:
type: string
line2:
type: string
city:
type: string
state:
type: string
postalCode:
type: string
country:
type: string
default: US
lat:
type: number
lng:
type: number
instructions:
type: string
description: gate code, apartment notes
Vehicle:
type: object
properties:
make:
type: string
model:
type: string
color:
type: string
plate:
type: string
DeliveryQuote:
type: object
properties:
deliverable:
type: boolean
reason:
type: string
enum:
- outsideZone
- belowMinimum
- providerUnavailable
- closed
fee:
type: integer
description: cents
minimumOrder:
type: integer
estimatedDeliveryTime:
type: string
format: date-time
provider:
type: string
zoneId:
type: string
Cart:
type: object
required:
- locationId
- lines
properties:
locationId:
type: string
lines:
type: array
minItems: 1
items:
$ref: '#/components/schemas/CartLine'
CartLine:
type: object
required:
- itemId
- quantity
properties:
lineId:
type: string
description: client-chosen; echoed in issues and receipt
itemId:
type: string
sizeId:
type: string
quantity:
type: integer
minimum: 1
modifiers:
type: array
items:
$ref: '#/components/schemas/CartModifier'
specialInstructions:
type: string
maxLength: 140
forName:
type: string
description: label for whom the item is (party orders)
CartModifier:
type: object
required:
- modifierId
properties:
groupId:
type: string
modifierId:
type: string
quantity:
type: integer
minimum: 1
default: 1
placement:
type: string
enum:
- whole
- left
- right
default: whole
modifiers:
type: array
items:
$ref: '#/components/schemas/CartModifier'
OrderRequest:
allOf:
- $ref: '#/components/schemas/Cart'
- type: object
required:
- orderType
properties:
orderType:
$ref: '#/components/schemas/OrderType'
requestedTime:
type: string
format: date-time
description: Omit for ASAP. Must be an available slot from /schedule. ASAP on a closed store
is rejected (LOCATION_CLOSED) — never silently rolled to the next day.
deliveryAddress:
$ref: '#/components/schemas/Address'
vehicle:
$ref: '#/components/schemas/Vehicle'
tableNumber:
type: string
tip:
type: object
properties:
amount:
type: integer
description: cents
percent:
type: integer
description: alternative to amount; server computes
promoCode:
type: string
notes:
type: string
maxLength: 250
description: order-level instructions
customer:
$ref: '#/components/schemas/Customer'
payment:
$ref: '#/components/schemas/PaymentRequest'
source:
type: object
properties:
channel:
type: string
enum:
- kiosk
- app
- web
- partner
deviceId:
type: string
partnerOrderId:
type: string
Customer:
type: object
properties:
firstName:
type: string
lastName:
type: string
email:
type: string
format: email
phone:
type: string
description: E.164 or 10 digits; server normalizes (formatted phones must never fail a paid
order)
marketingOptIn:
type: boolean
PaymentRequest:
type: object
required:
- method
properties:
method:
type: string
enum:
- cardToken
- wallet
- cardPresent
- softPos
- savedCard
- giftCard
- payAtStore
- prepaid
prepaid:
type: object
description: '*Live.* Paid on your own storefront before you send the order -- for example a
Stripe Checkout session -- so the kitchen rings it as paid and asks the guest for nothing.
Partner API keys only. Quote first and charge exactly the quoted `totals.total`: `amount`
must equal the server total or the order is refused with `402 PAYMENT_AMOUNT_MISMATCH` and
nothing reaches the kitchen. The `reference` is used once: sending the same payment again
returns the first order, never a second. Refunds of a prepaid order are made on your side,
with your processor.'
required:
- provider
- reference
- amount
properties:
provider:
type: string
description: Who took the payment, e.g. stripe
example: stripe
reference:
type: string
maxLength: 120
description: Your processor's payment id, e.g. a Stripe PaymentIntent pi_...
example: pi_3QxExample
amount:
type: integer
description: cents charged; must equal the quote total
example: 2716
cardToken:
type: object
description: Card-not-present token from Location.cardTokenization (e.g. Shift4 i4Go TrueToken).
Server charges it.
properties:
token:
type: string
postalCode:
type: string
wallet:
type: object
properties:
type:
type: string
enum:
- applePay
- googlePay
token:
type: string
cardPresent:
type: object
description: A kiosk pinpad payment already approved via /payments/v1/start. Server verifies
location, amount and single use.
properties:
paymentId:
type: string
softPos:
type: object
description: On-device Datacap (dsiEMVAndroid) result. Only accepted from a device enrolled
for SoftPOS; approvedAmount must equal the server total.
properties:
recordNo:
type: string
authCode:
type: string
refNo:
type: string
approvedAmount:
type: string
description: dollars, e.g. 27.16
cardType:
type: string
acctNoMasked:
type: string
savedCardId:
type: string
gifts:
type: array
maxItems: 5
description: '*Live.* Gift cards, each for a fixed amount. Alone with method `giftCard`, or
combined with `prepaid` or `payAtStore` for the remainder. The same card may not appear twice.
Numbers are not payment card data but can spend the card: never log them. A pin is passed
to the gift system and never stored.'
items:
type: object
required:
- number
- amount
properties:
number:
type: string
example: '6036000000000001111'
pin:
type: string
amount:
type: integer
description: cents to take from this card
example: 5000
loyaltyRewardIds:
type: array
items:
type: string
Quote:
type: object
properties:
quoteId:
type: string
expiresAt:
type: string
format: date-time
orderable:
type: boolean
description: false when issues[] is non-empty
issues:
type: array
items:
$ref: '#/components/schemas/Issue'
lines:
type: array
items:
$ref: '#/components/schemas/PricedLine'
totals:
$ref: '#/components/schemas/Totals'
estimatedReadyTime:
type: string
format: date-time
upsells:
type: array
items:
type: object
properties:
itemId:
type: string
name:
type: string
price:
type: integer
PricedLine:
type: object
properties:
lineId:
type: string
itemId:
type: string
name:
type: string
sizeName:
type: string
quantity:
type: integer
unitPrice:
type: integer
description: cents incl. modifiers
total:
type: integer
modifiers:
type: array
items:
type: object
properties:
modifierId:
type: string
name:
type: string
placement:
type: string
price:
type: integer
specialInstructions:
type: string
Totals:
type: object
description: total = subtotal - discount + fees + deliveryFee + tax + tip. All cents.
properties:
subtotal:
type: integer
discount:
type: integer
fees:
type: array
items:
type: object
properties:
type:
type: string
label:
type: string
amount:
type: integer
deliveryFee:
type: integer
tax:
type: integer
tip:
type: integer
total:
type: integer
giftCardApplied:
type: integer
amountDue:
type: integer
description: total - giftCardApplied - loyalty; what the primary method is charged
PaymentResult:
type: object
properties:
method:
type: string
status:
type: string
enum:
- pending
- approved
- declined
- voided
- refunded
- dueAtStore
amount:
type: integer
brand:
type: string
last4:
type: string
authCode:
type: string
reference:
type: string
description: processor reference (for support/refunds)
gifts:
type: array
description: One entry per gift card on the order. Absent when there are none.
items:
type: object
properties:
last4:
type: string
amount:
type: integer
description: cents
status:
type: string
enum:
- redeemed
- voided
description: In an error response also declined, insufficient or voidFailed; voidFailed
means our staff are reversing it by hand.
balance:
type: integer
description: cents; on GIFT_INSUFFICIENT only
message:
type: string
Order:
type: object
properties:
orderId:
type: string
description: opaque, non-sequential
orderNumber:
type: string
description: human/restaurant-facing number shown on tickets
accessToken:
type: string
description: returned on create only; lets GET /orders/{id}?token= be used from a customer tracking
link
locationId:
type: string
status:
type: string
enum:
- received
- scheduled
- sentToPos
- confirmed
- ready
- outForDelivery
- completed
- cancelled
- failed
statusReason:
type: string
orderType:
$ref: '#/components/schemas/OrderType'
placedAt:
type: string
format: date-time
requestedTime:
type: string
format: date-time
estimatedReadyTime:
type: string
format: date-time
lines:
type: array
items:
$ref: '#/components/schemas/PricedLine'
removedLines:
type: array
description: Always empty for API orders — sold-out items reject the order (ITEM_UNAVAILABLE)
rather than being silently dropped after payment.
items:
type: string
totals:
$ref: '#/components/schemas/Totals'
payment:
$ref: '#/components/schemas/PaymentResult'
customer:
$ref: '#/components/schemas/Customer'
deliveryAddress:
$ref: '#/components/schemas/Address'
delivery:
type: object
properties:
provider:
type: string
trackingUrl:
type: string
driverName:
type: string
pos:
type: object
properties:
system:
type: string
example: skytab
checkNumber:
type: string
status:
type: string
enum:
- pending
- sent
- acknowledged
- rejected
trackingUrl:
type: string
LoyaltyAccount:
type: object
properties:
enrolled:
type: boolean
points:
type: integer
pointsValue:
type: integer
description: cents
punchCards:
type: array
items:
type: object
properties:
id:
type: string
name:
type: string
punches:
type: integer
threshold:
type: integer
rewards:
type: array
items:
type: object
properties:
id:
type: string
name:
type: string
pointsCost:
type: integer
value:
type: integer
redeemable:
type: boolean
Questions or partner access: emad@realtimeordering.com.