Authentication

Every request to the API is authenticated with a billey personal access token, sent as an HTTP bearer token.

The Authorization header

Send the token in the Authorization header, prefixed with Bearer :

Authorization: Bearer 7|billey_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

The token has two parts joined by a pipe — the token id, then the secret (for example 7|billey_…). Send the whole string exactly as billey gave it to you.

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

A missing or invalid token is rejected with 401 Unauthorized:

{ "message": "Unauthenticated." }

There is no cookie, session, or OAuth flow — the bearer token is the only credential the API accepts. Always send it over HTTPS, and treat it like a password.

One token, one book

A token is bound to exactly one administration (book). There is no account-wide key and no book id in any URL: the token decides which book every request reads and writes. A firm managing several client books holds one token per book. See Tokens for how tokens are issued and scoped.

Abilities are checked twice

A token carries a fixed set of abilities (for example invoices:read, clients:write) chosen when it is created. Every request is authorised in two independent layers:

  1. The token's frozen abilities. The ability list is a snapshot taken at issue time. If the token was not granted invoices:write, no POST /api/v1/invoices will ever succeed with it.
  2. The account's live permission. On every request billey additionally re-checks that the account behind the token still holds the matching permission in the app right now. If a teammate's access to the book is downgraded or revoked, their tokens stop working immediately — even though the token's own ability list is unchanged.

A request that clears neither layer gets 403 Forbidden, and an ability failure names what was missing:

{
  "message": "This token does not have the 'invoices:write' ability.",
  "required_ability": "invoices:write"
}

The full list of abilities, and which resources each one unlocks, is on the Tokens page.

Calls from a browser

The API accepts a request from any origin. It sends the CORS headers that a browser needs. It also exposes the rate limit headers (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After) to your script.

This is safe because the API never reads a cookie. A page that does not have your token cannot call the API as you.

Do not put a token in a page that other people can open. Each person who opens the page can read the token. Keep the token on your server, and let the page call your server.

Klopt er iets niet of ontbreekt er iets? support@billey.nl