Introduction

Billey is a Dutch accounting product for freelancers, small companies and the firms that keep their books. This API gives your integration read and write access to the accounting data of one administration over a small, predictable REST surface at https://billey.nl/api/v1.

These pages are the guided tour. The exhaustive, field-by-field contract is the machine-readable OpenAPI spec.

One token = one book

Every request is authenticated with a single personal access token, and a token is bound to exactly one administration (book). There is no account-wide key, and no {book} segment in any URL — the token itself decides which book you are working in. An accounting firm that manages several client books issues one token per book.

This is structural, not a convention. A token can never read or write outside the book it was issued for, so an integration built for one client can never accidentally touch another. To work with three books, you hold three tokens.

Money is decimal strings

Every monetary amount is a JSON string with exactly two decimals — "1250.00" — never the number 1250 and never an integer count of cents. null means "no amount" and is distinct from "0.00". Decimal-cast fields (quantities, tax rates, hours) are strings too. This keeps money exact across every language's JSON parser. See Money & decimals.

Quickstart: fetch invoices in two minutes

1. Create a token. In Billey, open your book's Settings → API tokens and create one with the invoices:read ability. Creating a token requires two-factor authentication on your account. The token is shown once — copy it. It looks like 7|billey_xxxxxxxx.

2. Send it as a bearer token.

curl https://billey.nl/api/v1/invoices \
  -H "Authorization: Bearer 7|billey_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

3. Read the response. A list is a cursor-paginated envelope — the rows are in data:

{
  "data": [
    {
      "id": 42,
      "invoice_number": "2026-0042",
      "status": "sent",
      "currency": "EUR",
      "invoice_date": "2026-07-21",
      "due_date": "2026-08-04",
      "subtotal": "1033.06",
      "btw_total": "216.94",
      "total": "1250.00",
      "client": { "id": 7, "name": "Example Client B.V." },
      "line_items": [
        {
          "id": 1,
          "description": "Consulting",
          "quantity": "10.00",
          "unit_price": "103.31",
          "btw_percentage": "21.00",
          "line_total": "1033.06"
        }
      ]
    }
  ],
  "links": { "first": null, "last": null, "prev": null, "next": null },
  "meta": { "path": "https://billey.nl/api/v1/invoices", "per_page": 25, "next_cursor": null, "prev_cursor": null }
}

That is the whole loop: authenticate, call, read data, follow links.next to page. Fetch a single invoice with GET /api/v1/invoices/42.

Conventions at a glance

  • JSON everywhere. A single resource is { "data": { … } }; a list adds links and meta.
  • Timestamps are UTC ISO-8601 with a Z suffix (2026-07-21T10:15:30Z); date-only fields are plain Y-m-d.
  • Lists use cursor pagination, with incremental sync (updated_after) and sparse fieldsets (fields). See Pagination.
  • Creates are idempotent — every POST that creates a resource takes an Idempotency-Key header. See Idempotency.
  • Rate limited to 60 requests/minute per token. See Rate limits.

What is not in the API

Stated up front so you never design around something that isn't there:

  • No POS / point-of-sale endpoints, and a POS sale emits no webhook.
  • No user-management endpoints — you cannot create or manage users or team members through the API.
  • No *.updated or *.deleted webhook events. Only six events fire; see Events.

Where to go next

Something inaccurate or missing? support@billey.nl