Skip to content

Product synchronisation

PUT /api/v1/businesses/{businessId}/sync/products — scope products:write, application tokens only.

{
  "products": [{
    "externalId": "menu-981",
    "name": "Kota — The Works",
    "description": "Quarter loaf, chips, polony, cheese, egg",
    "category": { "externalId": "cat-12", "name": "Kotas" },
    "imageUrl": "https://cdn.example.com/kota.jpg",
    "taxClass": "STANDARD",
    "type": "STANDARD",
    "isActive": true,
    "variants": [
      { "externalId": "menu-981", "name": "Default", "sku": "KOTA-WORKS", "price": "65.00", "barcodes": [] }
    ],
    "modifierGroups": [
      { "name": "Extras", "minSelect": 0, "maxSelect": 3, "modifiers": [{ "name": "Extra cheese", "priceDelta": "8.00" }] }
    ]
  }]
}

Up to 200 products per call (pos.products.syncAll batches for you).

Behaviour

  • Upsert by external ID. First sight creates the product, its variants and category and records the mappings; later calls update.
  • Unchanged is free. Each product's payload is hashed; an identical resend is reported unchanged and touches nothing.
  • Per-product transactions. One bad product (say a SKU clash) fails alone; the run is PARTIAL and the rest are saved.
  • Ownership respected. Fields POS owns are not overwritten; differing values become conflicts. See Connector specification §6.
  • Removed variants (no longer in the payload) are archived, never deleted — past sales still reference them.
  • Prices are in the business's display basis (VAT-inclusive for most South African businesses). taxClass is STANDARD, ZERO_RATED or EXEMPT; VAT is computed by POS from effective-dated rates.
  • Events. Each created or updated product emits product.created / product.updated with a source field naming your application — ignore events you caused to avoid loops.

Response

{ "data": {
  "run": { "id": "…", "status": "SUCCEEDED", "created": 1, "updated": 0, "unchanged": 0, "failed": 0, "conflicts": 0, "errors": [] },
  "results": [{ "externalId": "menu-981", "productId": "cm…", "status": "created", "variants": [{ "externalId": "menu-981", "variantId": "cm…" }] }]
} }

Every run is stored as a SyncRun and visible to the business under Integrations and to TechTur in the Control Centre.

Reading back

  • GET …/products?updatedSince=2026-11-01T00:00:00Z — products changed in POS.
  • GET …/mappings?entityType=variant&externalId=… — resolve one of your IDs.