Integration path
Integration Guide: Sales & Catalog Synchronization
This guide shows how to build a reliable downstream sales and product-catalog synchronization. It is intended for reporting, ERP, product-information, and data-platform integrations that need a complete order history alongside the catalog used to sell those orders.
Before you start
- Use a service account with access to the brand or restaurant scope that you intend to synchronize.
- Choose one order scope per request:
brandId,brandIds,restaurantId,restaurantIds, orcustomerId. - Configure the target menu and, when relevant, the
consommation_modeoutside the extraction job. Do not assume that an active menu is unique. - Store a synchronization watermark and make loads idempotent before scheduling recurring runs.
1. Authenticate
Exchange the credentials supplied for the integration for an access token. The login payload is form-encoded, not JSON. Tokens remain valid for 24 hours, or 14 days when rememberMe=true.
POST/oauth/loginRequest an access token
Send an application/x-www-form-urlencoded body. Re-authenticate after a 401 token_expired response.
Request Body
rememberMe=false
username=integration@example.com
password=YOUR_SECURE_PASSWORD
grant_type=passwordRequest Body Properties
Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.
| Property | Type | Example | Description |
|---|---|---|---|
| rememberMe | boolean | false | The remember me value. |
| username | string | integration@example.com | The username value. |
| password | string | YOUR_SECURE_PASSWORD | The password value. |
| grant_type | string | password | The grant type value. |
Response
{
"access_token": "eyJhbGciOiJIUzI1...",
"token_type": "Bearer",
"status": 201,
"code": "token_created",
"message": "You have successfully logged in.",
"data": {
"user": {
"restaurants": [
{
"restaurantId": 2337
}
]
}
}
}Response Properties
Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.
| Property | Type | Example | Description |
|---|---|---|---|
| access_token | string | "eyJhbGciOiJIUzI1..." | The access token value. |
| token_type | string | "Bearer" | The token type value. |
| status | integer | 201 | HTTP status code returned by the API. |
| code | string | "token_created" | Machine-readable application code for the result. |
| message | string | "You have successfully logged in." | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.user | object | {…} | Object containing user fields. |
| data.user.restaurants | array | […] | List of restaurants entries. |
| data.user.restaurants[] | object | {…} | Object containing restaurants fields. |
| data.user.restaurants[].restaurantId | integer | 2337 | Identifier of the restaurant. |
Send Authorization: Bearer <access_token> with the order and menu-listing requests below.
2. Extract sales by update time
Use GET /v1/orders for both the initial backfill and recurring synchronization. For incremental jobs, ticketStartDate and ticketEndDate filterlastUpdateDate; startDate and endDate instead filter the original transaction date.
GET/v1/ordersList updated orders
Provide exactly one scope parameter. The response is paginated and wrapped in data.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| restaurantId | integer | Yes | Example scope. Use one, and only one, supported scope parameter. |
| ticketStartDate | ISO 8601 date-time | Yes | Inclusive lower bound for lastUpdateDate in an incremental run. |
| ticketEndDate | ISO 8601 date-time | Yes | Upper bound for lastUpdateDate in an incremental run. |
| limit | integer | No | Page size from 1 to 1000. Use a fixed value for the complete run. |
| offset | integer | No | Zero-based page offset; start at 0 and increase after every successful page. |
Response
{
"status": 200,
"code": "omnichannel_orders_succeed",
"data": {
"count": 1,
"omnichannelOrders": [
{
"reportOrderId": 317030553,
"omnichannelOrderId": "51544449",
"restaurantId": 2337,
"status": "PAID",
"startDate": "2026-08-05T13:58:30.000Z",
"lastUpdateDate": "2026-08-05T14:02:10.000Z",
"totalPriceDiscountedWithTaxIncluded": 2790,
"items": []
}
]
}
}Response Properties
Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "omnichannel_orders_succeed" | Machine-readable application code for the result. |
| data | object | {…} | Endpoint-specific response payload. |
| data.count | integer | 1 | Total number of matching records. |
| data.omnichannelOrders | array | […] | List of omnichannel orders entries. |
| data.omnichannelOrders[] | object | {…} | Object containing omnichannel orders fields. |
| data.omnichannelOrders[].reportOrderId | integer | 317030553 | Identifier of the associated report order. |
| data.omnichannelOrders[].omnichannelOrderId | string | "51544449" | Identifier of the associated omnichannel order. |
| data.omnichannelOrders[].restaurantId | integer | 2337 | Identifier of the restaurant. |
| data.omnichannelOrders[].status | string | "PAID" | HTTP status code returned by the API. |
| data.omnichannelOrders[].startDate | string | "2026-08-05T13:58:30.000Z" | Date or timestamp for start. |
| data.omnichannelOrders[].lastUpdateDate | string | "2026-08-05T14:02:10.000Z" | Date or timestamp for last update. |
| data.omnichannelOrders[].totalPriceDiscountedWithTaxIncluded | integer | 2790 | The total price discounted with tax included value. |
| data.omnichannelOrders[].items | array | [] | List of items entries. |
3. Make the incremental job recoverable
- Define a closed extraction window and replay a configurable overlap before the last successful watermark to capture late corrections.
- Read pages with a fixed
limit, beginning atoffset=0. Set the next offset to the current offset plus the requested limit, then stop when that next offset is greater than or equal todata.count. Do not advance byomnichannelOrders.length: the count and offset apply to source reporting rows before response de-duplication. - Upsert each order by
reportOrderIdand each line by the composite keyreportOrderId + itemId. - Advance the watermark only after every page, transformation, and target write has succeeded. A failed run must be replayed from the previous successful watermark.
Corrections and cancellations
Do not discard CANCELLED, PAID_CANCELLED, or CONTRA_ENTRY records. A paid order that is later reversed may surface both an origin record and a contra-entry record. Preserve each reporting record by reportOrderId, its status, and its monetary values. When present, reportOrderContraEntry identifies the origin and contra-entry order references; do not assume that those two rows share an omnichannelOrderId.
4. Map sales and catalog data
The following mapping is intentionally neutral so it can be applied to a data warehouse, ERP, or product-information system. Keep the API values in their source units until a single, explicit conversion step at the target boundary.
| Target concept | Innovorder field | Integration rule |
|---|---|---|
| Sales-header key | reportOrderId | Unique source reporting record; use as the idempotent order key. |
| Business order reference | omnichannelOrderId | Retain for traceability and operational reconciliation. |
| Scope and timing | brandId, restaurantId, channelId, startDate, lastUpdateDate | Use for ownership, reporting dimensions, source-time analysis, and incremental processing. |
| Business date | businessYear, businessMonth, businessDay | Build the business date in the target time/reporting model. |
| Sales line key | items[].itemId | Use with reportOrderId to make a unique line key. |
| Parent/add-in relationship | items[].parentItemId, items[].itemType | Keep the parent link and item type so products, customizations, and cross-selling lines remain distinguishable. |
| Product reference | items[].skuValue; data.products[productId].sku.value | SKU may be null or absent. Preserve the product/item ID as a fallback source identifier. |
| Amounts | totalPrice*, totalDiscount, price | Values are in cents. Convert by 100 only if the target stores major currency units. |
| VAT | items[].vatValue; data.products[productId].vat | Values are basis points: 1000 represents 10%. Divide by 100 to obtain a percentage. |
| Correction state | status | Persist status changes, including cancelled and contra-entry states, rather than deleting prior imports. |
5. Refresh the product catalog
First list the menus accessible to the configured restaurant or brand. Select the menu using your integration configuration (for example a known menu ID and channel), then retrieve its full catalog snapshot.
GET/menusList accessible menus
Requires a bearer token. Supply a brandId or restaurantId scope and select the intended menu deterministically in your integration configuration.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| restaurantId | integer | Yes | Example scope for a restaurant-specific catalog. |
Response
{
"status": 200,
"code": "menu_succeed",
"message": "You can access these menus.",
"data": [
{
"menuId": 28115,
"name": "Main Menu",
"restaurantId": 2337,
"isActive": true,
"channels": [
{
"channelId": 5
}
]
}
]
}Response Properties
Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "menu_succeed" | Machine-readable application code for the result. |
| message | string | "You can access these menus." | Human-readable result message. Do not use this value for program logic. |
| data | array | […] | Endpoint-specific response payload. |
| data[] | object | {…} | Endpoint-specific response payload. |
| data[].menuId | integer | 28115 | Identifier of the menu. |
| data[].name | string | "Main Menu" | The name value. |
| data[].restaurantId | integer | 2337 | Identifier of the restaurant. |
| data[].isActive | boolean | true | Whether this resource is active. |
| data[].channels | array | […] | List of channels entries. |
| data[].channels[] | object | {…} | Object containing channels fields. |
| data[].channels[].channelId | integer | 5 | Identifier of the associated channel. |
GET/menus/{menuId}/hash_mapRetrieve the full catalog snapshot
This endpoint is accessible as Guest and does not require a bearer token. consommation_mode is optional and may affect catalog VAT and availability.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| menuId | integer | Yes | The configured menu identifier. |
| consommation_mode | string | No | Configured consumption mode used consistently for catalog refreshes. |
Response
{
"status": 200,
"code": "menu_hash_map_succeed",
"message": "You can access these menus.",
"data": {
"layout": [
{
"categoryId": 437731,
"products": [
{
"productId": 5973839
}
]
}
],
"categories": {
"437731": {
"categoryId": 437731,
"name": "Best sellers"
}
},
"products": {
"5973839": {
"productId": 5973839,
"name": "Menu Duo",
"price": 2490,
"vat": 1000,
"sku": {
"value": "C24"
}
}
},
"steps": {
"2381448": {
"stepId": 2381448,
"name": "Choose your pizza"
}
}
}
}Response Properties
Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "menu_hash_map_succeed" | Machine-readable application code for the result. |
| message | string | "You can access these menus." | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.layout | array | […] | List of layout entries. |
| data.layout[] | object | {…} | Object containing layout fields. |
| data.layout[].categoryId | integer | 437731 | Identifier of the category. |
| data.layout[].products | array | […] | List of products entries. |
| data.layout[].products[] | object | {…} | Object containing products fields. |
| data.layout[].products[].productId | integer | 5973839 | Identifier of the product. |
| data.categories | object | {…} | Object containing categories fields. |
| data.categories.437731 | object | {…} | Object containing 437731 fields. |
| data.categories.437731.categoryId | integer | 437731 | Identifier of the category. |
| data.categories.437731.name | string | "Best sellers" | The name value. |
| data.products | object | {…} | Object containing products fields. |
| data.products.5973839 | object | {…} | Object containing 5973839 fields. |
| data.products.5973839.productId | integer | 5973839 | Identifier of the product. |
| data.products.5973839.name | string | "Menu Duo" | The name value. |
| data.products.5973839.price | integer | 2490 | The price value. |
| data.products.5973839.vat | integer | 1000 | The vat value. |
| data.products.5973839.sku | object | {…} | Object containing sku fields. |
| data.products.5973839.sku.value | string | "C24" | The value value. |
| data.steps | object | {…} | Object containing steps fields. |
| data.steps.2381448 | object | {…} | Object containing 2381448 fields. |
| data.steps.2381448.stepId | integer | 2381448 | Identifier of the associated step. |
| data.steps.2381448.name | string | "Choose your pizza" | The name value. |
The catalog payload structures are layout, categories, products, and steps. Metadata such as menuId, name, or image may also be present. Categories, products, and steps are hash maps keyed by their IDs; use layout to reconstruct the hierarchy.
6. Handle failures without losing data
| Condition | Recommended behavior |
|---|---|
| 401 token_expired | Authenticate again, then retry the affected idempotent read request. |
| 403 | Stop the job and verify service-account scope, restaurant/brand IDs, and menu configuration. |
| 429 or 5xx | Use bounded exponential backoff and retry the GET request; keep the same extraction window. |
| Invalid response or transformation failure | Quarantine/log the payload, fail the run safely, and do not advance the watermark. |
| Partial target load | Replay the window. Idempotent upserts prevent duplicates while completing the missing work. |
For complete endpoint contracts, see Authentication, Retrieve Orders, and Menus.