Skip to main content

Order Sync

This integration pattern enables you to synchronise Scoffable orders with your system, maintaining an accurate record of all orders and their changes over time.

Common Use Cases

  • EPOS Systems: Automatically record sales and track inventory changes
  • Accounting Platforms: Maintain accurate financial records with real-time updates
  • Analytics Systems: Build comprehensive order history for business intelligence
  • Fulfilment Services: Track order status and manage delivery workflows

Core Concepts

Order Versioning

Orders in Scoffable are dynamic - they can change after being placed. Every order has a version number that increments with each change. Each version is a complete snapshot of the order at that moment, not just the changes.

For detailed information about versioning, see Order Versioning in the Understanding Orders guide.

The Stream Model

The get-order-updates endpoint provides these versions as a chronological stream. Think of it like a timeline of all order activity that you can read from any point and resume where you left off.

State Management

The API uses pagination with a nextPageId token to track your position in the stream. You must persist this token between sync cycles to avoid missing or duplicating orders. See Pagination for details.

Quick Start

Prerequisites

Before starting, ensure you have:

  • A valid Bearer token (see Authentication)
  • A method to persist the sync position (nextPageId)

Starting Your Integration

You have two options when beginning your sync:

Option 1: Start from a specific date

GET https://partners-api.scoffable.com/v1/orderUpdates?fromTimestamp=2025-01-01T00:00:00Z
Authorization: Bearer YOUR_TOKEN

Option 2: Start from earliest available data

GET https://partners-api.scoffable.com/v1/orderUpdates
Authorization: Bearer YOUR_TOKEN

Response:

{
"hasMore": true,
"data": [
{
"id": 749274,
"version": 4,
"latestVersion": true,
"vendorId": "YOUR_VENDOR_ID_1",
"status": "accepted",
"type": "delivery",
"placedAt": 1641038400000,
"total": {
"amount": 2399,
"currency": "GBP"
},
"items": [
{
"name": "Margherita Pizza",
"quantity": 2,
"price": {"amount": 1199, "currency": "GBP"}
}
]
}
],
"nextPageId": "f0794ec6-a61b-418d-8911-d27a343fe55c"
}

Continue retrieving pages until hasMore is false. Store the final nextPageId to resume syncing from this position.

What to Sync

Not all order data will be relevant to your system. Here's what different systems typically track:

System TypeKey FieldsTypical Use
EPOSitems, total, customerPayments, statusRecording sales for accepted orders
Accountingtotal, serviceFee, deliveryFee, customerPaymentsFinancial reconciliation of accepted orders
Deliverystatus, type, deliveryAddress, deliveryProviderManaging fulfilment for accepted orders
AnalyticsAll fieldsComplete order history including all statuses

Common fields you might process:

  • status: Current order status (placed, accepted, rejected, cancelled)
  • items: Products ordered, including quantities and modifications
  • total: Final amount charged
  • customerPayments: How the customer paid
  • version & latestVersion: For tracking changes

Note on order status: Orders with status placed or rejected were never accepted by the vendor. Most integrations skip these orders and only process accepted or cancelled orders (since cancelled orders were initially accepted).

For detailed explanations of order structure, including substitutions, price adjustments, option categories, and payment types, see Understanding Orders.

Implementation Strategies

Your choice of implementation strategy depends on how your system handles data changes. Most EPOS and accounting systems fall into one of two categories:

Systems with Versioning

If your system supports versioning (can update existing records or maintain version history), simply process all order versions as they arrive:

const response = await fetch('https://partners-api.scoffable.com/v1/orderUpdates?' + new URLSearchParams({
pageId: savedPageId
}));

for (const order of response.data) {
// Optionally skip orders that were never accepted
if (order.status === 'placed' || order.status === 'rejected') {
continue;
}

await upsertOrder(order); // Update/record order version
}

Systems without Versioning

For systems using immutable records where changes require correction entries, you have a choice:

  • Process all updates immediately and handle multiple corrections per order
  • Use the features below to reduce corrections by waiting for stable data

If minimising corrections is important:

  • minAgeMinutes (request parameter) delays your view of the version stream. Setting minAgeMinutes=90 means you only see versions that were created 90+ minutes ago. Orders typically receive most updates in their first 90 minutes (acceptance, courier assignment, ETA changes), then stabilise. This delay means you're more likely to process the final state rather than intermediate changes.

  • latestVersion (response field) tells you whether each order version is the newest one globally at the time of the response. When latestVersion=false, a newer version exists somewhere in the system, so you can skip processing this outdated version entirely.

Using both together maximises efficiency: the delay from minAgeMinutes means many order versions in your response will have latestVersion=false, allowing you to skip most outdated versions and process mainly final states. This significantly reduces correction entries in your system:

const response = await fetch('https://partners-api.scoffable.com/v1/orderUpdates?' + new URLSearchParams({
pageId: savedPageId,
minAgeMinutes: 90 // Delay stream by 90 minutes
}));

const { data, nextPageId } = await response.json();

for (const order of data) {
// Skip outdated versions
if (!order.latestVersion) {
continue;
}

// Optionally skip orders that were never accepted
if (order.status === 'placed' || order.status === 'rejected') {
continue;
}

await createImmutableRecord(order);
}

Best Practices

Polling Frequency

We recommend polling every 5-10 minutes for most integrations. See Rate Limits for limits.

Error Handling

Implement retries with exponential backoff for transient failures. If you receive a 429 rate limit response, respect the Retry-After header. See Rate Limits for handling rate limit responses.

Resilience

Always save your nextPageId after successfully processing each page. This allows you to resume from the correct position if your sync process is interrupted.

Duplicate Prevention

Don't rely solely on nextPageId to prevent duplicate processing. Network issues or retries might cause you to receive the same orders again. Maintain your own record of which order ID + version combinations you've already processed to avoid processing duplicates.