# Delivering tax-authority data for a firm

`POST /v1/advisor/authority-extracts` stores a batch of tax-authority extracts
about the firm's clients. Each item names the client it is about by `cvr`. It
needs a firm token carrying `advisor-authority-extracts-write`. The full schema
is in the [API Reference](/api/advisor-authority-extracts).

What you deliver here belongs to the firm. Only the firm reads it; the client
companies the items are about do not.

## Request

```bash
curl -X POST https://api.ledgerbee.com/api/v1/advisor/authority-extracts \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "x-api-idempotency-key: 6d0c8a52-3f1e-4b7a-9c2d-5e8f1a4b7c90" \
  -d '{
    "sender": "revibot",
    "batchId": "2026-10-01-run-1",
    "extracts": [
      { "cvr": "12345678", "type": 1, "runDate": "2026-10-01T06:00:00" },
      { "cvr": "99999999", "type": 9, "runDate": "2026-10-01T06:00:00" },
      { "type": 9, "runDate": "2026-10-01T06:00:00" }
    ]
  }'
```

| Field | Required | Notes |
|---|---|---|
| `sender` | No | The delivering system. At most 64 characters. |
| `batchId` | No | Your own id for the batch. At most 128 characters. Part of the idempotency check. |
| `extracts` | Yes | `1` to `5000` JSON objects, one per extract. |

Each item is stored as received. LedgerBee reads `cvr` to name the client the
item is about: non-digits are removed, so `DK12345678`, `12 34 56 78` and
`12345678` name the same company. An item whose `cvr` is missing, is not a
string or a whole non-negative number, or has fewer than 8 or more than 32
digits is stored without a client.

The whole request body is limited to 15 MB; a larger one answers `413`.

## Response

`200` with:

```json
{
  "totalCreated": 3,
  "unmatchedCvrs": ["99999999"],
  "itemsWithoutCvr": 1
}
```

| Field | Meaning |
|---|---|
| `totalCreated` | Items stored. Every item in an accepted batch is stored. |
| `unmatchedCvrs` | Distinct CVRs in the batch, digits only, that are not among the firm's clients. Their items are stored all the same. A CVR here usually means the firm's client list is missing that client; compare it with [the firm's clients](/guides/advisor-firms/clients). |
| `itemsWithoutCvr` | Items whose `cvr` was missing or not a registration number. |

A CVR matches a client when it is the client's company number, or the company
number or VAT number of the client's LedgerBee company.

## Retries and idempotency

The batch is stored all or nothing. When a request fails, nothing from it was
stored; retry the whole request.

Send an `x-api-idempotency-key` header together with `batchId` to make retries
safe:

- A retry with the same key and `batchId` after the batch completed returns the
  original response body, including `unmatchedCvrs`, with
  `x-idempotent-replay: true`.
- The same key with a different `batchId` answers `422` with code
  `IDEMPOTENCY_KEY_REUSED`.
- A retry while the original is still running answers `409` with code
  `IDEMPOTENT_REQUEST_IN_PROGRESS`.

The key is optional. See [Idempotency](/guides/idempotency) for the header's
rules.

## Firm and company endpoints

A company that connects your app for itself delivers through the company
endpoint. The request body is the same.

| | Firm | Company |
|---|---|---|
| Endpoint | `POST /v1/advisor/authority-extracts` | `POST /v1/authority-extracts` |
| Credential | An advisor firm's token | The company's API key or token |
| Scope | `advisor-authority-extracts-write` | `authority-extracts-write` |
| Response | `totalCreated`, `unmatchedCvrs`, `itemsWithoutCvr` | `totalCreated` |
| Who reads the items | The firm | The company |

## Errors

| Condition | What you observe | Fix |
|---|---|---|
| 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, or deliver through `POST /v1/authority-extracts` |
| The token lacks `advisor-authority-extracts-write` | `403`, `code: "INSUFFICIENT_PERMISSIONS"` | Reconnect with the scope |
| `extracts` empty, over 5000 items, or holding a non-object | `400`, `code: "SHARED_VALIDATION_FAILED"` | Split the batch, or fix the item |
| `sender` over 64 or `batchId` over 128 characters | `400`, `code: "SHARED_VALIDATION_FAILED"` | Shorten the value |
| Body over 15 MB | `413` | Split the batch |
| Idempotency key reused for a different `batchId` | `422`, `code: "IDEMPOTENCY_KEY_REUSED"` | Use a fresh key per batch |
| Duplicate request while the original is running | `409`, `code: "IDEMPOTENT_REQUEST_IN_PROGRESS"` | Wait, then retry with the same key |
