External Data API
A secure, read-only REST API for pulling EqomOS master data — categories, brands, products, SKUs, prices, inventory and orders — into your own systems. Built for reliable, incremental, server-to-server integration.
API version v1Overview
The External Data API exposes EqomOS catalogue and order master data to authorised partner systems ("clients"). It is read-only (HTTP GET only), returns JSON, and is designed for both full extracts and lightweight incremental syncs.
- REST + JSON over HTTPS (TLS 1.2+). All traffic is encrypted in transit.
- HMAC request signing — every request is cryptographically signed; your secret never travels on the wire.
- Per-client authorisation — each client is granted access to specific APIs with its own page-size, fetch and rate limits.
- Paginated & incremental — page through large datasets and fetch only what changed since your last sync.
- Predictable errors — every error returns a machine-readable
codeand a human-readableerrormessage.
tokenId and signing secret are issued privately during onboarding and must never be shared or embedded in client-side code.Base URL & environments
All API endpoints are served under a single versioned base path:
| Item | Value |
|---|---|
| Protocol | HTTPS only (HTTP is not served) |
| API base URL | https://mdm.eqomos.com/api/external/v1 |
| Content type | application/json; charset=UTF-8 |
| Methods | GET only |
| API version | v1 (in the path) |
mdmexternalapi.eqomos.com) is informational only — API calls are made against the API base URL above.Onboarding & credentials
Access is granted per client. During onboarding you receive two values:
| Credential | Sent as | Description |
|---|---|---|
tokenId | Header X-Client-Id | Your public client identifier. Safe to log. Example: eqc_exampleClientId123. |
secret | Never sent | Your private HMAC signing key. Used only to compute the request signature. Store securely; it is shown only once at issue time. |
You are also told which APIs you are granted, and your per-API limits (page size, max records per fetch, requests per minute). To request access, additional APIs, or a secret rotation, contact your EqomOS account manager.
secret can call the API as you. Never put it in browsers, mobile apps, or public repositories. Rotate immediately if exposed.Authentication — HMAC request signing
Every request must be signed with HMAC-SHA256. The server recomputes the signature and rejects any request that does not match, is too old, or replays a previous request. This protects against tampering and replay attacks even though your secret is never transmitted.
Required headers
| Header | Required | Description |
|---|---|---|
X-Client-Id | required | Your tokenId. |
X-Timestamp | required | Current time as Unix epoch seconds (e.g. 1780805000). Must be within ±300 seconds of server time. |
X-Nonce | required | A unique random string per request (e.g. 16 random bytes hex-encoded). Each nonce may be used only once within the timestamp window. |
X-Signature | required | Base64-encoded HMAC-SHA256 of the canonical string (below), keyed with your secret. |
The canonical string
Build a string by joining these six elements with the newline character \n, in this exact order:
HTTP_METHOD (uppercase, e.g. GET)
REQUEST_PATH (e.g. /api/external/v1/categories — path only, no host, no query)
CANONICAL_QUERY (see below; empty string if no query params)
SHA256_HEX(body) (lowercase hex SHA-256 of the request body)
X-Timestamp (the same value sent in the header)
X-Nonce (the same value sent in the header)
Notes:
- Body hash: all endpoints are
GETwith an empty body, so the body hash is always the SHA-256 of the empty string:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 - CANONICAL_QUERY: take each query parameter as
key=value, percent-encode both key and value per RFC 3986 (unreserved charactersA–Z a–z 0–9 - . _ ~are left as-is), sort the encodedkey=valuepairs lexicographically, and join them with&. If there are no query parameters, use an empty string. - The
X-TimestampandX-Noncein the canonical string must be byte-for-byte identical to the header values.
Signing steps
- Generate
X-Timestamp(epoch seconds) and a fresh randomX-Nonce. - Build the canonical string as above.
- Compute
signature = Base64( HMAC_SHA256( canonicalString, secret ) ). - Send the request with the four headers; put
signatureinX-Signature.
Signing — code samples
The samples below call GET /api/external/v1/categories?isFullList=1&page=0&pageSize=50. Replace the placeholder credentials with the ones issued to you.
cURL + OpenSSL (bash)
TOKEN_ID="eqc_exampleClientId123"
SECRET="EXAMPLE_SECRET_DO_NOT_USE"
METHOD="GET"
PATH="/api/external/v1/categories"
QUERY="isFullList=1&page=0&pageSize=50" # already sorted by encoded key=value
TS=$(date +%s)
NONCE=$(openssl rand -hex 16)
BODY_HASH="e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" # sha256("")
CANON=$(printf '%s\n%s\n%s\n%s\n%s\n%s' "$METHOD" "$PATH" "$QUERY" "$BODY_HASH" "$TS" "$NONCE")
SIG=$(printf '%s' "$CANON" | openssl dgst -sha256 -hmac "$SECRET" -binary | base64)
curl -sS "https://mdm.eqomos.com${PATH}?${QUERY}" \
-H "X-Client-Id: $TOKEN_ID" \
-H "X-Timestamp: $TS" \
-H "X-Nonce: $NONCE" \
-H "X-Signature: $SIG"
Python 3
import time, os, hmac, hashlib, base64, requests
from urllib.parse import quote, unquote
BASE = "https://mdm.eqomos.com"
TOKEN = "eqc_exampleClientId123"
SECRET = "EXAMPLE_SECRET_DO_NOT_USE"
EMPTY = hashlib.sha256(b"").hexdigest()
def canonical_query(q):
if not q: return ""
pairs = []
for part in q.split("&"):
k, _, v = part.partition("=")
pairs.append(quote(unquote(k), safe="-._~") + "=" + quote(unquote(v), safe="-._~"))
return "&".join(sorted(pairs))
def get(path, query=""):
ts = str(int(time.time()))
nonce = os.urandom(16).hex()
canon = "\n".join([ "GET", path, canonical_query(query), EMPTY, ts, nonce ])
sig = base64.b64encode(hmac.new(SECRET.encode(), canon.encode(), hashlib.sha256).digest()).decode()
headers = { "X-Client-Id": TOKEN, "X-Timestamp": ts, "X-Nonce": nonce, "X-Signature": sig }
url = BASE + path + (("?" + query) if query else "")
return requests.get(url, headers=headers)
resp = get("/api/external/v1/categories", "isFullList=1&page=0&pageSize=50")
print(resp.status_code, resp.json())
Node.js
const crypto = require("crypto");
const BASE = "https://mdm.eqomos.com";
const TOKEN = "eqc_exampleClientId123";
const SECRET = "EXAMPLE_SECRET_DO_NOT_USE";
const EMPTY = crypto.createHash("sha256").update("").digest("hex");
const pct = s => encodeURIComponent(s).replace(/[!*'()]/g, c => "%" + c.charCodeAt(0).toString(16).toUpperCase());
function canonicalQuery(q) {
if (!q) return "";
return q.split("&").map(p => {
const [k, v = ""] = p.split("=");
return pct(decodeURIComponent(k)) + "=" + pct(decodeURIComponent(v));
}).sort().join("&");
}
async function get(path, query = "") {
const ts = Math.floor(Date.now() / 1000).toString();
const nonce = crypto.randomBytes(16).toString("hex");
const canon = ["GET", path, canonicalQuery(query), EMPTY, ts, nonce].join("\n");
const sig = crypto.createHmac("sha256", SECRET).update(canon).digest("base64");
const url = BASE + path + (query ? "?" + query : "");
const res = await fetch(url, { headers: {
"X-Client-Id": TOKEN, "X-Timestamp": ts, "X-Nonce": nonce, "X-Signature": sig } });
console.log(res.status, await res.json());
}
get("/api/external/v1/categories", "isFullList=1&page=0&pageSize=50");
Java 11+
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URI;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.*;
String base = "https://mdm.eqomos.com", token = "eqc_exampleClientId123", secret = "EXAMPLE_SECRET_DO_NOT_USE";
String path = "/api/external/v1/categories", query = "isFullList=1&page=0&pageSize=50";
String emptyBody = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; // sha256("")
String ts = String.valueOf(System.currentTimeMillis() / 1000L);
String nonce = UUID.randomUUID().toString().replace("-", "");
String canon = String.join("\n", "GET", path, query, emptyBody, ts, nonce); // query already sorted+encoded
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
String sig = Base64.getEncoder().encodeToString(mac.doFinal(canon.getBytes(StandardCharsets.UTF_8)));
HttpRequest req = HttpRequest.newBuilder(URI.create(base + path + "?" + query))
.header("X-Client-Id", token).header("X-Timestamp", ts)
.header("X-Nonce", nonce).header("X-Signature", sig).GET().build();
HttpResponse<String> res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.statusCode() + " " + res.body());
Common parameters
These query parameters are accepted by most endpoints. All are optional.
| Parameter | Type | Default | Description |
|---|---|---|---|
isFullList | integer (0 or 1) | 0 | 1 = return the full dataset (ignores syncDate). 0 = incremental: when combined with syncDate, returns only records created or updated on/after that time. |
syncDate | ISO-8601 date or datetime | — | Your last successful sync time, e.g. 2026-06-01 or 2026-06-01T09:30:00. Returns records where the change time (modifiedAt) is on/after this value. Ignored when isFullList=1. |
supplierId | integer | — | Restrict results to a single supplier. |
page | integer (≥0) | 0 | Zero-based page index. |
pageSize | integer (≥1) | per-client default | Records per page. Capped per client (see Pagination & limits). |
Endpoint-specific filters (e.g. productid, skuid, supplierskucode, orderRef) are documented under each API.
Pagination & limits
Results are paginated. Iterate by increasing page until hasNext is false.
pageSizemust not exceed your client's maximum page size. A request above the cap returns400 PAGE_SIZE_EXCEEDED.- A single fetch never returns more than your client's max records per fetch cap.
- If you omit
pageSize, your client's configured default is used.
page=0, keep your chosen pageSize constant, and stop when hasNext=false. Persist totalRecords if you need a progress indicator.Incremental sync
To keep a local copy in sync efficiently, do a one-time full load and then poll for changes:
- Initial load: call with
isFullList=1and page through all results. Record the time you started the sync. - Incremental: on each later run, call with
isFullList=0&syncDate=<last sync time>. You receive only records changed on/aftersyncDate. - After a successful run, store the new sync time for the next call.
modifiedAt). Use a small safety overlap (e.g. subtract a few minutes from your stored time) to avoid missing records written during the previous run.Response envelope
All list endpoints return the same paginated envelope:
{
"data": [ /* array of records for this page */ ],
"page": 0,
"pageSize": 50,
"totalRecords": 124,
"totalPages": 3,
"hasNext": true
}
| Field | Type | Description |
|---|---|---|
data | array | The records for this page (schema differs per API). |
page | integer | Zero-based index of the returned page. |
pageSize | integer | Effective page size used. |
totalRecords | integer | Total records matching the query across all pages. |
totalPages | integer | Total number of pages. |
hasNext | boolean | true if another page is available. |
Common field conventions used inside records:
| Field | Meaning |
|---|---|
supplierId | Identifier of the supplier the record belongs to. |
mapped*Id (e.g. mappedCategoryId) | The EqomOS platform's canonical identifier for the entity, once mapped. May be null if not yet mapped. |
status | Record lifecycle state. Only finalised master data is exposed (see FAQ). |
modifiedAt | Last-change timestamp (ISO-8601), used by syncDate filtering. |
fields ending in Json | A nested JSON document passed through as a string; parse client-side if needed. |
Errors & status codes
Errors return a JSON body with a machine-readable code and a human error message:
{ "error": "Request signature verification failed.", "code": "INVALID_SIGNATURE" }
| HTTP | code | Meaning & how to fix |
|---|---|---|
| 401 | MISSING_AUTH_HEADERS | One or more of the four required headers is missing. |
| 401 | INVALID_CLIENT | X-Client-Id is unknown or the client is suspended/revoked. |
| 401 | BAD_TIMESTAMP | X-Timestamp is not valid epoch seconds. |
| 401 | TIMESTAMP_SKEW | X-Timestamp is outside the ±300s window. Sync your clock (NTP). |
| 401 | NONCE_REPLAY | This X-Nonce was already used. Generate a fresh nonce per request. |
| 401 | INVALID_SIGNATURE | Signature did not match. Check the canonical string, query encoding/order, and secret. |
| 403 | IP_NOT_ALLOWED | Your source IP is not in the client's whitelist. |
| 403 | DOMAIN_NOT_ALLOWED | The request Origin is not whitelisted (browser callers only). |
| 403 | API_NOT_GRANTED | Your client is not granted access to this API. Contact your account manager. |
| 400 | PAGE_SIZE_EXCEEDED | pageSize is above your client's maximum. |
| 400 | BAD_SYNC_DATE | syncDate is not valid ISO-8601. |
| 400 | INVALID_PARAMETER | A parameter failed validation (e.g. isFullList not 0/1, negative page). |
| 429 | RATE_LIMITED | You exceeded your per-minute request limit. Back off and retry. |
| 503 | REPLAY_STORE_DOWN | Temporary server-side condition. Retry after a short delay. |
Rate limiting
Each client has a per-minute request limit per API. Exceeding it returns 429 RATE_LIMITED. Build a backoff (e.g. wait until the next minute, or exponential backoff with jitter) and avoid bursty parallel calls. For large extracts, page sequentially rather than firing many pages concurrently.
API reference
All endpoints are GET, require the four authentication headers, and return the paginated envelope. Only finalised master data is returned.
GET Categories
Returns the category catalogue, including each category's hierarchy level and full breadcrumb path.
Query parameters
| Name | Type | Req? | Description |
|---|---|---|---|
isFullList | 0/1 | opt | Full vs incremental (see Common parameters). |
syncDate | ISO-8601 | opt | Return categories changed on/after this time. |
supplierId | integer | opt | Filter to one supplier. |
page, pageSize | integer | opt | Pagination. |
Response fields (per item)
| Field | Type | Description |
|---|---|---|
id | integer | Category record identifier. |
supplierId | integer | Owning supplier. |
categoryCode | string | Supplier's category code (unique within a supplier). |
categoryName | string | Display name. |
parentCode | string | Parent category's code; null for root categories. |
level | integer | Depth in the hierarchy: 0=root, 1=parent, 2=child, 3=sub-child, and so on. |
path | string | Breadcrumb of names from root to this node, e.g. "Electronics > Phones > Smartphones". |
mappedCategoryId | integer | EqomOS platform category id (may be null). |
status | string | Record lifecycle state. |
modifiedAt | datetime | Last-change timestamp. |
Example response
{
"data": [
{ "id": 124, "supplierId": 7, "categoryCode": "ELEC", "categoryName": "Electronics",
"parentCode": null, "level": 0, "path": "Electronics",
"mappedCategoryId": 9001, "status": "SYNCED", "modifiedAt": "2026-06-01T10:15:00" },
{ "id": 127, "supplierId": 7, "categoryCode": "ANDROID", "categoryName": "Android Phones",
"parentCode": "SMART", "level": 3,
"path": "Electronics > Phones > Smartphones > Android Phones",
"mappedCategoryId": 9004, "status": "SYNCED", "modifiedAt": "2026-06-01T10:16:20" }
],
"page": 0, "pageSize": 50, "totalRecords": 6, "totalPages": 1, "hasNext": false
}
GET Brands
Returns the brand list, including the brand logo URL where available.
Query parameters
| Name | Type | Req? | Description |
|---|---|---|---|
isFullList | 0/1 | opt | Full vs incremental. |
syncDate | ISO-8601 | opt | Return brands changed on/after this time. |
supplierId | integer | opt | Filter to one supplier. |
page, pageSize | integer | opt | Pagination. |
Response fields (per item)
| Field | Type | Description |
|---|---|---|
id | integer | Brand record identifier. |
supplierId | integer | Owning supplier. |
brandCode | string | Supplier's brand code. |
brandName | string | Display name. |
mappedBrandId | integer | EqomOS platform brand id (may be null). |
logoUrl | string | Brand logo URL; null if none is available. |
status | string | Record lifecycle state. |
modifiedAt | datetime | Last-change timestamp. |
Example response
{
"data": [
{ "id": 51, "supplierId": 7, "brandCode": "ACME", "brandName": "Acme",
"mappedBrandId": 3300, "logoUrl": "https://cdn.example.com/brands/acme.png",
"status": "SYNCED", "modifiedAt": "2026-05-30T08:00:00" }
],
"page": 0, "pageSize": 50, "totalRecords": 1, "totalPages": 1, "hasNext": false
}
GET Products
Returns product master records with descriptive, dimensional and specification attributes.
Query parameters
| Name | Type | Req? | Description |
|---|---|---|---|
productid | integer | opt | Return a single product by its id. |
isFullList | 0/1 | opt | Full vs incremental. |
syncDate | ISO-8601 | opt | Return products changed on/after this time. |
supplierId | integer | opt | Filter to one supplier. |
page, pageSize | integer | opt | Pagination. |
Response fields (per item)
| Field | Type | Description |
|---|---|---|
id | integer | Product record identifier (use as productid filter). |
supplierId | integer | Owning supplier. |
productCode | string | Supplier's product code. |
productName | string | Product name. |
categoryCode | string | Supplier category code this product belongs to. |
mappedCategoryId | integer | Mapped platform category id (nullable). |
brandCode | string | Supplier brand code. |
mappedBrandId | integer | Mapped platform brand id (nullable). |
description | string | Long description. |
warranty | string | Warranty text. |
weight, length, breadth, height | number | Physical dimensions. |
specificationJson | string (JSON) | Structured specifications, as a JSON string. |
variantMappingJson | string (JSON) | Variant-to-SKU mapping, as a JSON string. |
mappedProductId | integer | Mapped platform product id (nullable). |
status | string | Record lifecycle state. |
modifiedAt | datetime | Last-change timestamp. |
GET SKUs
Returns SKU master records: identity, pricing, variants, dimensions, compliance and merchandising attributes.
Query parameters
| Name | Type | Req? | Description |
|---|---|---|---|
skuid | integer | opt | Return a single SKU by its id. |
supplierskucode | string | opt | Filter by supplier SKU code. |
isFullList | 0/1 | opt | Full vs incremental. |
syncDate | ISO-8601 | opt | Return SKUs changed on/after this time. |
supplierId | integer | opt | Filter to one supplier. |
page, pageSize | integer | opt | Pagination. |
Response fields (selected)
| Field | Type | Description |
|---|---|---|
id | integer | SKU record identifier (use as skuid filter). |
supplierId | integer | Owning supplier. |
skuCode | string | Supplier SKU code. |
sellerSkuCode | string | Platform-assigned seller SKU code. |
skuName | string | SKU name. |
parentCode | string | Parent SKU code (for variants); null if standalone. |
isParent | integer | 1 if this SKU is a variant parent, else 0. |
productCode, mappedProductId | string / integer | Owning product reference. |
categoryCode, mappedCategoryId | string / integer | Category reference. |
brandCode, mappedBrandId | string / integer | Brand reference. |
mappedSkuId | integer | Mapped platform SKU id (nullable). |
mrp, offerPrice, tp, shippingPrice | number | MRP, offer price, transfer price, shipping price. |
variants | array | Up to 4 variant axes, each { "label": "Color", "value": "Red" }. |
measureUnit, measureValue | string / number | Unit of measure and value. |
hsnCode, taxPercentage, originCountry | string / number / string | Tax/compliance attributes. |
weight, length, breadth, height | number | Dimensions. |
minOrderQty, deliveryTime | integer / string | Ordering attributes. |
isCodAllowed, isPrepaidAllowed, isWebAllowed, isAppAllowed | integer | Channel/payment availability flags (0/1). |
title, metaTitle, metaKeyword, metaDescription | string | SEO/merchandising metadata. |
specificationJson, variantMappingJson | string (JSON) | Structured attributes as JSON strings. |
status, modifiedAt | string / datetime | Lifecycle state and last-change time. |
GET Price & Inventory
Returns offer price, MRP and per-warehouse stock for SKUs, merged into one record per SKU.
Query parameters
| Name | Type | Req? | Description |
|---|---|---|---|
skuid | integer | opt | Filter by mapped platform SKU id. |
supplierskucode | string | opt | Filter by supplier SKU code. |
isFullList | 0/1 | opt | Full vs incremental. |
syncDate | ISO-8601 | opt | Return records changed on/after this time. |
supplierId | integer | opt | Filter to one supplier. |
page, pageSize | integer | opt | Pagination. |
Response fields (per item)
| Field | Type | Description |
|---|---|---|
supplierId | integer | Owning supplier. |
skuCode | string | Supplier SKU code. |
mappedSkuId | integer | Mapped platform SKU id (nullable). |
mrp | number | Maximum retail price. |
offerPrice | number | Current selling/offer price. |
tp | number | Transfer/cost price. |
effectiveFrom, effectiveTo | datetime | Price validity window (nullable). |
inventory | array | Per-warehouse stock: { "warehouseCode": "...", "mappedWhId": 0, "qty": 0 }. |
totalQty | integer | Sum of qty across all warehouses. |
modifiedAt | datetime | Most recent change across price and inventory. |
Example response
{
"data": [
{ "supplierId": 7, "skuCode": "ACME-WIDGET-RED", "mappedSkuId": 88012,
"mrp": 1999.0, "offerPrice": 1499.0, "tp": 1100.0,
"effectiveFrom": "2026-05-01T00:00:00", "effectiveTo": null,
"inventory": [
{ "warehouseCode": "WH-MUM", "mappedWhId": 12, "qty": 40 },
{ "warehouseCode": "WH-DEL", "mappedWhId": 15, "qty": 12 }
],
"totalQty": 52, "modifiedAt": "2026-06-02T07:45:00" }
],
"page": 0, "pageSize": 50, "totalRecords": 1, "totalPages": 1, "hasNext": false
}
GET Orders
Returns orders with header, customer, shipping/billing addresses and line items. Line items are grouped under each order — an order is never split across pages.
Query parameters
| Name | Type | Req? | Description |
|---|---|---|---|
orderRef | string | opt | Return a single order by its reference. |
isFullList | 0/1 | opt | Full vs incremental. |
syncDate | ISO-8601 | opt | Return orders changed on/after this time. |
supplierId | integer | opt | Filter to one supplier. |
page, pageSize | integer | opt | Pagination (by distinct order). |
Response fields (per order)
| Field | Type | Description |
|---|---|---|
supplierId | integer | Owning supplier. |
orderRef | string | Supplier order reference. |
mappedOrderId | integer | Mapped platform order id (nullable). |
orderType | string | Order type/category. |
orderStatus | string | Order status. |
orderDate | datetime | Order creation time. |
currency | string | Currency code. |
orderTotal | number | Order total amount. |
totalItems | integer | Number of items in the order. |
customer | object | { firstName, lastName, email, phone }. |
shippingAddress, billingAddress | object | { name, company, phone, email, gst, addr1, addr2, city, state, pincode, country }. |
items | array | Line items: { skuCode, sellerSkuCode, mappedSkuId, name, qty, price, subtotal, lineStatus }. |
modifiedAt | datetime | Most recent change across the order's rows. |
Example response
{
"data": [
{ "supplierId": 7, "orderRef": "SO-100245", "mappedOrderId": null,
"orderType": "ORDER_PUNCH", "orderStatus": "PROCESSING",
"orderDate": "2026-06-03T11:20:00", "currency": "INR",
"orderTotal": 2998.0, "totalItems": 2,
"customer": { "firstName": "Sample", "lastName": "Buyer",
"email": "buyer@example.com", "phone": "+91-90000-00000" },
"shippingAddress": { "name": "Sample Buyer", "company": null, "phone": "+91-90000-00000",
"email": "buyer@example.com", "gst": null, "addr1": "12 Sample Street", "addr2": null,
"city": "Mumbai", "state": "MH", "pincode": "400001", "country": "IN" },
"billingAddress": { "name": "Sample Buyer", "company": null, "phone": "+91-90000-00000",
"email": "buyer@example.com", "gst": null, "addr1": "12 Sample Street", "addr2": null,
"city": "Mumbai", "state": "MH", "pincode": "400001", "country": "IN" },
"items": [
{ "skuCode": "ACME-WIDGET-RED", "sellerSkuCode": "SLR-001", "mappedSkuId": 88012,
"name": "Acme Widget (Red)", "qty": 2, "price": 1499.0, "subtotal": 2998.0,
"lineStatus": "CONFIRMED" }
],
"modifiedAt": "2026-06-03T11:21:10" }
],
"page": 0, "pageSize": 50, "totalRecords": 1, "totalPages": 1, "hasNext": false
}
FAQ
Why is my dataset empty even though data exists?
The API only returns finalised master data (records that have completed internal validation/mapping). Records still in earlier processing stages are not exposed. If you expect data and see none, it may not yet be finalised.
My signature keeps failing (401 INVALID_SIGNATURE). What should I check?
Confirm: (1) the canonical string uses real newlines in the exact 6-line order; (2) you used the empty-body SHA-256 constant; (3) query parameters are percent-encoded and sorted identically to what you send; (4) X-Timestamp/X-Nonce in the string match the headers; (5) you are signing with the correct secret.
How fresh is the data?
Use modifiedAt as the source of truth for freshness and drive incremental syncs from it.
Can I call this from a browser?
No. Your secret must never be exposed to a browser or mobile app. Always call server-to-server.
Changelog
| Version | Notes |
|---|---|
v1 | Initial release: Categories, Brands, Products, SKUs, Price & Inventory, Orders. HMAC-SHA256 signing, pagination, incremental sync, per-client grants and rate limits. |