GET/api/v1/business
The business this key belongs to
No identifier is passed: the key names the business.
200 401 invalid_token, token_revoked, token_expired403 insufficient_scope — needs catalogue:read429 rate_limited
Every endpoint version 1 serves. Additive only: fields and endpoints may be added, and nothing already here is renamed, removed or retyped.
See also webhooks and the MCP server.
Send the key as a bearer token on every request. The session cookie is not accepted here, and the key names the business, so no path carries a business identifier.
curl -H "Authorization: Bearer torim_…" \ https://torim.ai/api/v1/business
The machine-readable schema for everything below is at /api/v1/openapi.json.
Chosen when the key is created and fixed for its life. A call needing a scope the key does
not hold answers 403 insufficient_scope.
| catalogue:read | The business, its services, its team and its free times. Every key holds this. |
| appointments:read | The diary: who is booked, when, and how to reach them. |
| appointments:write | Book, move and cancel appointments. |
| catalogue:write | Change services, opening hours and closures. |
The business this key belongs to
No identifier is passed: the key names the business.
200 401 invalid_token, token_revoked, token_expired403 insufficient_scope — needs catalogue:read429 rate_limited
Days the business is shut, from today on
200 403 insufficient_scope — needs catalogue:read
Shut the business for a day or a run of days
Body
from date, required — First day closed, `YYYY-MM-DD`.reason string,null to date — Last day closed, inclusive. Defaults to `from`, since a one-day
closure is the common case.201 400 invalid_range403 insufficient_scope — needs catalogue:write
Reopen a day that was closed
Parameters
id path, required — The closure's id204 Reopened403 insufficient_scope — needs catalogue:write404 no such closure here
The week the business is open
200 403 insufficient_scope — needs catalogue:read
Replace that week
The whole week, not a patch: "Tuesday now closes at six" and "Tuesday is gone" are the same edit to a caller holding the week. Sending an empty list closes the business, which is a real thing to want and not an error.
Body
rules OpeningRuleBody[], required 200 400 invalid_window, overlapping_hours, invalid_weekday403 insufficient_scope — needs catalogue:write
Every service, including retired ones
200 403 insufficient_scope — needs catalogue:read
Add a service
The slug is chosen by the server and returned — it has to be unique within the business, and a caller-proposed one is either a collision it cannot act on or a uniqueness check that races.
Body
arrival_window_min int32 — For a home visit, the arrival window it is booked as, in minutes —
"between eight and eleven" is 180 — or `null` for an exact time. A
multiple of half an hour, up to eight hours. Absent leaves it as it
is, for the reason the instructions give; anything but a home visit
has none.description_en string,null description_he string,null duration_min int32, required instructions_en string,null instructions_he string,null — What a customer should read before the visit, in paragraphs — shown on
the page, the confirmation and the visit card. Empty or `null` clears
them; **absent leaves them as they are**, unlike every field above.
The exception is on purpose: `/api/v1` takes this same body, and an
integration written before these fields existed sends a whole service
without them. Read as "none", its next update would silently wipe what
the business wrote for its customers.location string — `on_site` (the default), `online` or `at_home`. An online service
carries a room in `meeting_url`; the other two refuse one.meeting_url string,null name_en string,null name_he string, required occupy_min int32 — How long a booking holds the person performing it, when that differs
from how long the customer is booked for. Absent means it does not.price_agorot int32, required rebook_after_days int32 — How many days after a visit to nudge the customer to book the next
one — between [`REBOOK_AFTER_DAYS`]'s ends, or absent for never. Only
people who agreed to hear from the business are nudged.show_duration boolean,null — Whether customers are told how long it takes. The length still sets
the times offered either way. Absent leaves it as it is — on, for a
new service — for the reason the instructions give below.show_price boolean sort_order int32 visits_per_window int32 — For a home visit booked as a window, how many visits one person takes
in it — `null` for as many as the visit's length fits, never more.
Absent leaves it as it is.201 400 invalid_duration, invalid_occupancy, invalid_price and the other field checks403 insufficient_scope — needs catalogue:write
Change a service
Every field is replaced, so send the whole service. Changing a price leaves appointments already booked alone — each one keeps what it was booked with.
Parameters
slug path, required — The service's slugBody
arrival_window_min int32 — For a home visit, the arrival window it is booked as, in minutes —
"between eight and eleven" is 180 — or `null` for an exact time. A
multiple of half an hour, up to eight hours. Absent leaves it as it
is, for the reason the instructions give; anything but a home visit
has none.description_en string,null description_he string,null duration_min int32, required instructions_en string,null instructions_he string,null — What a customer should read before the visit, in paragraphs — shown on
the page, the confirmation and the visit card. Empty or `null` clears
them; **absent leaves them as they are**, unlike every field above.
The exception is on purpose: `/api/v1` takes this same body, and an
integration written before these fields existed sends a whole service
without them. Read as "none", its next update would silently wipe what
the business wrote for its customers.location string — `on_site` (the default), `online` or `at_home`. An online service
carries a room in `meeting_url`; the other two refuse one.meeting_url string,null name_en string,null name_he string, required occupy_min int32 — How long a booking holds the person performing it, when that differs
from how long the customer is booked for. Absent means it does not.price_agorot int32, required rebook_after_days int32 — How many days after a visit to nudge the customer to book the next
one — between [`REBOOK_AFTER_DAYS`]'s ends, or absent for never. Only
people who agreed to hear from the business are nudged.show_duration boolean,null — Whether customers are told how long it takes. The length still sets
the times offered either way. Absent leaves it as it is — on, for a
new service — for the reason the instructions give below.show_price boolean sort_order int32 visits_per_window int32 — For a home visit booked as a window, how many visits one person takes
in it — `null` for as many as the visit's length fits, never more.
Absent leaves it as it is.200 400 a field failed its check404 no such service here
Retire a service, or bring it back
There is no delete. A slug appears in booking links already handed out and in appointments already made; retiring stops new bookings and leaves both intact.
Parameters
slug path, required — The service's slugBody
active boolean, required 200 404 no such service here
Who works here
200 403 insufficient_scope — needs catalogue:read
When one service can be booked
Answered by the same code the public booking page uses, so a time offered here is a time `POST /appointments` will accept.
Parameters
service query, required — Service **slug**, as `/api/v1/services` returns it.from query — First day to look at, `YYYY-MM-DD`. Defaults to today where the
business is.days query — How many days to look at. Capped by the business's own booking horizon.200 400 invalid_range404 no such service
The diary, a window at a time
`to` is inclusive and defaults to six days after `from`. The window may not exceed 62 days.
Parameters
from query — First day of the window, `YYYY-MM-DD`. Defaults to today.to query — Last day of the window, inclusive. Defaults to six days after `from`.200 400 invalid_range403 insufficient_scope — needs appointments:read
Book somebody in
The slot is re-derived server-side and the price is read from the service, so neither is taken from this request. The booking is the business's own, so it is `confirmed` at once: it never waits for approval, and neither an unsigned consent form nor a rule about a customer's no-shows or cancellations refuses it — those govern customers booking for themselves.
Body
customer_email string,null — The only way this booking can ever be written to — a walk-in with no
address can be told nothing when it moves.customer_name string, required customer_phone string,null note string,null — Free text for the diary. A service at the customer's home takes no
address here: only a customer's own account sets one, so a walk-in's
address is written in the note, as the desk does.off_grid boolean — Take `start` as given rather than checking it against the slot grid.
Defaults to false, which is the safe answer for a program: ask
`/availability` and book one of the times it gave you. Set it when you
are recording something that happened away from the grid — a walk-in at
10:07 — and accept that nothing then checks the time is sensible except
the double-booking constraint.service string, required — Service **slug**.staff_id uuid, required — Which member of the team performs it. From `/api/v1/team`.start date-time, required — Absolute start instant, RFC 3339.201 400 invalid_name, invalid_email, invalid_service403 insufficient_scope — needs appointments:write404 no such service, or that person does not work here409 slot_unavailable, slot_taken, session_full
One appointment
Parameters
id path, required — The appointment's id200 404 no such appointment here
Call one off
Frees the slot and tells the waiting list. Where the business keeps a freed time for whoever has waited longest, `/availability` does not offer it while it is kept. A `pending` request may be cancelled here too; this is a cancellation, not the diary's decline, and the customer is not sent the decline message.
Parameters
id path, required — The appointment's id200 400 illegal_status404 no such appointment here
Reschedule
One update under the same lock a booking takes, so a lost race leaves the appointment where it was rather than nowhere.
Parameters
id path, required — The appointment's idBody
off_grid boolean — As on `CreateAppointment`.start date-time, required — The new start instant, RFC 3339.200 404 no such appointment here409 already_cancelled, slot_unavailable, slot_taken
Every appointment carries a status. Statuses may be added
within v1 — pending was — so do not treat the list as closed.
| pending | A customer's booking request, waiting for the business to approve it — only where the business asks for approval. It holds its time like a confirmed booking. Approved, it becomes confirmed; declined, or unanswered about an hour before it starts, cancelled. A booking made with a key is never pending. |
| confirmed | Booked, and still ahead or not yet marked. |
| completed | Marked done in the diary. |
| no_show | Marked in the diary as somebody who did not come. |
| cancelled | Called off, by either side. Cancelled appointments stay in the list. |
A customer may book several services back to back as one visit. Each part is an
appointment of its own here, and every part carries the same visit_id; a booking of one service has none. Cancelling or moving a part through this API leaves
the others where they are.
A booking made with a key is the business's own: it is confirmed at once, and neither an unsigned consent form nor a rule about a customer's no-shows or cancellations refuses it. Those apply to customers booking for themselves.
Every failure is JSON carrying a stable code and an English error. Switch on the code; the sentence is for you reading
logs, and may be reworded.
{ "code": "insufficient_scope", "error": "this token does not hold the permission for that" } | 401 | invalid_token | No bearer token, or no such token. |
| 401 | token_revoked | The key was revoked. |
| 401 | token_expired | The key is past its expiry. |
| 403 | insufficient_scope | The key does not hold the scope this call needs. |
| 429 | rate_limited | Too many requests on this key. Back off. |
| 404 | not_found | No such thing, or it belongs to somebody else. |
| 409 | slot_unavailable | That time is not one the grid offers. |
| 409 | slot_taken | Somebody reached that slot first. |
| 500 | server_error | Something broke on our side. |
600 requests an hour per key, counted per key rather than per account — two integrations
do not share a budget. Over it answers 429 rate_limited.
Version 1 is additive only: fields and endpoints may be added, and nothing already here is
renamed, removed or retyped. Requests reject unknown fields, so a misspelled one is a 400 rather than a value silently ignored.
Something not answered here? Get in touch.