One bearer token, and only the permissions you were given
Authorization: Bearer <token>. A token holds an explicit list of scopes; anything it was not granted is refused, and a token granted nothing can do nothing.Getting a key
Keys belong to the operator, not to you. An admin on their account opens their API keys screen, picks the scopes your system needs, and gives you the key that appears — once. We store only a hash of it, so nobody, including us, can read it back. If it is lost, the operator revokes it and mints another.
Ask for the narrowest set that does the job. It is the operator who decides, and a request for everything is a slower conversation than a request for two scopes with a reason attached.
Open the API keys screenUsing it
curl https://hireabus.com/api/v1/me \
-H "Authorization: Bearer $HIREABUS_API_KEY" \
-H "Accept: application/json"/api/v1/me needs no scope and is the cheapest way to check a key is alive and which operator it belongs to. Send the header on every request — there are no cookies, no session and no CSRF token on this API.
Scopes
These are the whole list. There is no free-text scope and no wildcard: a key holds cases from this table and nothing else, which is why a scope added tomorrow cannot silently widen a key issued today.
jobs:readSee job requestsRead the job requests in your inbox, without customer contact details.
GET /api/v1/jobsGET /api/v1/jobs/{ref}— One job, addressed by its trip ref
quotes:writeSend and update quotesSubmit, revise, withdraw and decline quotes, and answer booking requests.
POST /api/v1/jobs/{ref}/confirm-booking-request— Accept a customer's booking request: charges their saved card for the deposit and confirms the bookingPOST /api/v1/jobs/{ref}/decline— Turn the job downPOST /api/v1/jobs/{ref}/quote— Submit a quote, or revise one already submittedPOST /api/v1/jobs/{ref}/reject-booking-request— Turn a booking request down, with a reason the customer is shownPOST /api/v1/jobs/{ref}/unconfirm— Drop a confirmed quote to `likely` without touching the price. The customer can still request to book; it just stops being instantly bookablePOST /api/v1/jobs/{ref}/withdraw— Retract a quote the customer has not yet acted on
bookings:readSee confirmed bookingsIncludes customer contact detailsRead your confirmed bookings, including the customer contact details you already see in the portal.
GET /api/v1/bookingsGET /api/v1/bookings/{ref}POST /api/v1/bookings/{ref}/driver— Assign (or re-assign) a driver and vehicle registration
drivers:readSee your driversRead your driver roster.
GET /api/v1/drivers
drivers:writeAdd and update driversAdd drivers to your roster and update their details.
POST /api/v1/bookings/{ref}/driver— Assign (or re-assign) a driver and vehicle registrationPOST /api/v1/drivers— Add a driver to the rosterPOST /api/v1/drivers/{id}/reinvite— Re-send the driver's app sign-in link
payouts:readSee your payoutsRead your payout history and what is on its way.
GET /api/v1/payouts
webhooks:manageManage event notificationsSet up and remove the URLs we notify when something changes.
GET /api/v1/webhooksPOST /api/v1/webhooks— Subscribe a URLDELETE /api/v1/webhooks/{id}GET /api/v1/webhooks/{id}/deliveries— The delivery log for one subscription — what we sent, when, and what status came back. This is the endpoint an integrator uses to answer "did you send it?" without asking us
A job before a deposit is paid carries the itinerary and nothing that identifies the customer — no name, no email, no phone number. Those appear on a booking once it is won, and only to a key holding a scope that says so.
Tokens for the mobile app
The Hire A Bus operator and driver apps get their token a different way — a sign-in link or an SMS code exchanged for a bearer, rotated per device and revocable per handset. These endpoints exist for those apps; a third-party integration uses a key from the portal instead.
| Endpoint | What it does |
|---|---|
| GET /api/v1/auth/devices | The signed-in devices for the calling operator user |
| DELETE /api/v1/auth/devices/{id} | Sign a device out. Takes effect on that device's very next call |
| POST /api/v1/auth/exchange | Exchange a consumed sign-in link for a bearer token |
| POST /api/v1/auth/link | Request a sign-in link |
| POST /api/v1/auth/rotate | Rotate the presented token. The old one dies in the same request |
When we say no
Every failure has the same shape. Branch on error.code, which is stable; error.message is written for a human and may be reworded.
{
"error": {
"code": "missing_ability",
"message": "This key is not allowed to send quotes.",
"missing_ability": "quotes:write"
}
}| Status | error.code | What it means |
|---|---|---|
| 401 | unauthenticated | No token, or one that has expired or been revoked. Nothing to retry — get a new key. |
| 403 | missing_ability | The key is real but is not allowed to do this. `error.missing_ability` names the one scope it is short of. |
| 404 | not_found | No such thing on your account. A reference belonging to another operator answers 404, not 403, so the API cannot be used to discover what exists. |
| 429 | rate_limited | Too fast. `Retry-After` says how long to wait. |
A 403 is the only one worth special-casing: it names the exact scope the key is short of, so your error message to the operator can tell them what to tick when they mint the replacement.
Looking after the key
Keep it server-side. It is a credential for someone else's business: it can see their jobs, and depending on scope it can price work and see the people who booked it. Do not put it in a mobile app, a browser or a repository, and rotate it if it has ever been somewhere it should not have been — the operator can revoke and re-issue in under a minute.
