Incremental sync
Keep an existing copy of inventory and documents up to date by retrieving changed record IDs and fetching their current data.
Read sync changes
GET/api/v3/sync
Required scopes: items:read, invoices:read, estimates:read, purchase_orders:read, sales_orders:read only for the resources selected.
For example, selecting only item requires only items:read.
The API user must have account-wide read visibility for each selected resource. Inventory users assigned to a single location and document users limited to their own records cannot sync those resources. Deleted markers do not retain former ownership or location, so this restriction prevents disclosure of inaccessible record IDs. Normal record endpoints continue to enforce field permissions such as unit-cost visibility.
| Parameter | Type | Description |
|---|---|---|
start | string | Use now to obtain a checkpoint before an initial full download. |
since | string or integer | Start from ISO 8601 with an explicit timezone, such as 2026-09-01T00:00:00Z, or Unix epoch seconds, such as 1788220800. The boundary second is included. Must not be in the future or more than 90 days old. |
cursor | string | Continue from a previously returned cursor or next_cursor. |
resources | string | Required with start or since; comma-separated values from item, invoice, estimate, purchase_order, sales_order. Omit with cursor; the selection is retained in the token. |
limit | integer | Maximum change markers examined per page, from 1 to 500; default 100. A page may contain fewer matching changes or none. |
Supply exactly one of start, since, or cursor. Unknown parameters and malformed values return 400. Encode query values, especially + in timezone offsets. Unix values must be non-negative integer seconds (at most 10 digits). Milliseconds, fractional Unix seconds, and scientific notation are rejected with 400; values are never automatically rescaled. The v2 modifiedSince parameter name is not accepted; pass its epoch-seconds value as since instead.
Start from a timestamp
If your integration already has a complete copy, request changes since its last known checkpoint:
curl --get 'https://yourbusiness.salesbinder.com/api/v3/sync' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--data-urlencode 'resources=item,invoice' \
--data-urlencode 'since=2026-09-01T00:00:00Z'The equivalent Unix-seconds request is:
curl --get 'https://yourbusiness.salesbinder.com/api/v3/sync' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--data-urlencode 'resources=item,invoice' \
--data-urlencode 'since=1788220800'Unix timestamps identify an absolute instant relative to the UTC epoch; no timezone suffix is needed. Both formats use the same inclusive boundary-second, 90-day retention, future-date validation, and cursor continuation rules. For example, 1788220800000 is milliseconds and is rejected rather than converted.
{
"object": "sync_page",
"resources": ["invoice", "item"],
"changes": [
{"resource": "item", "id": "90000000-0000-4000-8000-000000000101", "operation": "upsert"},
{"resource": "invoice", "id": "90000000-0000-4000-8000-000000000102", "operation": "delete"}
],
"has_more": false,
"next_cursor": "OPAQUE_CURSOR"
}Use next_cursor for every subsequent request, including the next scheduled poll. Do not replace it with your local clock or a record's updated_at value. The timestamp entry point uses the change log's timestamp, not the parent record's updated_at field. Subsecond timestamps include the whole boundary second, so duplicate processing must be safe.
Start a full download
Request start=now and save the returned cursor before downloading your initial records:
curl --get 'https://yourbusiness.salesbinder.com/api/v3/sync' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--data-urlencode 'resources=item,invoice' \
--data-urlencode 'start=now'{
"object": "sync_start",
"resources": ["invoice", "item"],
"retention_days": 90,
"cursor": "OPAQUE_CURSOR"
}Download your selected collections through their existing endpoints, then consume changes using this saved cursor to catch changes made during the download. Use stable record IDs for local upserts. For a complete inventory download, page through /api/v3/items?archived=all&include_sold=true; ordinary inventory lists exclude sold unique items. The change feed alone does not enumerate historical records that have no retained change marker. For lists that can shift during concurrent edits, reconcile the initial enumeration before treating it as a complete snapshot.
Apply a change page
curl --get 'https://yourbusiness.salesbinder.com/api/v3/sync' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--data-urlencode 'cursor=OPAQUE_CURSOR' \
--data-urlencode 'limit=100'For upsert, retrieve the record's current representation and insert or replace your local copy. For delete, remove the local record; no delete request to SalesBinder is needed.
| Resource | Fetch current records |
|---|---|
item | /api/v3/items?ids=UUID,UUID |
invoice | /api/v3/invoices?ids=UUID,UUID |
estimate | /api/v3/estimates?ids=UUID,UUID |
purchase_order | /api/v3/purchase-orders?ids=UUID,UUID |
sales_order | /api/v3/sales-orders?ids=UUID,UUID |
Batch at most 50 IDs of the same resource per request. Fetch related detail endpoints when your copy needs data outside the canonical response, such as variations or payment history. Missing IDs in a successful exact-ID response can be removed locally. Do not interpret a failed request as a deletion.
Save next_cursor only after successfully applying the entire page. On failure, retry from the last saved cursor and tolerate duplicates. Continue whenever has_more is true, even if changes is empty. When false, save the cursor for your next poll. Standard API rate limits apply to both change requests and record retrieval; respect 429 responses.
Consistency and retention
This is a compacted change feed, not an audit history or snapshot. Multiple changes to one record may produce one marker representing its latest state. Creation and updates both use upsert; archival is an update. Changes to a record during pagination may move it into the next polling cycle. Keep polling after finishing the current pages. Fetched data can be newer than its marker.
The feed reuses the application's recorded parent-resource changes, including related mutations that mark the parent. It does not backfill changes made before tracking existed, and it is not a feed for every reference-data edit. Maintain reference data separately if your integration needs it.
Cursors are signed and bound to the account, user, selected resources, and effective permissions. A permission or scope change may require rebuilding the local copy. Do not share cursors between integrations with different permissions. Internal mobile cursors are not accepted.
History is retained for 90 days. An expired cursor, a timestamp outside retention, or pruning during a timestamp catch-up returns 409 rather than silently skipping history. Start a new full download after expiration; do not reset to start=now and keep an old local copy without reconciliation.
| Error code | Meaning |
|---|---|
invalid_query_parameter | Invalid or conflicting parameters (400). |
sync_cursor_invalid | Malformed, tampered, or unsupported cursor (400). |
sync_permission_denied | Missing resource scope or account-wide visibility (403). |
sync_cursor_owner_mismatch | Cursor belongs to another account or user (403). |
sync_scope_changed | Effective permissions changed; rebuild the copy (409). |
sync_cursor_expired | Cursor or retained history expired; rebuild the copy (409). |
sync_full_refresh_required | The starting timestamp is older than retained history (409). |