Skip to main content
Under construction. This API and its documentation are actively evolving. Endpoints, payload shapes, and behavior described here are subject to change without notice. Please confirm specifics with the Maple team before relying on any detail for production work.

The short version

A basic ordering integration is three pieces of work:
#What you buildAPI surfaceTypical effort
1Publish your menuOne endpoint, one JSON document~1–2 days
2Receive and decide ordersOne webhook endpoint + accept/deny/status calls~2–3 days
3Keep stock in sync (optional on day 1)Read/write stock status by stable ID~half a day
Everything is plain JSON over HTTPS. There is no SDK to install, no payment processing to build (Maple owns the entire customer payment lifecycle), no incremental-sync protocol to maintain, and no price math to implement. You develop against a dedicated sandbox with test credentials before touching anything live. A working end-to-end integration is realistic in about a week of engineering.

Step 0 — Onboarding (we do this with you)

Maple creates your developer application and hands you:
  • Test credentials for the sandbox environment (https://sandbox.dev-api.maple.inc/v1). Live credentials (https://dev-api.maple.inc/v1) are enabled once your integration is approved. The two environments are fully isolated — test credentials can never touch live merchant data.
  • Auth: an API key for early development, with OAuth client-credentials available for long-term server-to-server access. Access is scoped (menus:write, orders:read, orders:write, …) so your app holds only what it needs.
  • A webhook URL you register, plus a signing secret for verifying our deliveries. A built-in test endpoint sends you a signed sample event so you can verify your handler before any real traffic.
Each restaurant you integrate is a location that the merchant grants your app access to. You can list your granted locations at any time (GET /locations).

Step 1 — Publish your menu (one endpoint, one document)

You send your entire menu as one JSON document to a single endpoint:
POST /locations/{locationId}/menu
The document is the catalog: categories → items → variations (the price carriers) → modifiers (nested to any depth), with your own IDs throughout. The minimum valid catalog is just a currency and your category tree:
{
  "currency": "USD",
  "categories": [
    {
      "externalId": "cat-drinks",
      "name": "Drinks",
      "items": [
        {
          "externalId": "itm-latte",
          "name": "Latte",
          "variations": [
            { "externalId": "var-sm", "name": "Small",
              "price": { "amount": { "amountMinor": 450, "currency": "USD" } } },
            { "externalId": "var-lg", "name": "Large",
              "price": { "amount": { "amountMinor": 550, "currency": "USD" } } }
          ],
          "modifierGroups": [
            { "externalId": "mg-milk", "name": "Milk",
              "minSelections": 1, "maxSelections": 1,
              "options": [
                { "externalId": "mo-whole", "name": "Whole" },
                { "externalId": "mo-oat", "name": "Oat",
                  "price": { "amount": { "amountMinor": 75, "currency": "USD" } } }
              ] }
          ]
        }
      ]
    }
  ]
}
What makes this easy to build against:
  • One write, no sync protocol. You always send the complete document; the server diffs it against the current catalog. There is no object-by-object CRUD, no dependency ordering, no orphan cleanup on your side. Your nightly “export menu → publish” job is the whole sync.
  • Your IDs are the keys, and they’re stable. Every object carries your externalId, and the server matches on it across publishes — renames, reprices, and reorders never change an object’s identity. All cross-references inside the document use your IDs, so you map straight from your existing data model with no translation tables.
  • Republishing is safe. Publishing an identical document is a no-op. Stock status (an 86’d item, say) survives republishes untouched unless you explicitly set it. You never have to coordinate menu pushes with operational state.
  • Validation is all-or-nothing and tells you everything at once. A bad document writes nothing and returns every issue in one response, pointing at the objects in question — so you fix the whole batch in one pass, without back-and-forth with our team.
  • Shared modifier groups. Define a group like “Toppings” once and reference it from many items, with per-item overrides (two free toppings on the pizza, max two on the slice). Your menu document stays as small as your menu actually is.
Money is always integer minor units + ISO-4217 currency ({ "amountMinor": 450, "currency": "USD" } = $4.50) — no float rounding. Large menus process as an async job you can poll (GET /locations/{locationId}/menu/syncs/{syncId}), and you get a menu.sync_completed / menu.sync_failed webhook either way.

Step 2 — Receive and decide orders (one webhook handler)

You host one HTTPS endpoint. When a customer places an order, Maple POSTs you an orders.notification event:
{
  "event_id": "evt_123",
  "event_type": "orders.notification",
  "location_id": "loc_123",
  "resource_id": "ord_123",
  "resource_type": "order",
  "resource_href": "https://dev-api.maple.inc/v1/orders/ord_123",
  "idempotency_key": "order:ord_123:notification"
}
You follow resource_href to fetch the full order. Every line references your menu by the externalIds from your published catalog, so routing the order into your system is a direct lookup against data you already own. Totals (subtotal, tax, tip, fees) arrive precomputed by Maple — you never calculate a price. Then you respond through simple decision endpoints as the order progresses:
CallWhen
POST /orders/{orderId}/acceptYou can fulfill it (include your POS ticket ID, estimated ready time)
POST /orders/{orderId}/denyYou can’t (structured reason codes: ITEM_AVAILABILITY, STORE_CLOSED, CAPACITY, …)
POST /orders/{orderId}/readyThe order is ready for pickup / courier handoff
POST /orders/{orderId}/completeThe order is fulfilled
That’s the whole lifecycle: pending → accepted → ready → fulfilled, with cancel/deny branches. You’ll also receive orders.cancelled if the customer or store cancels. Webhook handling is deliberately boring to implement:
  • Signature verification is standard HMAC-SHA256 with your signing secret (X-Maple-Signature + X-Maple-Timestamp headers) — a few lines in any language, and the subscription test endpoint lets you verify it before go-live.
  • Delivery is at-least-once; you dedupe by event_id. Every write you make carries an Idempotency-Key, so retries on your side are always safe too.
  • One webhook URL covers everything; you can opt into extra lifecycle echo events later if you want a full event ledger from Maple.
Payments: nothing to build. Maple owns the customer payment lifecycle end to end (Stripe-backed payment links). The order resource includes the payment status and link; your only job is to treat Maple’s totals as authoritative. No card data, no PCI scope, no payment provider integration on your side.
Optional quality hook — order validation. For POS-injection integrations, Maple can ask you to pre-validate an order (orders.validation_requested) before the customer finalizes it — you run the same availability check you’d run at accept time, just earlier, and reply via POST /orders/{orderId}/validation_result. It reuses the exact logic you build for accept/deny, so it adds almost nothing to the build.

Step 3 — Keep stock in sync (optional)

Stock is deliberately simple — a 3-state flag per orderable object (in_stock | out_of_stock | low_stock), no quantity tracking:
// updateStockStatus
[ { "id": "var_abc123", "stockStatus": "out_of_stock" } ]
getStockStatus returns one row per item, variation, and modifier option with the stable catalog id (and your externalId) to key on. Because IDs are stable across menu publishes, you can store these handles once and use them forever. If you skip this step on day 1, new objects default to in_stock and your accept/deny flow covers availability.

What a basic integration does not require

  • No payment integration. Maple creates and owns the payment link; you never touch a card.
  • No price engine. Taxes, fees, and discounts are published as data; Maple computes every customer-facing total.
  • No incremental sync protocol. Full-document publish with server-side diffing; IDs stay stable.
  • No polling. Orders are pushed to your webhook with a link to the canonical resource.
  • No SDK dependency. Plain JSON over HTTPS; any language works.
  • No guessing in production. You build and test entirely in the sandbox with test credentials, signed test webhooks, and isolated data before live access is enabled.

The growth path (same contract, more fields)

The catalog document you send in week one already has room for the full commerce model — you add fields to the same document, with no breaking change and no architectural rework:
  • Tax: categories and rates — percentage, flat, statutory brackets, compound tax-on-tax, tax-inclusive (VAT) pricing.
  • Fees: delivery, service charges, bag fees, bottle deposits/CRV, auto-gratuity, and more.
  • Discounts & promotions: percentage/amount/BOGO benefits, product-set targeting, scheduling windows, stacking policies, coupon codes with redemption limits.
  • Rich modifiers: nested to any depth, shared groups with per-item overrides, quantity rules (“first two toppings free”, “double” options), preselected defaults, free-text entries (cake inscriptions).
  • Merchandising metadata: SKUs, barcodes, dietary labels, allergens, calorie counts, day-parted availability (breakfast menus, happy hour).
  • Multi-currency: per-currency price overrides with correct minor-unit handling.
Beyond the catalog, the same developer app, auth, and webhook plumbing extends to delivery tracking (read-only courier status and ETA events) and, later, reservations — no new integration architecture, just new event types and endpoints.

Summary

You buildYou get
1 menu-publish job (your menu → one JSON doc)Your full menu live in Maple, IDs stable forever
1 webhook handler (verify, dedupe, route)Real-time orders pushed with precomputed totals
Accept/deny/ready/complete callsCustomers see live order progress
(Optional) stock syncAccurate availability without quantity logic
One document, one webhook, four decision calls — and payments, pricing, and delivery handled on our side. We provide the sandbox, test credentials, signed test events, and a seeded test location from day one, and our team is available throughout your build.