API reference

Last updated 2026-07-14

Delitero is launching soon. Join the waitlist and we will email you when it opens.

The public ordering API reads a restaurant and its menu, prices a cart, creates an order, and reports order status. It needs no authentication, and it is the same API the hosted ordering page and the embed run on, so it is exercised on every real order. Base URL:

https://api.delitero.com

Conventions: JSON in and out; money is integer cents; timestamps are epoch milliseconds; phone numbers are E.164. The contract is versioned and changes additively: fields are added, not removed or repurposed. The changelog records every change.

Read a restaurant

Terminal
curl https://api.delitero.com/v1/public/restaurants/your-restaurant

Returns the public restaurant document: name, currency, timezone, opening hours, fulfillment options (pickup, delivery fee and minimum), tax rate, pause state, and the current menu version. Cached at the edge for up to a minute (Cache-Control: public, max-age=15, s-maxage=60), so changes propagate within about 60 seconds.

A slug that exists but has never published answers 404 with error code restaurant_not_published and the restaurant's name in the body, so you can render "not open for online ordering yet". A slug that does not exist answers a plain 404 not_found.

Read the menu

Terminal
curl https://api.delitero.com/v1/public/restaurants/your-restaurant/menu

Returns the published menu snapshot: categories, items, and option groups, with live availability already merged in. An item marked out of stock shows available: false here without waiting for a publish. Item photos are served from /v1/public/images/ plus the item's imageKey (immutable, cached for a day; keys change when images change).

Quote a cart

Every price a customer sees should come from the quote endpoint. It runs the exact pricing pipeline of order creation (item and option prices, availability, tax, delivery fee and minimum, tip, promo code) without creating anything. Re-quote on every cart change; client-side money math will drift on rounding and availability.

JavaScript
const res = await fetch(
  'https://api.delitero.com/v1/public/restaurants/your-restaurant/quote',
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      type: 'pickup',
      items: [{ itemId: 'itm_…', quantity: 2, modifierIds: ['mod_…'] }]
    })
  }
)
const quote = await res.json()
// quote.subtotalCents, quote.taxCents, quote.totalCents, …
Terminal
curl -X POST https://api.delitero.com/v1/public/restaurants/your-restaurant/quote \
  -H 'Content-Type: application/json' \
  -d '{"type":"pickup","items":[{"itemId":"itm_…","quantity":2,"modifierIds":[]}]}'

Errors are semantic and customer-renderable: min_order_not_met, item_unavailable, modifier_unavailable, restaurant_closed, invalid_schedule, and the promo family (promo_invalid, promo_expired, promo_min_order, promo_exhausted) whose messages are written to be shown to the customer verbatim. Rate limit: 60 requests per minute per IP.

Create an order

Order creation re-prices everything server-side from the live menu (prices sent by the client are ignored), enforces availability, hours, delivery minimums, and option rules, and returns the numbers plus everything needed to take the payment.

JavaScript
const res = await fetch(
  'https://api.delitero.com/v1/public/restaurants/your-restaurant/orders',
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      type: 'pickup',
      customer: { name: 'Ada', phone: '+15551234567' },
      items: [{ itemId: 'itm_…', quantity: 2, modifierIds: [] }]
    })
  }
)
const order = await res.json()
// order.orderId, order.totalCents, order.payment.paymentIntentClientSecret, …

For delivery orders, send deliveryAddress with line1, city, state (two-letter USPS code, required), postalCode, and country: "US". Scheduled pickup sends scheduledFor (epoch ms) on a 15-minute grid inside opening hours, between now plus preparation time and 7 days out. Rate limit: 10 orders per minute per IP.

Refusals you should handle: 409 restaurant_closed, 409 restaurant_paused, 409 payments_not_ready (Stripe not connected yet), 400 invalid_schedule, 400 validation (the response names the offending field path), plus the quote error set above.

Take the payment

The response's payment object carries a Stripe PaymentIntent client secret, the restaurant's stripeAccountId, and a publishableKey. Three Stripe integration facts will save you an afternoon each:

  • Initialize Stripe.js with both the publishable key and stripeAccount: the PaymentIntent lives on the restaurant's connected account.
  • Pass layout: { type: 'accordion', defaultCollapsed: false } to the Payment Element; the default tabs layout can render collapsed against connected-account PaymentIntents.
  • confirmPayment requires a return_url even for cards; use redirect: 'if_required' and cards confirm inline without redirecting.
JavaScript
import { loadStripe } from '@stripe/stripe-js'

const stripe = await loadStripe(order.payment.publishableKey, {
  stripeAccount: order.payment.stripeAccountId
})

const elements = stripe.elements({
  clientSecret: order.payment.paymentIntentClientSecret
})
const payment = elements.create('payment', {
  layout: { type: 'accordion', defaultCollapsed: false }
})
payment.mount('#payment')

// on your pay button:
await stripe.confirmPayment({
  elements,
  confirmParams: { return_url: 'https://your-site.example/order-confirmed' },
  redirect: 'if_required'
})

Poll order status

Never declare success from the client: paid status enters through Stripe's webhook, so after confirming, poll the status endpoint until state leaves PENDING_PAYMENT. The unguessable order id is the capability; anyone with the id and slug can read the status, which is what makes customer status links work.

JavaScript
const res = await fetch(
  'https://api.delitero.com/v1/public/orders/' + order.orderId + '?restaurant=your-restaurant'
)
const status = await res.json()
// status.state: 'PAID' | 'ACCEPTED' | 'PREPARING' | 'READY' | …

The response carries the priced items, the full money breakdown, the promised estimatedReadyAt when the kitchen accepted with a preparation time, and a timeline array with the first timestamp each state was entered. A few seconds of polling interval is plenty; the hosted status page polls every 5 seconds and stops on terminal states.

Errors, generally

Every error is JSON: { "error": { "code": "…", "message": "…" } }. Unknown routes answer the same envelope with not_found. Codes are stable API surface; messages are for humans and may improve over time. Promo and quote refusal messages are written in customer voice so you can render them directly.