Skip to main content

Pagination and syncing

List endpoints share the same cursor pagination and sync parameters, so one sync loop works for vehicles, enquiries, customers, deals and everything else.

Two lists differ: GET /v1/events is ordered by when each event happened (see Webhooks and events), and GET /v1/theme-versions is paged by number (see Website themes).

Cursor pagination​

ParameterDescription
limitItems per page, 1 to 100. Default 25.
cursorThe meta.next_cursor from the previous page. Omit it for the first page.

Every list response includes:

{
"data": [],
"meta": {
"next_cursor": "eyJpdiI6...",
"has_more": true
}
}

Keep requesting with cursor=<next_cursor> until has_more is false (next_cursor is then null).

Cursors are opaque. Do not build or edit them; a cursor that has been changed is refused with 422 validation_failed.

Ordering​

Lists are always ordered by updated_at, then id, ascending: oldest change first. That makes the last item on the last page the most recently changed one, which is exactly what a sync job needs to remember.

Incremental sync with updated_since​

Every list accepts updated_since, an ISO 8601 timestamp. Only items whose updated_at is at or after that time are returned.

curl "https://api.vehiso.com/v1/enquiries?updated_since=2026-09-27T14:00:00Z&limit=100" \
-H "Authorization: Bearer $VEHISO_API_KEY"

The comparison is inclusive, so the item you saw last on the previous run is returned again. Make your writes upserts keyed on id and that repeat is harmless; it also guarantees that nothing updated in the same second is skipped.

Deletions: include_deleted​

A normal list leaves out deleted items. To mirror deletes, add include_deleted=true to any list of records that can be deleted (vehicles, enquiries, customers, deals, appointments and job cards, for example). Deleted items then appear in the same ordered stream as tombstones:

{
"id": "b7e3a1c2-9f4d-4a6e-8c21-7d5b0e9f3a64",
"deleted": true,
"deleted_at": "2026-09-27T09:15:42Z"
}

A tombstone has only these three fields. When you see "deleted": true, delete (or mark deleted) the row with that id in your copy.

Worked example: mirror to a warehouse​

This job copies a dealer's customers into a warehouse table and keeps it current. Run it on a schedule (every few minutes is plenty). It stores one value between runs: the highest updated_at it has processed.

The same loop works for any list endpoint; change the path and the upsert.

import os
import time
import requests

API = "https://api.vehiso.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['VEHISO_API_KEY']}"}


def sync_customers(db):
since = db.get_checkpoint("customers") # None on the first run
params = {"limit": 100, "include_deleted": "true"}
if since:
params["updated_since"] = since

newest = since
while True:
res = requests.get(f"{API}/customers", params=params, headers=HEADERS, timeout=30)
if res.status_code == 429:
time.sleep(int(res.headers.get("Retry-After", "5")))
continue
res.raise_for_status()
body = res.json()

for item in body["data"]:
if item.get("deleted"):
db.delete_customer(item["id"])
newest = max(newest or "", item["deleted_at"])
else:
db.upsert_customer(item) # keyed on item["id"]
newest = max(newest or "", item["updated_at"])

if not body["meta"]["has_more"]:
break
params["cursor"] = body["meta"]["next_cursor"]

if newest:
db.set_checkpoint("customers", newest)

Why this is safe:

  • Ordering. Items arrive oldest change first, so if the job stops half way, the checkpoint it would have saved is still correct next time. Save the checkpoint only after a run finishes, as above, and a crash simply repeats work.
  • Inclusive updated_since. Items changed in the same second as the checkpoint are fetched again and upserted again, never missed.
  • String comparison. Timestamps are all UTC in the same YYYY-MM-DDTHH:MM:SSZ format, so comparing them as strings orders them correctly.
  • Tombstones. Deletes arrive in the same stream, so your copy never keeps a row the dealer has deleted.

Webhooks or polling?​

Polling with updated_since is the simplest reliable design and is what a warehouse wants. If you need to react within seconds, add webhooks and keep a slower poll running as a safety net.