Skip to content
innovorder
⌘K

Integration path

Integration Guide: Sales & Catalog Synchronization

System map · Data synchronization
Orders + menus
Cursor + hash maps
Data platform

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, or customerId.
  • Configure the target menu and, when relevant, the consommation_mode outside 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

json
rememberMe=false
username=integration@example.com
password=YOUR_SECURE_PASSWORD
grant_type=password
Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
rememberMebooleanfalseThe remember me value.
usernamestringintegration@example.comThe username value.
passwordstringYOUR_SECURE_PASSWORDThe password value.
grant_typestringpasswordThe grant type value.

Response

json
{
  "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.

PropertyTypeExampleDescription
access_tokenstring"eyJhbGciOiJIUzI1..."The access token value.
token_typestring"Bearer"The token type value.
statusinteger201HTTP status code returned by the API.
codestring"token_created"Machine-readable application code for the result.
messagestring"You have successfully logged in."Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.userobject{…}Object containing user fields.
data.user.restaurantsarray[…]List of restaurants entries.
data.user.restaurants[]object{…}Object containing restaurants fields.
data.user.restaurants[].restaurantIdinteger2337Identifier 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

NameTypeRequiredDescription
restaurantIdintegerYesExample scope. Use one, and only one, supported scope parameter.
ticketStartDateISO 8601 date-timeYesInclusive lower bound for lastUpdateDate in an incremental run.
ticketEndDateISO 8601 date-timeYesUpper bound for lastUpdateDate in an incremental run.
limitintegerNoPage size from 1 to 1000. Use a fixed value for the complete run.
offsetintegerNoZero-based page offset; start at 0 and increase after every successful page.

Response

json
{
  "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.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"omnichannel_orders_succeed"Machine-readable application code for the result.
dataobject{…}Endpoint-specific response payload.
data.countinteger1Total number of matching records.
data.omnichannelOrdersarray[…]List of omnichannel orders entries.
data.omnichannelOrders[]object{…}Object containing omnichannel orders fields.
data.omnichannelOrders[].reportOrderIdinteger317030553Identifier of the associated report order.
data.omnichannelOrders[].omnichannelOrderIdstring"51544449"Identifier of the associated omnichannel order.
data.omnichannelOrders[].restaurantIdinteger2337Identifier of the restaurant.
data.omnichannelOrders[].statusstring"PAID"HTTP status code returned by the API.
data.omnichannelOrders[].startDatestring"2026-08-05T13:58:30.000Z"Date or timestamp for start.
data.omnichannelOrders[].lastUpdateDatestring"2026-08-05T14:02:10.000Z"Date or timestamp for last update.
data.omnichannelOrders[].totalPriceDiscountedWithTaxIncludedinteger2790The total price discounted with tax included value.
data.omnichannelOrders[].itemsarray[]List of items entries.

3. Make the incremental job recoverable

  1. Define a closed extraction window and replay a configurable overlap before the last successful watermark to capture late corrections.
  2. Read pages with a fixed limit, beginning at offset=0. Set the next offset to the current offset plus the requested limit, then stop when that next offset is greater than or equal to data.count. Do not advance by omnichannelOrders.length: the count and offset apply to source reporting rows before response de-duplication.
  3. Upsert each order by reportOrderId and each line by the composite key reportOrderId + itemId.
  4. 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 conceptInnovorder fieldIntegration rule
Sales-header keyreportOrderIdUnique source reporting record; use as the idempotent order key.
Business order referenceomnichannelOrderIdRetain for traceability and operational reconciliation.
Scope and timingbrandId, restaurantId, channelId, startDate, lastUpdateDateUse for ownership, reporting dimensions, source-time analysis, and incremental processing.
Business datebusinessYear, businessMonth, businessDayBuild the business date in the target time/reporting model.
Sales line keyitems[].itemIdUse with reportOrderId to make a unique line key.
Parent/add-in relationshipitems[].parentItemId, items[].itemTypeKeep the parent link and item type so products, customizations, and cross-selling lines remain distinguishable.
Product referenceitems[].skuValue; data.products[productId].sku.valueSKU may be null or absent. Preserve the product/item ID as a fallback source identifier.
AmountstotalPrice*, totalDiscount, priceValues are in cents. Convert by 100 only if the target stores major currency units.
VATitems[].vatValue; data.products[productId].vatValues are basis points: 1000 represents 10%. Divide by 100 to obtain a percentage.
Correction statestatusPersist 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

NameTypeRequiredDescription
restaurantIdintegerYesExample scope for a restaurant-specific catalog.

Response

json
{
  "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.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"menu_succeed"Machine-readable application code for the result.
messagestring"You can access these menus."Human-readable result message. Do not use this value for program logic.
dataarray[…]Endpoint-specific response payload.
data[]object{…}Endpoint-specific response payload.
data[].menuIdinteger28115Identifier of the menu.
data[].namestring"Main Menu"The name value.
data[].restaurantIdinteger2337Identifier of the restaurant.
data[].isActivebooleantrueWhether this resource is active.
data[].channelsarray[…]List of channels entries.
data[].channels[]object{…}Object containing channels fields.
data[].channels[].channelIdinteger5Identifier 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

NameTypeRequiredDescription
menuIdintegerYesThe configured menu identifier.
consommation_modestringNoConfigured consumption mode used consistently for catalog refreshes.

Response

json
{
  "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.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"menu_hash_map_succeed"Machine-readable application code for the result.
messagestring"You can access these menus."Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.layoutarray[…]List of layout entries.
data.layout[]object{…}Object containing layout fields.
data.layout[].categoryIdinteger437731Identifier of the category.
data.layout[].productsarray[…]List of products entries.
data.layout[].products[]object{…}Object containing products fields.
data.layout[].products[].productIdinteger5973839Identifier of the product.
data.categoriesobject{…}Object containing categories fields.
data.categories.437731object{…}Object containing 437731 fields.
data.categories.437731.categoryIdinteger437731Identifier of the category.
data.categories.437731.namestring"Best sellers"The name value.
data.productsobject{…}Object containing products fields.
data.products.5973839object{…}Object containing 5973839 fields.
data.products.5973839.productIdinteger5973839Identifier of the product.
data.products.5973839.namestring"Menu Duo"The name value.
data.products.5973839.priceinteger2490The price value.
data.products.5973839.vatinteger1000The vat value.
data.products.5973839.skuobject{…}Object containing sku fields.
data.products.5973839.sku.valuestring"C24"The value value.
data.stepsobject{…}Object containing steps fields.
data.steps.2381448object{…}Object containing 2381448 fields.
data.steps.2381448.stepIdinteger2381448Identifier of the associated step.
data.steps.2381448.namestring"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

ConditionRecommended behavior
401 token_expiredAuthenticate again, then retry the affected idempotent read request.
403Stop the job and verify service-account scope, restaurant/brand IDs, and menu configuration.
429 or 5xxUse bounded exponential backoff and retry the GET request; keep the same extraction window.
Invalid response or transformation failureQuarantine/log the payload, fail the run safely, and do not advance the watermark.
Partial target loadReplay the window. Idempotent upserts prevent duplicates while completing the missing work.

For complete endpoint contracts, see Authentication, Retrieve Orders, and Menus.