Keys
An agency generates its own keys from Settings. A key belongs to exactly one agency, and there is no way to ask for another agency's data with a valid key.
The secret is shown once, when the key is created. We store only its SHA-256, so it cannot be recovered or shown again. If it leaks, revoke it and issue another; revocation takes effect on the next request rather than eventually.
Authorization: Bearer bk_live_…
Scopes
Grants are per area and per level, not one global read and write. An integration that pushes placements has no business reading your billing ledger, so it cannot. Write implies read for the same area only; it never leaks across areas.
- placements the book: placements, workers, clients, contacts, and the bulk importer
- timesheets weeks, shifts, AWR absences, and the approval chain
- billing both invoice series and the accounting exports
- compliance KIDs, right-to-work records, umbrella evidence, the evidence pack
- jobs postings, applications, the talent pool
A scope is written area:level, and a key carries a space-separated list:
That key can push the book and the weeks, read the compliance position, and cannot see the money at all.
What no key can do
Three things are closed to every key whatever its scopes, because they are not integration work.
- The operator plane. That is us running the platform, not you using it.
- Key management. A leaked key cannot mint its own replacement or widen its own grants. Only a signed-in person can.
- Subject access and erasure. A DSAR and an erasure are decisions a person makes and answers for. Handing them to a script that runs unattended is how an agency erases a record it was legally required to keep.
Anything we have not published is also refused. A new endpoint has to be classified deliberately, so it can never quietly widen a key you were issued months ago.
Shape
JSON in, JSON out, over HTTPS. Errors carry a message written for a person, because our refusals say why: a self-billed invoice raised before its agreement exists, a right-to-work check missing the evidence October requires, an umbrella placement with no umbrella named. Those are the product working, not failures to route around.
An unknown or revoked key gets 401. A valid key reaching outside its grants gets 403, and the message names the area it would have needed. Every write lands on the same hash-chained audit trail a person's click would, attributed to the key that made it.
Endpoints
The published surface, by area. Anything not listed here is refused to every key, including endpoints added after your key was issued.
placements
- POST /api/placements open an assignment; umbrella engagements must name their umbrella
- GET /api/placements the book
- POST|GET /api/directory/workers and /clients, /contacts, /umbrellas
- PATCH /api/directory/umbrellas/{id} update an umbrella company
- POST /api/import/validate then /api/import/execute the five-CSV bulk load, dry run first
timesheets
- POST|GET /api/timesheets weeks, and POST /api/timesheets/{id}/submit
- GET /api/timesheets/{id}/entries the days behind a week
- GET /api/timesheets/awr the AWR clock per placement
- POST|GET /api/shifts the rota, with /{id}/worked, /{id}/cancel
- POST /api/shifts/build-week turn worked shifts into a draft week; a night shift counts on the day it began
- POST /api/awr-absences record sickness, family leave or a shutdown so the clock pauses or accrues correctly
compliance
- POST|GET /api/compliance/rtw right-to-work records, method and evidence
- POST /api/compliance/kid/{placementId} issue the Key Information Document
- POST /api/compliance/assignment-confirmation/{placementId} the regulation 21 confirmation
- POST /api/compliance/umbrellas/{id}/evidence file an assessment document
- GET /api/compliance/jsl the joint and several liability register
- GET /api/compliance/changes the change radar: each dated change with the people it touches
- GET /api/compliance/evidence-pack/{placementId} the whole chain for one placement
billing
- POST /api/billing/agreements and /umbrella-agreements the self-billing agreements that must exist before an invoice can be raised
- POST /api/billing/invoices/generate raise self-billed purchase invoices from approved weeks
- POST|GET /api/billing/client-invoices your own sales series, with /{id}/sent, /paid, /void
- GET /api/billing/invoices/xero.csv and /client-invoices/xero.csv
- GET /api/reports/intermediaries.csv the quarterly return, built from placements rather than invoices
jobs
- /api/jobs postings and applications
- /api/talent the pool, including CV ingestion
- /api/proposals put a candidate forward; only the candidate accepts
A worked example
The usual first integration: your CRM owns candidates and vacancies, BookKept owns the evidence chain.
- Create a key scoped placements:write timesheets:write compliance:read.
- On every new assignment, POST the placement. Umbrella engagements must name their umbrella; that link is the joint and several liability evidence and is refused without it.
- Push approved hours as timesheet weeks, or let the client sign them off through their portal, whichever suits your flow.
- Read the compliance position back into your own dashboard: which placements lack a KID, which umbrellas carry live placements on a stale file, which workers need re-checking before 1 October.
Adopt the compliance layer without a migration
Nothing here asks you to move your candidate data or retrain a desk. Keep the system your consultants know, and put the part that carries statutory risk somewhere it is evidenced, dated and hash-chained. If you later want the whole thing, the importer takes your book as five CSVs and reads your own column headings.