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:
- The token's frozen abilities. The ability list is a snapshot taken at issue time. If the token was not granted
invoices:write, noPOST /api/v1/invoiceswill ever succeed with it. - 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.