# Keep an ERP in sync

> Copy a merchant's invoices, payments and credit notes into your system without gaps or duplicates, and write to Kosoor safely with your own reference ids.

This page is for a system that mirrors a merchant's documents, such as an ERP or an accounting package, and may also write to Kosoor.

## Read what is new

Every list returns the newest records first, whichever channel created them: the POS, the kiosk, the online store, the dashboard or the API. A record created while you page appears at the top of the list, so it never shifts the pages you are reading. See [Pagination](https://docs.kosoor.com.sa/pagination.md).

So to fetch what is new since your last run:

1. Keep the `id` of the newest record you stored for each list.
2. Ask for the first page: `GET /v1/sales-invoices?limit=100`.
3. Store each record until you meet the `id` you kept. Stop there.
4. If you did not meet it and `hasMore` is `true`, ask for the next page with `startingAfter` set to the last `id` of this page, and go on from step 3.
5. Keep the `id` of the first record of the first page for the next run.

```bash
curl "https://api.kosoor.com.sa/v1/sales-invoices?limit=100&startingAfter=c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f" \
  -H "Authorization: Bearer ksr_live_..."
```

```json
{
  "data": [
    {
      "id": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e",
      "object": "salesInvoice",
      "number": "INV-1042",
      "issueDate": "2026-11-07",
      "supplyDate": null,
      "dueDate": null,
      "branchId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d",
      "customerId": "5a4db0c5-0000-4000-8000-000000000001",
      "currency": "SAR",
      "subtotal": 100,
      "discountTotal": 0,
      "vatTotal": 15,
      "total": 115,
      "amountPaid": 115,
      "amountDue": 0,
      "notes": null,
      "costCentreId": null,
      "referenceId": "order-20417",
      "metadata": {},
      "createdVia": "api",
      "zatca": {
        "kind": "simplified",
        "status": "reported",
        "uuid": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
        "counter": 17,
        "hash": "dZBGdHmJoIQ0SOi+mt6KcGC6mWlVmpjbPEJ4y/3tps4=",
        "qr": "ARBTYW5kYm94IE1lcmNoYW50Ag8zOTk5OTk5OTk5MDAwMDMDFDIwMjYtMTEtMDdUMDk6MzA6MTJaBAYxMTUuMDAFBTE1LjAw",
        "submittedAt": "2026-11-07T09:30:40.118Z",
        "clearedAt": null,
        "reportedAt": "2026-11-07T09:30:41.204Z",
        "errors": []
      }
    }
  ],
  "hasMore": false
}
```

A list leaves out each invoice's `lines`. Add `expand=lines` to get them, and `expand=customer` to embed the customer (which also needs `customers:read`); repeat the parameter for both: `expand=lines&expand=customer`.

The same steps work for `GET /v1/payments` and `GET /v1/credit-notes`. The credit note list includes refunds made at the POS and on the dashboard.

To load history once, filter by date instead: `issuedAfter` and `issuedBefore` on sales invoices and credit notes (instants in ISO 8601, such as `2026-01-01T00:00:00Z`), and `dateFrom` and `dateTo` on payments (days, such as `2026-01-01`).

## Tell the channels apart

`createdVia` says which channel created a record: `api`, `dashboard`, `pos` (the point of sale in a branch), `kiosk`, `storefront` (the merchant's online store), or `unknown` for a record saved before Kosoor recorded the channel. `api` covers every key the merchant issued, not only yours. To recognise your own records, give them your own `referenceId`, or a key in `metadata`.

## Records that change after you stored them

A list shows each record once, when it is created. Some fields change later:

| Field | Changes when |
|---|---|
| `zatca.status` and the other `zatca` fields | ZATCA answers: `pending` turns `cleared`, `reported` or `rejected` |
| `amountPaid`, `amountDue` of an invoice | a payment is allocated to it, or a credit note takes an amount off it |
| any field of a customer | the merchant or a key edits the customer |

Read a record again with `GET` on its `id` when you need it fresh. `zatcaStatus=pending` on `GET /v1/sales-invoices` and `GET /v1/credit-notes` lists the documents still waiting for ZATCA. ZATCA results that arrive later are pushed to you by webhook; see [Webhooks](https://docs.kosoor.com.sa/webhooks.md).

## Write without duplicates

Two things protect a write, and they work together:

- **`Idempotency-Key`**: sending the same request again with the same key returns the first reply and creates nothing. Kosoor keeps a key for 30 days. See [Retries and idempotency](https://docs.kosoor.com.sa/idempotency.md).
- **`referenceId`**: your own id for the record, such as your ERP's document number. No two records of the same kind may share one:

| Resource | `referenceId` is unique across |
|---|---|
| customers | the merchant's customers; it is set once and never changes |
| sales invoices | the merchant's sales invoices and credit notes together |
| payments | the merchant's payments |
| credit notes | the merchant's sales invoices and credit notes together |

So if your system crashes after sending an invoice and loses its `Idempotency-Key`, sending the invoice again under a new key cannot create a second one: it answers `409 reference_id_taken`. `existingId` names the record that holds the reference when it is the same kind of record and your key may read it, and `GET /v1/sales-invoices?referenceId=...` finds it in any case:

```json
{
  "error": {
    "type": "invalid_request_error",
    "code": "reference_id_taken",
    "message": "Another resource already has this referenceId.",
    "param": "referenceId",
    "docUrl": "https://docs.kosoor.com.sa/errors#reference_id_taken",
    "existingId": "c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f"
  }
}
```

Before you write, you can also look the reference up: every list takes a `referenceId` filter.

## Map the catalogue once

Your ERP's branches, items and payment methods map to Kosoor ids from `GET /v1/branches`, `GET /v1/items` and `GET /v1/payment-methods`. The sandbox keeps the live ids of the catalogue it copied, so a mapping you build in the sandbox holds on live data. Customers are the exception: the sandbox has its own, so map them again on live data, by `referenceId`, phone or VAT number.

Source: https://docs.kosoor.com.sa/erp-sync
