Coverage and limitations
What this API does not do, stated plainly and in one place — so you design against facts rather than assumptions, and never discover a gap by failing in production. Where a limit belongs to a delivery platform we say so; where it is ours, we say that too.
Platform-dependent coverage
Delivery platforms expose different capabilities to integrations. OneTablet normalizes what can be normalized and documents the rest.
| Capability | DoorDash | Uber Eats | Grubhub |
|---|---|---|---|
| Orders (read + webhooks) | Yes | Yes | Yes |
| Order-level promotion attribution | Yes — merchant-funded and co-funded only | Yes | Partial |
| Store status read + discrepancy | Yes | Yes | Yes |
| Pause / unpause | Yes | Yes | Yes — no pause-reason support platform-side |
| Item availability | Yes | Yes | Yes |
| Reports | Not available — platform-side constraint | Yes | Yes |
| Menu publish | Yes — confirms | Yes — acknowledges | Yes — acknowledges |
| Promotions read / write (Phase 3) | Read-only | Read + write | No API surface |
Per-platform report types, window constraints and official-docs pointers live in the Platform report catalogue. From Phase 2 the same matrix is readable programmatically via fetchReportCoverage, so an agent can discover a gap instead of being told about one.
Notes worth designing around
- Reporting on DoorDash is a platform-side constraint, not a OneTablet gap: DoorDash does not currently give our integration category programmatic report access. Until it does,
fetchReportCoveragereturns those rows as unavailable with that reason attached, and the interim path is the DoorDash merchant-portal CSV export. - Menu publish confirmation differs by platform. Some platforms confirm a menu went live (
confirmed); others only acknowledge the submission (acknowledged). The publication status tells you which one you got — we never report the weaker signal as the stronger one. - Promotion attribution is structurally incomplete on some platforms. Promotions funded entirely by the platform are not transmitted to anyone, so no integration can attribute them. Attribution covers merchant-funded and co-funded activity.
- Promotions (Phase 3) follow each platform's ceiling. Uber Eats offers integrations a full create / read / revoke lifecycle, and promotions are immutable once created — there is no update or pause platform-side. DoorDash restaurant storefronts expose no promotion write API to integrations; campaigns are created in DoorDash's own tools and integrations receive order-level attribution. Grubhub exposes no promotions API surface at all; promotion amounts arrive on order payloads and are carried through the order's financial breakdown. Platform APIs change on their own schedule; the platforms' own documentation is authoritative.
Not available
| Capability | Status |
|---|---|
| Reviews and ratings | Not available — platform access varies. On the roadmap, no phase committed. |
| Sponsored placement / advertising spend (listing bids, boost budgets) | Not exposed to any integration by the platforms. |
| Order lifecycle writes (accept, mark ready, cancel) | Deliberately absent. Those actions happen in the kitchen, on the OneTablet tablet, so this API can never contradict the people actually cooking. |
Per-observation timestamps on store status (observed.observedAt, discrepancy.detectedAt) | Not reported, and not planned. lastPolledAt is the freshness signal — see below and Data freshness. |
Semantics that surprise integrators
Read these once. Each is the root cause of a predictable integration bug.
- Saving a menu is not publishing it. Menu edits change OneTablet state only. Guests see a change after an explicit publish, which is asynchronous and per-platform.
- Item availability applies at the kitchen (location) level and fans out to every storefront selling the item. There is no per-storefront 86: a store-scoped availability call resolves to the parent location and affects sibling storefronts too.
fetchStoreItemsis store-_addressed_, not store-_scoped_, for exactly this reason, and echoes thelocationIdit answered from. What it does narrow is the brand — a kitchen hosts several — so you see the storefront's own catalogue, not its neighbours'. - Item availability is what OneTablet intends, not what the platform shows. A platform can mark an item unavailable on its own side, and no read here reports that. It is the item-level counterpart of the
desired/observedsplitfetchStoreStatusmakes explicit for the storefront. - A modifier's availability can cascade to parent items. The write response enumerates everything that actually changed — read it rather than assuming the blast radius.
- Webhooks are at-least-once and unordered. Deduplicate, order by the transported state, and reconcile from the reads. See Webhooks.
- Order history is queried by creation time, with an inclusive lower bound and an exclusive upper one. There is no maximum window width and no rolling retention ceiling, so history stays queryable well beyond any platform's export window and a full backfill can be one request's window paged with the cursor. There is no pickup-time filter: an order's pickup time is a different timestamp, and windowing on creation time is what makes a backfill exact — every order is created exactly once, so contiguous windows partition your history with no gap and no overlap. Filtering by
orderIds,locationCode,storeIdandstatusesworks alongside the window, and the page is sorted by creation time in whichever direction you ask for. locationCodeandstoreIdcannot be combined onfetchOrders— sending both is a400. A storefront is already inside exactly one kitchen, so the pair is either redundant or contradictory; send whichever one you mean.- Observed platform state has a freshness window. Check
lastPolledAtbefore acting — see Data freshness. lastPolledAtis the only timestamp on a store status. There is no per-observation timestamp (observed.observedAt) and no "first detected" timestamp on a discrepancy (discrepancy.detectedAt); neither is reported, and neither is planned. A discrepancy is a standing condition rather than an event, so it is true as oflastPolledAtand you re-read to see whether it still holds. Both fields stay optional in the schema — they are simply never populated, so branching on their presence is dead code.- A published-but-unimplemented operation answers
501, not an empty result. An empty page would be a factual claim that nothing matched, and an integration is entitled to act on it. See Rollout phases.