# Keep data in sync

> Two ways to keep another system up to date with a Couplr account: ask for what changed with updated_since, or have Couplr push events to you through a webhook subscription your app creates itself.

- Canonical URL: https://couplrfsm.com/developers/syncing
- Markdown: https://couplrfsm.com/developers/syncing.md
- API version: v1 (base URL https://couplrfsm.com/api/v1)
- OpenAPI specification: https://couplrfsm.com/developers/openapi.yaml
- All documentation pages: https://couplrfsm.com/llms.txt

> **Flip:** There are two ways to stay current with me. You can ask "what changed since I last looked?", or I can tell you the moment something happens. Most integrations that need to be reliable do both: listen for my events, and sweep for changes now and then in case one was missed.

## Ask for what changed

Every paginated list accepts `updated_since`: customers, jobs, invoices, payments, price book items, estimates, maintenance plans, plan enrollments, equipment, notes, photos, time entries, schedule events and leads. It returns only records changed at or after the time you give.

```bash
curl "https://couplrfsm.com/api/v1/customers?updated_since=2026-11-14T15:00:00Z&per_page=100" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

How to use it:

1. Before each run, note the current time.
2. Request the list with `updated_since` set to the time you noted on your previous run. Read every page. See [Pagination](https://couplrfsm.com/developers/errors-and-limits#pagination).
3. Save each record by its `id`, replacing your copy.
4. When the run succeeds, store the time from step 1 for next time.

Details that matter:

- The time is ISO 8601. An offset such as `+02:00` is honored. A date alone, such as `2026-11-14`, means the start of that day in UTC.
- The filter is inclusive, so a record changed in the same second as your last run comes back again. Saving by `id` makes that harmless.
- A customer counts as changed when one of its addresses is added or edited.
- A payment counts as changed when its invoice changes, so a refund brings the payment back with its new `status` and refund amounts.
- Lists stay in their usual order (newest first), not in order of change.
- A deleted record simply stops appearing in its list. To learn about deletions, ask for them: see [Notice deletions](#notice-deletions) below.

> **Flip:** Start your next run from the time you noted before you asked me, not after. That way nothing that changed while you were reading slips through.

## Notice deletions

Customers, jobs, invoices and estimates can be removed in Couplr. Ask which ones were removed since your last run:

```bash
curl "https://couplrfsm.com/api/v1/deleted?since=2026-11-14T15:00:00Z&per_page=100" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Each entry is a `type` (`customer`, `job`, `invoice` or `estimate`), the record's `id` (always a string) and when it was removed. Remove those records from your copy. Use the same saved time you use for `updated_since`.

- No extra ability is needed. You are shown removals for the kinds of record your key can already read.
- A record that someone restores from the Trash stops being listed here and comes back in its own list with a new `updated_at`.
- A removed record is listed for as long as Couplr keeps it in its Trash. If you sync rarely, compare against a full read from time to time as well.
- This only reports. Nothing can be deleted through the API.

## Have events pushed to you

An app can subscribe itself to events with a key that has the `webhooks:manage` ability. No one has to paste a URL into the Integrations page.

```bash
curl -X POST "https://couplrfsm.com/api/v1/webhook-subscriptions" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: YOUR_UNIQUE_KEY" \
  -d '{"url": "https://example.com/couplr-events", "events": ["invoice.paid", "job.completed"], "description": "Accounting sync"}'
```

Response (`201`):

```json
{
  "data": {
    "id": 14,
    "url": "https://example.com/couplr-events",
    "description": "Accounting sync",
    "events": ["invoice.paid", "job.completed"],
    "active": true,
    "disabled_reason": null,
    "last_delivery_at": null,
    "created_at": "2026-11-14T15:10:00+00:00",
    "secret": "YOUR_SIGNING_SECRET"
  }
}
```

- `secret` is returned only in this response. Store it and use it to check the signature on every delivery. See [Verify the signature](https://couplrfsm.com/developers/outbound-webhooks#verify-the-signature).
- The URL must start with `https://` and use a public hostname.
- `events` takes names from the [event list](https://couplrfsm.com/developers/outbound-webhooks#events), or `"*"` for all of them.
- Send `"active": false` to create a subscription switched off, then turn it on with an update when your receiver is ready.
- List, change and delete subscriptions with the other [webhook subscription endpoints](https://couplrfsm.com/developers/api-reference/webhook-subscriptions).

> **Flip:** A key with `webhooks:manage` can point my events at any address. Give that ability only to the app that needs it.

## Which to use

| Need | Use |
| --- | --- |
| React within seconds, for example to a paid invoice | Webhook subscription |
| A nightly or hourly copy of records | `updated_since` |
| Not miss anything, even if your server was down | Both: events for speed, an `updated_since` sweep to catch up |
| Changes to records that have no event, such as an edited customer | `updated_since` |

Events exist for the moments listed in the [event list](https://couplrfsm.com/developers/outbound-webhooks#events). There is no event for every edit, so `updated_since` is the complete view and events are the fast one. Each event carries a short summary with the record's id; fetch the full record from the API when you need it.
