For developers
Everything the Bottle app does, it does through this API. There is no private back door: the web app is a client like any other.
Two ways in
Section titled “Two ways in”An API key belongs to a business. It carries the permissions it was granted and keeps working when whoever created it leaves. Use one for a partner’s system, an accounting sync, or anything running unattended.
A connection belongs to a person, through OAuth. It acts for them, within what they allowed, and dies with their membership. Use one for an assistant working on somebody’s behalf. See Connecting over MCP.
Both resolve to the same permissions, so an endpoint asks one question regardless of who is calling.
Our own driver apps sign in a third way: with the person’s email and password,
like the web. Signing in at /auth/sign-in/email answers with the session in a
set-auth-token header, and the app sends it back as Authorization: Bearer.
It is the same session the web keeps in a cookie, so the app is that person,
driver rules and all.
The basics
Section titled “The basics”The API speaks JSON over HTTPS. Every tenant-scoped request needs a business:
an API key names its own, and a session sends X-Tenant-Id.
curl https://api.bottle.example/customers \ -H "Authorization: Bearer btl_7f3a9c21_..."Errors come back with a stable error code and a message written for a person,
which the app shows as-is:
{ "error": "missing_scope", "message": "This needs the customers:write permission, ..." }Depend on the code, not the message. Messages get rewritten, and translated.
The reference
Section titled “The reference”The API reference is generated from the OpenAPI document the
server itself publishes at /openapi.json, so it cannot describe an endpoint
that does not exist.
Sending a write twice
Section titled “Sending a write twice”A request can be lost on the way back: the write happened, but the answer never
arrived. Send an Idempotency-Key header with any write, and sending the same
request again is safe.
curl -X POST https://api.bottle.example/customers \ -H "Authorization: Bearer btl_7f3a9c21_..." \ -H "Idempotency-Key: 0192c7d4-5e1f-7a3b-9c2d-8e4f6a1b3c5d" \ -H "Content-Type: application/json" \ -d '{"tradingName": "The Anchor"}'- The first request with a key does the work. The same request with the same
key gets the first answer back, with
Idempotent-Replayed: true, and changes nothing. - A key is 8 to 100 letters, digits, dots, colons, dashes or underscores. A UUID made fresh for each write is ideal.
- Keys belong to whoever sent them, in one business, and are kept for a week.
- The same key on a different request is refused with
idempotency_key_reused. One that arrives while the first is still being worked on getsidempotency_in_progress: wait a moment and send it again. - An answer that went wrong on our side (a 5xx) is not kept, so sending it again really does try again.
When it happened
Section titled “When it happened”Writes that record something done in the world, a delivery or cash taken at a
door, take an optional occurredAt: when it happened, as an ISO date and time
with its offset. A phone out of signal sends the delivery later, and the
delivery still says when it happened.
It is taken within three days back and two minutes ahead of our clock. Outside that, or left out, it happened when the request arrived.