# Company statements

A statement lists a company's orders and invoices for a period: what was ordered, what was paid and what is still due. Orbit builds it afresh for each request, with the store's invoice branding, then hands you a PDF to send to the company or a CSV for a spreadsheet, so it shows the records as they stand when you ask. The public API keeps nothing; the [download link](#single-use-download-links) the dashboard and the account area use serves the copy made when the link was created.

This page covers the public API route an integration calls, the period rules it enforces, and the single-use link the dashboard and the account area use when a person asks for a statement.

## Requesting a statement

`GET /v1/companies/{id}/statement` (`company:read`)

| Parameter | Meaning |
|---|---|
| `from` | The first day the statement covers, `YYYY-MM-DD`. Defaults to the first day of the current month. |
| `to` | The last day the statement covers, `YYYY-MM-DD`, included. Defaults to today. |
| `format` | `pdf` or `csv`. Defaults to `pdf`. |

A parameter sent blank (`?from=&to=`) counts as left out, so it takes its default.

```http
GET /v1/companies/6f2a1c3e-9d4b-4a7f-8e2c-1b3f5a7c9d0e/statement?from=2026-09-01&to=2026-09-30&format=pdf HTTP/1.1
Host: api.myorbitcommerce.net
Authorization: Bearer oc_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

The answer is the file itself, streamed as an attachment, not a JSON envelope: a PDF (`application/pdf`) or a CSV (`text/csv; charset=utf-8`), with `Content-Disposition: attachment` and a filename built from the company name and the period, such as `statement-beacon-hardware-ltd-2026-09-01-to-2026-09-30.pdf`. The response carries `Cache-Control: no-store`.

The request is refused with a JSON answer instead of a file. These refusals carry an `errorCode`, a malformed date and an unknown format included:

| Status and `errorCode` | When |
|---|---|
| `400` `B2B_STATEMENT_PERIOD_INVALID` | A date is not a real calendar day written `YYYY-MM-DD`, or `from` is after `to`. |
| `400` `B2B_STATEMENT_PERIOD_TOO_LONG` | The period is longer than 13 months. |
| `400` `B2B_STATEMENT_FORMAT_INVALID` | `format` is neither `pdf` nor `csv`. |
| `404` | The company is not one of this store's companies. |
| `413` `B2B_STATEMENT_TOO_LARGE` | The period holds more than 2,000 orders. |

A company id that is not a UUID, a query parameter other than `from`, `to` and `format`, or one of them sent twice, answers a plain validation `400` without an `errorCode`.

The 413 cap is the one to plan for: a statement is a document, not a data dump, so a period with more than 2,000 orders is refused with the message "This period has more than 2,000 orders. Choose a shorter range." Split a long history into shorter periods, or sync the underlying records instead (see [Statements or syncing](#statements-or-syncing)).

## The period a statement covers

Both bounds are calendar days in UTC, written `YYYY-MM-DD`. `from` must not be after `to`, and the period may span at most 13 months; the day after `to` is the exclusive end of the range, so an order placed late on `to` is included. With no parameters, the statement covers the current month to date.

## What a statement lists

There is one line per order placed under the company in the period, leaving out drafts, orders awaiting approval, cancelled orders, fully refunded orders (an order whose `status` or `paymentStatus` is `refunded`) and card orders not yet paid (`paymentStatus` `pending`, `failed`, `expired` or `canceled`, with nothing paid and nothing due). Orders awaiting a manual payment, owed on account or on terms, part-paid or with a payment in flight are listed, and a part-refunded order is listed at its original total. Each line carries the order number, the order date, the buyer, the PO number, the branch, the cost centres charged on the order, the invoice number and its due date, and the money: goods (the total less VAT), VAT, total, paid and due. For an order billed from a B2B invoice, paid and due come from the latest live invoice; otherwise they come from the order's own record.

The lines are grouped into one section per currency, and each section closes with a total row for the period. An order on account shows its invoice and due date, so the company can see what it still owes.

## The CSV statement

The CSV has one header row, one row per order and a total row per currency section. Its columns are:

| Column | Holds |
|---|---|
| `Order Number` | The order's number. |
| `Order Date` | `YYYY-MM-DD`. |
| `Buyer` | The customer's name, or their email when no name is recorded. |
| `PO Number` | The buyer's purchase order number, when the order carries one. |
| `Branch` | The company location the order is for, when it names one. |
| `Cost Centres` | The order's cost centre codes, joined with `, `. |
| `Invoice Number` | The B2B invoice that bills the order, when it has one. |
| `Due Date` | The invoice's due date, `YYYY-MM-DD`, when it has one. |
| `Currency` | The order's currency code. |
| `Goods` | The total less VAT, as a decimal string. |
| `VAT` | The VAT amount, as a decimal string. |
| `Total` | The order total, as a decimal string. |
| `Paid` | What has been paid. |
| `Due` | What is still owed. |

The file starts with a byte order mark so a spreadsheet opens it in the right encoding, and each currency section ends with a `Total` row whose first cell reads `Total`.

## The PDF statement

The PDF is a landscape A4 document with the store's masthead (logo or name, and its VAT number when set), the company block (its name, your account reference and its tax id), the statement date and the period. Each currency gets its own table under a heading, and a bold total row closes it. The footer notes the date the amounts were recorded and reads: "Draft, cancelled and fully refunded orders, orders awaiting approval and card orders not yet paid are not included. Part-refunded orders show their original total."

## Single-use download links

The dashboard's company page and the storefront account area do not stream the file to a person; they mint a single-use link instead. The link object carries:

| Field | Meaning |
|---|---|
| `url` | The link to follow to download the file. |
| `filename` | The name the browser saves it under. |
| `format` | `pdf` or `csv`. |
| `expiresAt` | When the link stops working, ten minutes after it was created. |

The link works exactly once. Following it redirects the browser to the file; following it again, or after `expiresAt`, answers `404` with `errorCode: "B2B_STATEMENT_LINK_EXPIRED"` and the message "This statement link has expired or has already been used. Create the statement again." The public API streams the file directly, so an integration does not need this flow.

## Statements or syncing

A statement is a document for people. To keep a ledger in your own system, sync the records and build the view yourself: `GET /v1/orders?companyId=&includeArchived=true` lists the company's orders with their PO numbers, branches, cost centres and amounts due (without `includeArchived=true` it leaves archived orders out), and `GET /v1/b2b-invoices` (filter with `companyId`) lists its invoices with their status and due dates. See [Orders, approvals and pay on account](/guides/b2b-orders) for the order fields and the [B2B invoices](/scope-catalog#b2b-invoices-b2b-invoicelist-b2b-invoiceread) scopes for the invoice routes.

## Next steps

- The order fields a statement summarises, and how they are created: [Orders, approvals and pay on account](/guides/b2b-orders).
- Keeping a ledger of your own instead of a document: [B2B / ERP integration](/guides/b2b-erp-integration).
