# The firm's clients

`GET /v1/advisor/clients` lists every client of the firm whose connection you
hold, whether or not the client keeps its books in LedgerBee. It needs a firm
token carrying `advisor-clients-read`. The full schema is in the
[API Reference](/api/advisor-clients).

```bash
curl "https://api.ledgerbee.com/api/v1/advisor/clients?page=1&limit=100" \
  -H "Authorization: Bearer ACCESS_TOKEN"
```

## Query parameters

| Parameter | Default | Notes |
|---|---|---|
| `page` | `1` | 1-indexed. |
| `limit` | `50` | Clients per page, `1` to `100`. |
| `updatedAtFrom` | — | Only clients added to the firm, or changed, at or after this instant. A calendar date (`2026-10-01`, read as midnight UTC) or a date-time with a time zone (`2026-10-01T09:30:00Z`, `2026-10-01T11:30:00+02:00`). A date-time without a zone is refused. |

## Response

```json
{
  "data": [
    {
      "id": "0199c4d8-7b2e-7f10-9a3c-5e6f7a8b9c0d",
      "companyNumber": "12345678",
      "productionUnitNumber": null,
      "name": "Alpha ApS",
      "externalClientNumber": "K-1042",
      "onLedgerBee": true,
      "updatedAt": "2026-10-01T09:30:00.000Z"
    },
    {
      "id": "0199c4d9-1a40-7c22-8b51-2f3e4d5a6b7c",
      "companyNumber": "87654321",
      "productionUnitNumber": "1012345678",
      "name": "Beta Holding A/S",
      "externalClientNumber": null,
      "onLedgerBee": false,
      "updatedAt": "2026-09-14T07:02:11.000Z"
    }
  ],
  "total": 2,
  "page": 1,
  "limit": 100,
  "totalPages": 1
}
```

Clients are sorted by name, then by `companyNumber`. The list is always
paginated; request pages until `page` reaches `totalPages`.

| Field | Meaning |
|---|---|
| `id` | The id of the firm's own record of the client when the firm keeps one, otherwise the id of the client's LedgerBee company. A client first listed under its company id takes its record's id once the firm records it. |
| `companyNumber` | The client's company number (for a Danish company, its CVR), or `null`. Compare it with spaces and dashes removed. Name the client by it when you [deliver data](/guides/advisor-firms/authority-extracts) about it. |
| `productionUnitNumber` | The P-number of the office the client is, 10 digits, when the client is one office of a company; `null` for the whole company. Two offices of one company are two clients with the same `companyNumber`. |
| `externalClientNumber` | The firm's own client number, when it keeps one. |
| `onLedgerBee` | `true` while the client keeps its books in LedgerBee; `false` for a client the firm tracks that does not, or that has stopped. |
| `updatedAt` | The latest of: when the client was added to the firm, when the firm's record of it last changed, and when the client's own company last changed. |

Key your copy on `companyNumber` and `productionUnitNumber` together, not on
`id`: the same client can change `id`, and a key on `companyNumber` alone merges
the offices of one company into one client. A client with no company number
(`null`) has only its `id` to key on. That `id` is its LedgerBee company's, and
it stays put until the client has a company number, because the firm keeps its
own record of a client only under one. When a company number appears, move that
client to the `companyNumber` and `productionUnitNumber` key.

## Keeping a copy in sync

1. Note the current time, then page through the whole list without
   `updatedAtFrom`.
2. On each later run, note the time again and send the time you noted on the
   previous run as `updatedAtFrom`. Upsert each client by `companyNumber` and
   `productionUnitNumber`, or by `id` when it has no company number.
3. Run a full listing now and then. Removals are not reported, so a client
   missing from a full listing has left the firm.

`updatedAt` moves on any change to the client's company, so an incremental
listing can return a client whose listed fields are unchanged. Each page is
computed when you request it, so a client added or removed while you page can
shift a later page by one position and skip a client. An incremental run does
not bring back a skipped client that has not changed since, so the full listing
in step 3 is what catches it.

## Errors

| Condition | What you observe | Fix |
|---|---|---|
| No token, or an expired or invalid one | `401`, `code: "UNAUTHORIZED"` | Refresh the token, or reconnect |
| The connection ended | `401`, `details.reason` `grant_revoked` or `consenting_user_inactive` | Reconnect with a firm admin; see [how a connection ends](/guides/advisor-firms/overview) |
| An API key or a company's token | `403`, `code: "PUBLIC_API_HOLDER_MISMATCH"`, `details: { "required": "advisor", "actual": "company" }` | Use a firm token |
| The token lacks `advisor-clients-read` | `403`, `code: "INSUFFICIENT_PERMISSIONS"`, `WWW-Authenticate` with `error="insufficient_scope"` | Reconnect with the scope |
| `updatedAtFrom` without a time zone, or not a date | `400`, `code: "SHARED_VALIDATION_FAILED"` | Send `YYYY-MM-DD` or a date-time with `Z` or an offset |
| `page` below `1`, or `limit` outside `1`–`100` | `400`, `code: "SHARED_VALIDATION_FAILED"` | Correct the parameter |
