OneTablet API Documentation

Changelog

Changes to the Management API contract, newest first.

Evolution here is additive: new optional fields and new operations may arrive at any time, and nothing published is removed or narrowed. So this page is a record of what became available, not a list of things you have to react to — a client that ignores fields it does not recognise keeps working across every entry below.

Changes to draft shapes are recorded here too, and they are the one exception to the paragraph above. A shape marked beyond Phase 0 is a design commitment under review with early-access partners and may still move — including being withdrawn — before general availability. When one does, it appears here, and the entry says plainly whether anything was ever served to you.

2026-10 — Uber Eats' own refusal of a promotion is a 400, with Uber Eats' reason

When Uber Eats itself refuses a createUberEatsPromotion call, for a limit only Uber Eats enforces (for example an unknown userGroup, or a budget period it does not offer), the call now fails with 400 instead of a 5xx. The response carries errorCode and subType bad_request.uber_eats_refused, and its message is Uber Eats' own reason, relayed as is. Nothing is created. The 400 is final: fix the request, do not retry it unchanged.

Every other failure is unchanged, including the 400s that name a field, which OneTablet answers before the request reaches Uber Eats.

2026-10 — Uber Eats promotions are live in production

From 2026-10-13 the four Uber Eats promotion operations are served in production: fetchUberEatsPromotions, fetchUberEatsPromotion, createUberEatsPromotion and revokeUberEatsPromotion. Until then they were a draft shape that had never been served; the shape in the reference is the one now served.

Promotions are available for Uber Eats storefronts only. createUberEatsPromotion spends money on a live storefront and is not idempotent, and a promotion cannot be changed after it is created, only revoked: read the operation's notes on retries before you write the calling code.

2026-10 — menu.publication.completed: when it fires, and what its id is

The menu.publication.completed reference page now says exactly when the event fires and how its id is built. Nothing was removed or narrowed: the payload is still the MenuPublication that fetchMenuPublications returns, and no field, type or enum changed.

  • It fires each time a publication reaches a terminal state: completed, which can include failed stores (read data.stores[].status), or failed.
  • A publication cancelled while it is queued or publishing reads failed and sends the event. Cancelling one that had already failed sends nothing more; its failed event was already sent.
  • A publication where no storefront had a menu to publish completes at once, with an empty data.stores, and sends the event.
  • When OneTablet retries the failed stores of a finished publication, the publication goes back to publishing. When it finishes again, a second event arrives with a new completedAt and a new id. Both are real events; keep the one with the later data.completedAt.
  • The id is built from the publication and the instant it finished — today <publicationId>:<completedAt>. It is not a UUID. Deduplicate on it as on every event, and read data.publicationId and data.completedAt rather than parsing it.

You subscribe as for any other event: put menu.publication.completed in newUrlEvents with setAllWebhookSettings. Deliveries come from the same pipeline as store.status.changed, so each one is signed the same way (v2). Subscribing is available now. Production started sending this event to subscribed organizations on 2026-10-12 at 11:02 UTC.

2026-10 — Menu publications follow each platform's own answer; publishedAt and failedAt are published

MenuPublicationStore has two new optional fields: publishedAt, when a store's publication succeeded, and failedAt, when it failed. Both are ISO-8601 UTC instants.

How fetchMenuPublications decides each store's status is changing, so that it follows the delivery platform's own answer instead of the moment OneTablet handed your menu on:

  • publishing lasts until the platform answers. A store waiting behind another publication to the same storefront reads queued until its turn, and a submission retried after a temporary error stays publishing instead of reading failed first.
  • DoorDash confirms each menu it takes live, so a DoorDash store now reaches confirmed, with confirmationMode: confirmed, usually within a few minutes of acknowledged. Until then the publication stays publishing. Every other platform still stops at acknowledged.
  • A store that is not connected to its delivery platform reads failed, and platformError says that nothing was sent to it.
  • A publication's completedAt is the exact time it finished.

No value was added to or removed from any enum. The new fields are published now; stores begin reporting them, and the statuses above, when the change reaches production. Publications recorded before then keep the status they were given.

The change took effect in production on 2026-10-12 at 10:35 UTC.

A platform answer that arrives more than about 60 minutes after the submit does not change a store already reported failed.

2026-10 — wentLiveAt and liveAt are set-up times, not go-live times

Store.wentLiveAt on fetchStores and liveAt on fetchStore were described as "when the storefront went live", absent until go-live. That was not what most storefronts carried. The fields are now described as what they are: when OneTablet set the storefront up.

  • A storefront you provision with the Onboarding API's addBrandsToLocation gets the value when it is created, before it takes any order.
  • Older storefronts may carry the set-up time of their brand at that location, or the moment they were first switched on.

Names and types are unchanged.

This entry is an exception to the promise at the top of this page. It changes what an existing field is documented to mean rather than adding something, so it is something to react to: if you read either field as "this storefront is selling", switch to fetchStoreStatuses. That is where open, paused and closed have always been reported.

2026-09 — MenuAssignment.snapshot is typed, and snapshotWithheld is published

snapshot on fetchStoreMenus was published as an untyped object. It is now MenuSnapshot — the menu document as it was sent to the delivery platform, with named shapes for its categories (MenuSnapshotCategory), items (MenuSnapshotItem), modifier groups and options (including nested modifiers), prices in integer cents, and weekly and special hours. The bytes returned are unchanged: snapshots are still returned exactly as recorded, so every field is optional, a few are nullable, and an older snapshot may carry a key the schema does not list — ignore it. Note that snapshot.storeId is the delivery platform's id for the storefront, not the OneTablet store id.

MenuAssignment.snapshotWithheld is new. It is true when a published snapshot exists but is larger than the 2 MB per-snapshot cap and was left out, so a missing snapshot no longer means two things: snapshot and snapshotWithheld both absent means no published snapshot exists for that assignment.

2026-09 — Uber Eats promotion discount is typed, one branch per promoType (draft)

discount on createUberEatsPromotion and on every returned UberEatsPromotion was an untyped object. It is now a union discriminated by its own promoType — one named shape per mechanic: UberEatsFlatOffDiscount, UberEatsPercentOffDiscount, UberEatsBogoDiscount, UberEatsFreeItemDiscount, UberEatsMenuItemDiscount and UberEatsFreeDeliveryDiscount. Amounts are integer minor units; item ids are the ones fetchLocationItems reports. A create whose discount.promoType differs from the promotion's promoType is now a 400.

The promotion operations are a draft shape that has never been served in production, so nothing you depend on changed.

2026-09 — report.completed is declared and can be subscribed to

The report.completed webhook now has a generated reference page of its own, filed under Reports, and setAllWebhookSettings accepts report.completed in newUrlEvents. It was listed in the webhook catalogue but was not yet a value the settings operation would take, so subscribing to it was refused with a 400. Nothing was removed or narrowed: a new event type and a new page.

The event fires when a report you requested reaches a terminal state — ready or failed — and carries the Report object, so status, completedAt and error arrive with the delivery. It does not carry files: a downloadUrl is signed at read time with a 24-hour expiry and a delivery may be retried for longer than that, so call fetchReport with the reportId when the event arrives. Polling fetchReports remains authoritative — a missed delivery never strands a report.

Subscribing is available now; deliveries begin when the producer ships.

2026-09 — Reporting refusals answer 4xx with a <category>.<reason> subType

A request the Reporting API refuses because of something you sent is now answered with the 4xx that describes it — never a 500. Before this change, some of these reached you as a 500.

The error body's subType (and errorCode, which carries the same value) is now a two-part code, <category>.<reason>. The category always agrees with the HTTP status:

categorystatusmeaning
bad_request400input only the reporting backend can check is invalid
not_found404does not exist, or is not yours — deliberately indistinguishable
conflict409conflicts with the current state
unprocessable422well-formed, but refused by a business rule

The codes in use today:

  • unprocessable.report_unavailable — the (platform, type) is not available to your organization (every DoorDash type, for example). See fetchReportCoverage.
  • unprocessable.report_window — the period violates the type's windowConstraint.
  • unprocessable.report_no_eligible_stores — no eligible connected storefront matches the request.
  • not_found.report — fetchReport for an unknown reportId, or one belonging to another organization.

If you branch on subType: the createReport refusals previously documented as report_unavailable, report_window and report_no_eligible_stores now carry the prefixed form. Match on the part after the ., or on both spellings, while this rolls out. Branch on the status code for the category; branch on the reason for the detail. New reasons may be added under any category without notice.

2026-09 — fetchReports lists your reports and fetchReport downloads one

fetchReports lists the reports of the organization your API key authenticates, newest requestedAt first. You can filter by statuses, platforms and types, and page with page (page.limit above 100 is a 400). List entries never carry files.

fetchReport returns one report by reportId. When the report is ready, each entry in files carries a signed downloadUrl that works for 24 hours without credentials, so treat it as a secret. expiresAt says exactly when it stops working. Every call signs a fresh URL: when one expires, call fetchReport again. The report is never regenerated for this. A reportId that does not exist and one that belongs to another organization both return the same 404.

2026-09 — createReport requests a platform report for your organization

createReport requests one platform report (Uber Eats or Grubhub) for a date range and returns immediately with the Report in status queued. The report covers only the storefronts of the organization your API key authenticates, optionally narrowed by locationIds or storeIds (sending both is a 400). A (platform, type) that fetchReportCoverage does not list as available — including every DoorDash type — a scope that matches no eligible storefront, or a period that violates the type's windowConstraint, is a 422 whose subType says which.

The Idempotency-Key header is accepted and not yet honoured: a retried createReport requests a second report. Treat a timeout as "may have succeeded" and check your reports before retrying.

2026-09 — report coverage is available and scoped to your organization

fetchReportCoverage reports only the platform report capabilities reachable through storefronts connected to the organization authenticated by your API key. It is never a network-wide catalogue: an organization with no eligible connected storefronts receives an empty coverage array, and a platform connected only to another organization is absent. The response is a small fixed matrix and is deliberately not paginated.

Use available, windowConstraint, dataLag, and notes to decide which report requests are valid before creating one. Platform and report type are open strings so new vendor capabilities can be added without a contract version change.

2026-09 — x-mealco-tx-id is now declared in the OpenAPI document

Nothing about the responses changed — the header has been on every Management API response since it was introduced. What changed is that the machine-readable contract now says so, on every response of every operation, so a client generated from openapi.json gives you a typed accessor for it instead of leaving you to read it out of the raw headers. If you already read it by name, nothing to do.

It is declared optional, and the reason is worth knowing: a request whose JSON body is malformed is rejected before it reaches an operation, so no id has been minted yet and that one response carries neither the header nor the txId field of the error body. Every response that reaches an operation carries it. See Errors and retries → Every failure names itself.

2026-09 — store.status.changed now fires on resume, and no longer claims "reactivated"

store.status.changed is sent when a store is resumed, not only when it is paused. Until now the event fired on the pause and stayed silent on the reopen, so a partner tracking availability saw stores go down and never come back up — the store looked paused forever until you re-polled fetchStoreStatuses. Both transitions now arrive, including the automatic reopen when a pausedUntil elapses, which comes through the same event with no separate subscription.

The resume payload is desired: { "state": "open" } and nothing else. pauseNote, pausedUntil and updatedAt are absent on a resume — deliberately, and they will stay absent. Carrying the ended pause's values forward would say a store that is open has a note and an expiry, and updatedAt means "when the current pause began", which an open store does not have. If you persist these fields, clear them when state is open rather than keeping the last value you saw. As always, observed, discrepancy and lastPolledAt are omitted on operator-driven pause and resume: this event carries intent, and those describe an observation nobody took.

"Reactivated" is removed from the list of things that trigger this event. The trigger prose read "paused, resumed, auto-reopened, taken offline by the platform, or reactivated"; it now reads "paused, resumed, auto-reopened, or observed offline by the delivery platform". Nothing stopped being sent — no such event was ever sent. Reactivating a deactivated store is not implemented, so the word described a delivery that could not happen, and a handler waiting on it would have waited forever. No field, type or enum member changed, and the schema is untouched.

The event is sent on every status write, not only on a genuine change. Pausing an already-paused store, or resuming an already-open one, sends it. This is not new for pause and is now also true for resume; the producer acts on the operator's intent and does not re-read the prior state to suppress a no-op. Read the payload as the current state — which it always is — and collapse repeats on data.desired.state if you need strict edge semantics.

2026-09 — Store status: lastPolledAt is the timestamp, and the only one

fetchStoreStatuses and fetchStoreStatus do not report observed.observedAt or discrepancy.detectedAt. They never have been populated, and they are not going to be — this entry records a settled answer, not a limitation waiting on future work, so stop coding against their eventual arrival.

Use lastPolledAt instead. It is on every status that carries an observed block, it is when that observation was taken, and it is what makes the derived discrepancy as fresh — or as stale — as the observation behind it. There is no second, finer timestamp underneath it, and a discrepancy carries no "first seen" time: a discrepancy is a standing condition, so read it as true as of lastPolledAt and re-read to see whether it still holds. See Data freshness and Coverage and limitations.

Nothing is removed: both fields remain optional in the published schema, as every field on StoreStatus is, so a client that already tolerates their absence needs no change. If you branch on either one being present, that branch is dead code today and always will be.

2026-09 — fetchOrders: sorting, a storeId filter, locationCode, and no window ceiling

The order feed is now sorted, and you choose the direction. sort takes createdAtDesc (newest first, the default) or createdAtAsc. Ordering on this operation used to be unspecified — that warning is gone. Ties on createdAt break on the order's id, so the ordering is total and paging over it is a real partition: no page repeats a row and none skips one.

sort: "createdAtAsc" with page: { "limit": 1 } answers "my very first order", and with a storeId it answers it for one storefront.

storeId is a new filter — narrow the feed to one storefront, by the Store.id fetchStores reports.

locationId is now locationCode, the short site code carried on every store's location. Both name one kitchen; the code is the value we publish on the store rows you already read, and taking it directly removes a lookup that made a real-but-empty kitchen answer 404 instead of matching nothing. locationCode and storeId are mutually exclusive — sending both is a 400, because a storefront is already inside exactly one kitchen and honouring one filter while ignoring the other would hand you a page whose meaning you cannot see.

The 31-day ceiling on [createdAtStartDate, createdAtEndDate) is removed. Both bounds stay optional and omitting both means all-time. There was never a retention ceiling; now there is no width ceiling either, so a two-year backfill is one window paged with the cursor rather than twenty-four windows stitched together. Sizing them narrower is still fine if your loader wants checkpoints.

The fetchOrders cursor now stops at 10,000 orders, with a 400 naming the number. Nothing became unreadable: a narrower [createdAtStartDate, createdAtEndDate) window reaches the same rows, and windows lost their width limit in the same change, so the pair is strictly more reach than the old 1,000 pages x 100 rows cap gave you — and it steers you off the one access pattern that is expensive for both sides.

Nothing was removed from a response, and no existing value changed meaning. The one request-shape change is locationId → locationCode; if you had already written a call against locationId, send the code from the same store row instead of its location's id.

2026-09 — fetchOrders time windows are served, and named after the timestamp they filter

fetchOrders now filters on a time window. The parameters are createdAtStartDate (inclusive) and createdAtEndDate (exclusive), at most 31 days apart, and they bound the order's creation time — the moment OneTablet recorded it. Sending them used to return 400; it now returns the window.

These are the names the published reference has always used. Our contract briefly spelled them from / to, which meant a call written against the reference had its window silently ignored and came back unfiltered — a full page that looks like a correct answer. That is the defect this entry closes; from / to are gone and were never served.

Creation time, not pickup time. The earlier description of these parameters said they bounded the order's pickup time. That was wrong — the parameter name is the contract — and the 400 existed only because of it: rather than serve a pickup window we could not compute, we refused. There is nothing left to refuse. If you are reconciling against a platform's own pickup-time export, expect the boundaries to differ by the order's lead time; a pickup-time filter, if it is ever added, will arrive as a separately named parameter and will not change what these two mean.

Windowing on creation time is what makes a backfill exact: an order is created exactly once, so contiguous [createdAtStartDate, createdAtEndDate) windows partition your history with no gap and no overlap. There is still no rolling retention ceiling, so a full warehouse load is a loop over windows with the cursor loop nested inside each one.

2026-09 — every response carries a transaction id

Every response now carries an x-mealco-tx-id header, on a 200 exactly as much as on a failure, and error bodies carry the same value as txId. It is the identifier we use to follow one call end to end: log it beside your own record of each request, and quote it when you open a ticket — one id is something we can look up, where a status code and a time of day is not.

You may send your own x-mealco-tx-id on a request and we will adopt it, so your correlation id and ours are the same string. It is a label only: no authorization meaning, no access of any kind, and nothing to put personal information into.

Nothing changed in any existing field. Full rules — length limits, the one request shape that has no id, what not to put in it — under Concepts → API conventions.

2026-09 — setAllWebhookSettings: an omitted field is now left unchanged

setAllWebhookSettings used to be a full replace. Every field you did not send was reset to a default, so the only safe way to change one setting was to restate all of them. That is no longer true: a field you omit keeps its stored value, and a field you send is set.

The case that matters is legacyUrlEnabled. It arrived after this operation did, which meant a call written before it existed — registering a newUrl for the first time, say — omitted it and therefore switched the legacy order feed off, with a 200 and no warning. Registering a new destination is exactly when you least want your existing one to stop. If you send nothing about legacyUrlEnabled now, legacy delivery carries on untouched.

Nothing you already send changes meaning. A call that states all four fields does exactly what it did before — this only affects fields you leave out, which previously could not be left out safely at all.

Two details to design around.

newUrlEvents still replaces the whole list when you send it, and [] still means "no events" — it is not an append, because a list you could only grow would be useless. Omit it to leave the subscription list alone.

Omitting newUrl no longer clears the destination. To clear it, send newUrl: null explicitly. Most of the time you want newUrlEnabled: false instead — that stops delivery while keeping the registration, and clearing your only destination is refused with a 400 either way.

The operation keeps its name. "Set all" means this one call covers all of the settable webhook fields, not that it resets the ones you leave out.

2026-08 — the hours writes are owner-only, and say so

setLocationHours and setLocationSpecialHours write a locationId your organization owns — one whose Location.organizationId equals your own organization id. If you operate a brand out of somebody else's host kitchen, fetchLocations shows you that kitchen and fetchStoreHours reports its hours, but the hours writes are not yours to make there: neither write is brand-scoped, so a single call moves the schedule for every brand trading at that location, including the host's. Ask the host to make the change.

Nothing was narrowed — the boundary is where it always was. Both operations were owner-only from the day they shipped, enforced one hop further in. The two things that change are that the rule is now written down on both operations, and that the refusal is now the documented 404 instead of the 500 Internal Server Error that hop's rejection used to surface as. If you saw that 500 against a host kitchen, this is the fix; a call that succeeded before still succeeds.

The 404 is the same one you get for a locationId that does not exist at all, and it is deliberately indistinguishable from it: the body is a bare {"message":"Not Found"} in every case, because anything that told the cases apart would let you enumerate locations you cannot see. The rule above is the explanation the body does not carry — compare organizationId on the location from fetchLocations with your own organization id before you write, and treat a 404 on an id that fetchLocations returned as "that kitchen is not yours", not as a bug in the directory.

2026-08 — setLocationHours: the weekly template is now writable

setLocationHours replaces a location's REGULAR weekly operating hours. Send the complete week as windows — one entry per open period, day plus opensAt and closesAt in HH:MM — and it replaces whatever the location had. Several entries may name the same day for a split service, and an entry whose closesAt is earlier than its opensAt crosses midnight, exactly as fetchStoreHours already reports them.

This completes the hours pair: setLocationSpecialHours writes the date-specific overrides, setLocationHours writes the weekly template underneath them, and fetchStoreHours reports the resolved result. The two tiers are independent — a weekly write never touches a special date, and a special-date write never touches the week.

Two things to design around.

windows is required and must be non-empty. There is no "clear the week" form, deliberately: an empty template leaves every menu at the location with no hours at all, which is a closure, not an hours edit. Close a location with setLocationSpecialHours or the store-status pause operations.

This write republishes menus. Propagation reaches every storefront at the location whose menus FOLLOW location hours — a menu configured with its own hours is unaffected, and fetchStoreMenus reports which is which. It is asynchronous, typically completing within minutes, and it is a real publish: a menu holding unpublished saved changes goes live with them. The reply echoes what was persisted; read the settled, per-storefront result back from fetchStoreHours.

A locationId that is not yours is a 404, indistinguishable from one that does not exist — the same convention as every other single-resource operation here. See the owner-only entry above for exactly which locations count as yours.

2026-08 — webhook signatures are asymmetric: the v2 scheme

Store-event deliveries now carry X-OneTablet-Signature: v2=<jwt> — a standard ES256 JWT you verify with a public key fetched from https://api.onetablet.com/.well-known/onetablet-webhook-jwks.json.

The delivery body is unchanged, byte for byte. The signature is detached: the token carries a body_sha256 claim over the raw bytes rather than wrapping the envelope, so nothing about the JSON you already parse has moved.

Why this replaced v1 before anyone was on it. v1 is HMAC under a shared secret, and a shared secret that lets you verify also lets you sign — every party holding it can mint deliveries to every other party, with no breach required. Under v2 we hold the private key and you hold nothing that can produce a signature. The store-event pipeline had no partners pointed at it yet, which is the only moment this change is free.

What you get for nothing: the replay window is now nbf/exp, enforced by your JWT library rather than by clock arithmetic you have to write; key rotation is a JWKS refresh your library does on its own, so it never involves you again.

v1 still works and will not be removed without a migration arranged with you. During any overlap a delivery carries both, comma separated — v1=<hex>,v2=<jwt> — so match on the prefix rather than on the whole header. New integrations should implement v2 only.

Full details, the claim set and a verified Node example: Verifying webhook signatures.

2026-08 — Special hours are the location tier only

fetchBrandLocationSpecialHours has been withdrawn from the specification. Special hours on this API are now modelled at one tier: the location, written with setLocationSpecialHours and reported inside fetchStoreHours. There is no operation addressed by a brand-and-location pair.

No integration has to react to this. The operation was drafted and published to this specification but was never served by the production endpoint — it existed in the document and in no deployed release, so no client can have been calling it and none can have been generated against it. It is being withdrawn ahead of general availability under the draft-shape provision stated at the top of this page.

Why, so you can design around the shape rather than the history. OneTablet does carry brand-at-location date overrides internally, and a storefront's effective special dates are the union of that tier and the location tier, with the more-closed schedule winning on a shared date. What this API published was the READ of that inner tier with no matching WRITE and no place for it in the documented model — half of a concept, which is worse to integrate against than none of it. Removing it leaves a coherent surface: one tier you can both read and write, and fetchStoreHours as the resolved answer that already accounts for both tiers internally.

If the brand tier is published later it arrives as a matched read and write pair, with its own reference pages, and it appears here first.

2026-08 — store-event webhook deliveries are signed

Every delivery from the store-event pipeline now carries X-OneTablet-Timestamp and X-OneTablet-Signature: v1=<hex> — an HMAC-SHA256 over "<timestamp>." + <raw body>, with a five-minute replay window. Signing is mandatory on that pipeline: there is no opt-in, no per-endpoint flag, and no unsigned mode.

Verifying webhook signatures is the new page under Concepts, with the exact string being signed, a working Node snippet, and how rotation behaves. Verification is optional to adopt but strongly recommended — a webhook url is not a secret, and without a signature a forged POST is indistinguishable from ours.

Phase 0 order deliveries are unchanged and remain unsigned; adding signatures there is a coordinated change scheduled with each partner, because we cannot start sending signatures before you are verifying them.

2026-08 — Single-subject reads, and the order feed goes live

Three new operations, each the one-of-a-thing form of a list you already have:

  • fetchOrder — get one order by its OneTablet id. The read to make when an order webhook lands.
  • fetchStoreStatus — one storefront's desired / observed / discrepancy, identical in shape and in meaning to a row of fetchStoreStatuses.
  • fetchStoreItems — item availability addressed by storeId instead of locationId. It answers from the store's parent kitchen, narrowed to the store's brand, and echoes locationId so the kitchen grain is visible in the response. Availability is still a kitchen-level fact: every sibling storefront of that brand at that kitchen reports the same values.

And fetchOrders now returns data instead of 501. Filter by orderIds, locationId and statuses, and page with the cursor.

One limitation at the time: the time-window filter was not served — sending it returned 400 rather than quietly applying a window to a different timestamp than the one it was documented against, because a windowed backfill built on the wrong timestamp would look complete and be missing orders. Every other filter worked. No longer current: the window is served, on the order's creation time, as of the 2026-09 entry above.

2026-08 — Store status is its own section, on its own path prefix

fetchStoreStatuses, pauseStores, unpauseStores and the store.status.changed webhook moved out of Stores into a new Store status section, and their paths moved with them:

WasNow
/management/stores/fetchStoreStatuses/management/store-status/fetchStoreStatuses
/management/stores/pauseStores/management/store-status/pauseStores
/management/stores/unpauseStores/management/store-status/unpauseStores

Nothing about the request or response bodies changed, and no other operation moved. Stores now covers only the identity map and schedules — what you operate; Store status covers whether it is currently taking orders, and whether OneTablet and the delivery platform agree about it.

2026-08 — Guide pages: webhooks, freshness, reports, coverage

Added Webhooks, Data freshness, Platform report catalogue and Coverage and limitations to Concepts, and published the rate-limit position on Authentication. No contract change.

2026-08 — fetchStoreStatuses v1

Store status published as a single operation covering one storefront or a whole estate. Each status carries desired, observed and discrepancy distinctly, and lastPolledAt so a consumer can check the age of an observation before acting on it. Phase 1; the server answers 501 until the read behind it ships.

2026-08 — Directory reads

fetchLocations, fetchBrands and fetchStores published — the identity map, including the crosswalk from OneTablet identifiers to each platform's own storefront identifier. Phase 1; answering 501 until the reads ship.

2026-08 — fetchOrders

The normalized order feed and single-order read published as one operation: time-window, status and location filters, or explicit orderIds. Phase 0; answering 501 until the query behind it ships. Order webhooks are live today independently of this operation — see Webhooks.

2026-08 — Draft specification published

Initial draft of the Management API: Phase 0 (orders and order webhooks, live), Phases 1–3 (stores, status and discrepancy, hours, availability, reporting) as the design commitment for early-access review, and Phase 4 (menus) as roadmap.

The reference on this site is generated from the contract itself, so it cannot describe an operation we do not publish. What a published operation does not yet do is stated by its phase label and by the 501 it answers — see Rollout phases.