Keeping your data in sync
Many integrations keep their own copy of some Provet data, such as clients, patients, appointments or invoices. This page describes how to keep that copy up to date with as few requests as possible. Integrations that follow it get faster responses, stay well within the rate limits, and put less load on the clinics they serve.
In short:
Read everything once, then on each run ask only for what changed since your last successful run.
Follow the
nextlink from page to page, with pages of 50 to 200 records.Use webhooks to hear about changes as they happen, and keep a “changed since” run as a backstop.
The examples on this page leave out the start of the address. Real requests include your
organization’s ID: https://provetcloud.com/<provet_id>/api/0.1/client/.
Sync only what changed
The list endpoints for clients, patients, appointments, reminders, consultations, invoices,
invoice rows and invoice payments accept a modified__gte filter: “records changed on
or after this time”.
First run. Note the time, then read the whole list once. On a large list, read it in
idorder rather than by page number (see Read a large list by id).Every run. Note the time when the run starts. Ask only for records changed since the start of your last successful run:
GET /api/0.1/client/?modified__gte=2026-10-21T02:55:00ZWhen the run has finished, save its start time for the next run. If a run fails, don’t save it, so the next run covers the same period again.
A few things keep this from missing records:
Overlap. Start each window a few minutes earlier than the last run’s start time. Records saved while your previous run was reading would otherwise be missed, and list reads can lag behind the latest writes by no fixed amount. A few minutes is usually enough, but no overlap is guaranteed to catch everything.
Duplicates are fine. Because of the overlap, some records come back twice. Save each record under its
id, updating it if you already have it, so that seeing one twice does no harm. Reminders and unallocated payments have noidfield; use the number at the end of theirurlinstead, here and below.Keep the newer copy. A record can occasionally come back older than a copy you already have, because list reads can lag. Compare
modifiedand keep the copy with the later time. Custom field values don’t includemodified; replace your copy with the one you read most recently.Act once per change. If your system does something for each record, such as sending a message, tie it to the record’s
idandmodifiedtime, not to the run that returned it. Otherwise the overlap makes it happen twice.Clocks. The start time comes from your clock and
modifiedfrom ours, so keep your server’s clock synchronized.A full read now and then. If your copy must be exact, read the full list again from time to time, for example once a month, outside clinic hours. It is the only way to catch the changes listed under What a “changed since” request doesn’t show.
Warning
Don’t send the same fixed date on every run, such as modified__gte=1901-01-01, and
don’t re-read a fixed window, such as “the last 30 days” or “the next two weeks”, on
every run. Both download the same records again each time. Use the time of your last
successful run instead.
Send times with a time zone
Include a time zone in every date filter, either Z for UTC or an offset such as
+03:00. A time without one is read in the time zone set for the organization in
Provet, not in UTC. In a URL, write the + of an offset as %2B. An
unencoded + is read as a space, so the time can be read wrongly and the request
returns the wrong records, without an error:
GET /api/0.1/appointment/?modified__gte=2026-10-21T05:55:00%2B03:00
Check the spelling of filters
A field or method the endpoint doesn’t support for filtering is ignored rather than
refused, so a misspelled filter, such as modifed__gte, returns the list as if it weren’t
there: every record you have access to. A value the endpoint can’t read, such as a
malformed date or text on a number field, is refused with an error, and so is a method used
on a related object instead of one of its fields, such as phonenumbers__icontains. The
phone number list (/api/0.1/phonenumber/) has no modified filter at all. If a
“changed since” request returns as many records as your first full run, check the filter. See
Query filtering for the filters each endpoint supports.
What a “changed since” request doesn’t show
A record’s modified time changes when that record is saved. Some data is stored
separately, and changing it on its own doesn’t save the record it belongs to. A “changed
since” request may not return:
a client whose phone numbers were added or edited. Subscribe to the “Phone number” webhook to hear about these.
a client or patient whose custom field values changed. Read changed values from their own list instead:
GET /api/0.1/custom_field_values/?modified__gte=2026-10-21T02:55:00Z. Each value names itsfieldand theobject_idof the record it belongs to. The field’scontent_typein/api/0.1/custom_fields/says whether that is a client, a patient or a consultation.a patient whose weight was added or edited.
a few changes made in bulk by Provet itself, such as an invoice’s
due_sumbeing recalculated after a card terminal payment, or patients, notes, consultations, appointments and invoices moved to the other client when duplicate clients are merged.
A daily “changed since” run on the client or patient list doesn’t pick these up either.
Records that are removed show up in different ways:
Clients and patients are archived, not deleted:
archivedbecomestrue, and the record is returned as changed.Appointments that are cancelled stay in the list with
activeset to0, and the “Appointment delete” webhook is sent.Reminders, phone numbers, invoice rows, consultations and draft invoices are deleted, and so are appointment bookings that were never confirmed. Voided draft invoices also disappear from the API. They no longer appear in the list, so a “changed since” request can’t show them. The “Reminder delete”, “Invoice row delete” and “Consultation delete” webhooks report those deletions. Deleting a draft invoice sends the regular “Invoice” webhook, after which the invoice can no longer be read. Phone numbers and never-confirmed bookings have no delete webhook.
A client whose data is erased on request disappears from the API, so it can’t appear in a “changed since” request. The “Client delete” webhook reports it.
See List of Webhook Triggers for the full list of webhooks.
Paging
Most list endpoints return 50 records per page by default. Ask for up to 1000 with
page_size (see Paginated results).
Follow next
Read page after page by following the next link in each response until it is null.
Don’t work out page numbers yourself from count or num_pages: on some lists these
totals can be a few minutes old. If a request is refused with HTTP 429, wait the number of
seconds in its Retry-After header and request the same link again; there’s no need
to start the run over.
Deep pages get slower
Pages are numbered, not linked by a cursor, and deep pages can be much slower to return
than the first ones. When a page starts more than 10,000 records into a list, the response
carries an X-Pagination-Warning header. Read a smaller set, with modified__gte or a
date filter, or read the list by id as below, rather than paging through the whole
list.
Read a large list by id
Lists are sorted by id. To read a large list, such as on the first run, ask for the
records after the last id you have, and always read the first page:
GET /api/0.1/client/?id__gt=0&page_size=200
GET /api/0.1/client/?id__gt=<largest id on the previous page>&page_size=200
Stop when a page comes back with no results. Every request is as fast as the first, and records that change or are deleted while you read don’t make you skip others, as page numbers can: when a record on a page you’ve read drops out of the list, the rest move up. Records added while you read may be missed, so note the start time before the first page; the next “changed since” run then picks them up.
The client, patient, consultation, invoice, invoice row and invoice payment lists accept
id__gt. The appointment and reminder lists don’t; read them in parts by created
time instead, such as one month at a time, since a record’s created time never
changes. A list that doesn’t support id__gt ignores it and returns the same first
page every time, so stop if a page holds no id larger than the last one you had.
Page size and the rate limit
A list request counts against the rate limit in steps of the default page size: a page of up to 50 counts once, a page of 51 to 100 counts twice, and a page of 1000 counts as 20 requests, even when fewer records come back. Where an endpoint caps the page size, the capped size is what counts. Large pages don’t let you read more records per minute, but each one takes much longer to build and to receive. Pages smaller than 50 use up the limit faster for the same number of records.
Pages of 50 to 200 records are a reasonable starting point for most integrations.
Use webhooks, with a backstop
Webhooks tell your system that a record changed, usually within moments, so you don’t have to poll every few minutes. Delivery time isn’t guaranteed, though. A webhook delivery counts as received only when your endpoint answers with HTTP 200; other answers, including 201 and 204, are retried. After 10 attempts in total the delivery is dropped (after 3 for some fiscal registration webhooks). Keep a “changed since” run, for example once a day, to pick up records whose change a webhook missed. It doesn’t replace webhooks for the changes a “changed since” request doesn’t show, such as phone numbers and deleted records. Archived clients and patients, and cancelled appointments, do show up in it.
Do and don’t
Don’t |
Do |
|---|---|
Send |
Send |
Re-read a fixed window, such as the last 30 days, on every run |
Ask for what changed since your last successful run |
Download the whole list, unfiltered, on every run |
Read it once, then use |
Work out page numbers from |
Follow |
Ask for |
Start reading; follow |
Use |
Use pages of 50 to 200 records |
Loop over every client with |
Read |
Send times without a time zone |
Send |
Overwrite your copy with whatever arrives last |
Keep the copy with the later |
Rely on webhooks alone |
Use webhooks and a daily |