Catalog API
REST API for native apps to render published menus, sections, products, and customizations.
Use this API when a native app needs to read published catalog data and render its own UI. For storefront JavaScript in the merchant theme, use the Widget API instead.
A machine-readable OpenAPI 3.1 spec is published in this organization as eats-catalog-v1. Import it into Postman, Insomnia, or your codegen tool from GitBook OpenAPI settings, or from the repo file docs/openapi/catalog-v1.yaml.
Authentication
In Zapiet Eats go to Settings → API credentials.
Create a personal access token. Copy the secret once (it is not shown again).
Send it on every request:
Authorization: Bearer {token}
Accept: application/jsonThe token is bound to one store. It cannot read another merchant’s menus.
Optional (must match the token’s store if present):
X-Zapiet-Eats-Shop-Url: your-store.myshopify.comA mismatched shop header returns 403.
Required scope: catalog:read (native apps) or api:* (existing full-access merchant tokens).
catalog:readis read-only catalog access. New tokens default to this. It cannot call other Zapiet REST surfaces.api:*is full REST access for the bound store. Do not putapi:*in a native app.
Rate limits
Default: 40 requests per second per store.
Over the limit: 429
{ "error": "rate_limited" }withRetry-After.
Errors
HTTP
error
Meaning
401
unauthorized
Missing, expired, revoked, or unbound token
403
forbidden
Shop header does not match the token’s store
403
missing_scope
Valid token without catalog:read or api:*
404
not_found
Unknown id, other store’s id, draft menu, or API not enabled
422
validation_error
Bad query params
429
rate_limited
Too many requests
REST
Base: https://{eats-host}/api/catalog/v1
Prices are integer cents in the shop currency.
Localization
Menu name
Yes
name + translations
Section name
Yes
name + translations
Section description
No
description string or null
Product name
No (Shopify string)
name only
Allergens / dietary restrictions
Yes
{ id, name, translations }
name is the store default locale (Settings → Languages). translations is a locale → string map. When a menu or section has no localized names, name is "" and translations is {}.
List locations
List published menus
Get a menu tree (slim product cards)
Id or handle (m-1197) both work. Draft menus are omitted.
The menu root and each section include name and translations. Product cards omit body_html, product_options, and Shopify options / option_groups.
Each product card includes:
id,name,image_url,price_range(min/maxin cents),caloriesvariants[]—variant_id,name,price(cents)has_options—truewhen published Product Options, more than one variant, or meal-deal / pick-a-side groups existallergens[]anddietary_restrictions[]as{ id, name, translations }
List section metadata (no products)
Get one section (slim product cards)
Use this after the section list when loading a large menu one section at a time.
Get one product (full payload)
Returns body_html, published product_options, Shopify options / option_groups, full variants, and bundle / choice groups. 404 if the product is not on that published menu section.
List allergens and dietary restrictions
Same { id, name, translations } shape. Merchant Settings labels win over Zapiet defaults. Cache these maps.
Loading pattern
Small menu:
GET /menus/{id}, then nested product URL on tap.Large menu:
GET /menus/{id}/sections, thenGET /menus/{id}/section/{sectionId}per section; nested product URL on tap.
What v1 does not do
Add to cart, checkout, or charge the customer (use Shopify / existing WebView).
Return draft menus or draft customizations.
Return another store’s data.
Replace the App Proxy page for browser customers.
Store the token safely
iOS: Keychain. Android: EncryptedSharedPreferences / Keystore.
One token per store. Rotate by revoking in Settings → API credentials.
Never ship the Shopify app secret, and never log the Bearer token.
Examples
Last updated