Skip to content

Permissions

Every tenant-scoped endpoint names the permission it needs, as resource:action. A session carries what its role allows; a key or a connection carries what it was granted, which is never more.

Scope What it allows
tenant:read See the business and who is in it.
customers:read Read customers.
customers:write Add and change customers.
products:read Read what the business sells and its prices.
products:write Change products and agreed customer rates.
orders:read Read orders.
orders:write Take orders and change them.
rounds:read Read rounds, vehicles, and the bases they start from.
rounds:write Plan rounds and change them.
drivers:read See which vehicle each driver usually takes.
drivers:write Change which vehicle a driver usually takes.
rota:read See shifts, time off and cover. A driver sees only their own.
rota:write Run the rota: shift patterns, time off, cover and approvals.
timeoff:write Ask for time off. A driver asks only for their own.
deliveries:write Say what happened at a door, with proof, and whether the vehicle has left.
members:read See who works there.
members:write Change what people can do, and remove them.
invitations:read See open invitations.
invitations:write Invite people and cancel invitations.
keys:read, keys:write Manage API keys. Never granted to a key or a connection.
connections:read, connections:write Manage connected apps. Same.
shops:read, shops:write Connect a shop and pull its catalogue. Never granted to a key or a connection.
billing:read Read the plan and the invoices Bottle has raised, and what is in the business’s own Stripe account.
billing:write Change the plan. Never granted to a key or a connection.
privacy:read See the retention rules, what is due and what has been erased.
privacy:write Change the retention rules and erase somebody. Never held by a key.

Every price and total is an integer number of the smallest unit, excluding VAT. There are no decimal amounts anywhere in this API: a price of 5400 is £54.00.

Sterling is the only currency Bottle handles at the moment. Nothing in the API is shaped around that, and it will widen; today a shop selling in anything else is refused rather than misread.

billing:read is granted like any other scope, and is what an accounting integration or an assistant answering “what do we pay for Bottle” needs. It covers the plan, the trial, the renewal date and every invoice, each with a link to the invoice itself.

Nothing can change what a business pays except a person signed in, and an owner at that. billing:write cannot be granted to a key or a connection whoever creates it, and the routes behind it turn away anything that is not a session.

Owners have every scope. Admins have every one except billing:write: running the office does not include deciding what the business pays.

Drivers have tenant:read, their own connections, rounds:read, deliveries:write and products:read. That is their work: read the round they are driving, say what happened at each door, say when the vehicle left and when it is back, and sell from the van, which needs to know what the business sells and what it costs. The customer book, the people list and the money are not theirs; a driver making a sale gets a name search from their own round (GET /rounds/{id}/customers) that answers with a few matches and nothing more.

Both round scopes are narrowed by role as well as by scope. A driver reading rounds gets the ones they are driving; a driver writing an outcome may only write to a stop on one of those. Somebody else’s round answers 404, not 403: whose round it is is not a driver’s business either.

Role still matters after the scope check. An admin holds members:write but cannot change an owner, and the last owner cannot be demoted.

{ "error": "missing_scope", "message": "This needs the members:write permission, ..." }

missing_scope means this caller never had that power. forbidden means it has it in general, but not over this particular thing.