Torli API
Book appointments, read a worker's free times, and list services and workers from your own software. Everything a business can do at the counter, your system can do over HTTP.
Introduction
The API is organised around REST. It accepts JSON request bodies, returns JSON responses, and uses standard HTTP status codes and verbs. Every request must be made over HTTPS.
All endpoints are relative to this base URL:
https://torli.net/torliapi/v1
Authentication
Authenticate by sending your API key in the Authorization header as a bearer token.
Authorization: Bearer torli_sk_1a2b3c4d5e6f7890_xxxxxxxxxxxxxxxxxxxxxxxx
The header X-API-Key is accepted as an alternative if bearer tokens are awkward in your client.
Each key belongs to exactly one business, and every request is scoped to that business automatically. You never pass a business id, and a key cannot read or change another business's data.
Creating a key
Keys are created from the business dashboard. Sign in, go to Settings, and find the API access section.
- Give the key a name that says where it will be used.
- Choose an expiry. A key that expires limits the damage if it ever leaks; you can always create a new one.
- Select only the permissions the integration actually needs.
Revoking a key takes effect immediately. If you do not see the API access section in your settings, the feature is not yet enabled for your business — contact us and we will turn it on.
Permissions
Each key carries a set of permissions, and each endpoint requires one. A request with a key that lacks the permission is refused with 403 insufficient_scope. Grant the narrowest set that does the job.
| Permission | Allows |
|---|---|
services:read | List the services the business offers |
workers:read | List the workers of the business |
availability:read | Read free appointment slots |
appointments:write | Book appointments on behalf of customers |
Booking flow
A booking is four calls. Fetch the catalogue once and cache it; availability must be read fresh each time.
GET /services— let the customer pick a service, and note itsid.GET /workers— offer the workers whoseservice_idscontain that service.GET /availability— show the free start times for the chosen worker, service and day.POST /appointments— book one of those times.
409 slot_unavailable — the response includes the times that are still free, so you can offer them straight away rather than starting over.Endpoints
/meConfirms that a key works and reports which business it belongs to and what it is allowed to do. Make this your first call when setting up an integration.
Example request
curl https://torli.net/torliapi/v1/me \ -H "Authorization: Bearer torli_sk_YOUR_KEY"
Example response
{
"business": {
"id": "64f92377b0db42b258177408",
"name": "Dana's Barbershop"
},
"scopes": [
{ "scope": "services:read", "description": "List the services the business offers" },
{ "scope": "workers:read", "description": "List the workers of the business" },
{ "scope": "availability:read", "description": "Read free appointment slots" },
{ "scope": "appointments:write","description": "Book appointments on behalf of customers" }
]
}/servicesrequires services:readLists the services the business offers. Use the returned id as service_id in the availability and booking calls. Services hidden in the dashboard are not returned.
Example request
curl https://torli.net/torliapi/v1/services \ -H "Authorization: Bearer torli_sk_YOUR_KEY"
Example response
{
"services": [
{
"id": "656e49108ab3575e936ba0e1",
"name": "Haircut",
"description": "A classic haircut",
"duration": 60,
"price": 80
},
{
"id": "656e49108ab3575e936ba0e2",
"name": "Beard trim",
"description": "",
"duration": 20,
"price": 40
}
]
}/workersrequires workers:readLists the workers of the business. Each worker carries the ids of the services they perform, so you can offer the customer only the workers who can do the chosen service.
Example request
curl https://torli.net/torliapi/v1/workers \ -H "Authorization: Bearer torli_sk_YOUR_KEY"
Example response
{
"workers": [
{
"id": "64f923d5b0db42b258177415",
"name": "Dana",
"service_ids": ["656e49108ab3575e936ba0e1"]
}
]
}/availabilityrequires availability:readReturns the times at which the given service can start with the given worker on one day. Each value is a start time in HH:mm; the appointment runs for the duration of the service.
Parameters
| Name | Type | Description |
|---|---|---|
service_idrequired | string | Id of the service, from GET /services. |
worker_idrequired | string | Id of the worker, from GET /workers. |
daterequired | string | The day to check, as YYYY-MM-DD. |
splitTimeoptional | number | Granularity of the returned start times in minutes. Defaults to 5. Use 30 to offer half hour slots. |
Example request
curl -G https://torli.net/torliapi/v1/availability \ -H "Authorization: Bearer torli_sk_YOUR_KEY" \ -d service_id=656e49108ab3575e936ba0e1 \ -d worker_id=64f923d5b0db42b258177415 \ -d date=2026-10-06 \ -d splitTime=30
Example response
{
"date": "2026-10-06",
"worker_id": "64f923d5b0db42b258177415",
"service_id": "656e49108ab3575e936ba0e1",
"duration": 60,
"available_times": ["10:00", "10:30", "11:00", "13:00", "13:30"]
}/appointmentsrequires appointments:writeBooks an appointment. If the phone number does not belong to an existing customer, a customer record is created automatically. The customer receives the same WhatsApp confirmation as a booking made in the dashboard.
Parameters
| Name | Type | Description |
|---|---|---|
service_idrequired | string | Id of the service being booked. |
worker_idrequired | string | Id of the worker. Must be a worker who performs the chosen service. |
appointment_timerequired | string | Start time as YYYY-MM-DDTHH:mm. Must be one of the values returned by the availability call, in the future, and no more than 12 months ahead. |
customerPhonerequired | string | The customer's phone number, for example 0501234567 or +972501234567. |
customerNameoptional | string | Used when creating a new customer record. Ignored if the phone number is already known. |
customer_noteoptional | string | A note stored with the appointment and shown to the business. |
Example request
curl -X POST https://torli.net/torliapi/v1/appointments \
-H "Authorization: Bearer torli_sk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"service_id": "656e49108ab3575e936ba0e1",
"worker_id": "64f923d5b0db42b258177415",
"appointment_time": "2026-10-06T10:30",
"customerPhone": "0501234567",
"customerName": "Yossi Levi"
}'Example response
{
"id": "64f923d5b0db42b258177004",
"appointment_time": "2026-10-06T10:30",
"duration": 60,
"status": "approved",
"customer_notified": true,
"service": { "id": "656e49108ab3575e936ba0e1", "name": "Haircut", "price": 80 },
"worker": { "id": "64f923d5b0db42b258177415", "name": "Dana" },
"customer":{ "id": "64f923d5b0db42b258177003", "name": "Yossi Levi", "phone": "0501234567" }
}Errors
Torli uses conventional HTTP status codes. Codes in the 2xx range mean success, 4xx means the request was wrong in a way you can fix, and 5xx means something failed on our side.
Every error has the same shape:
{
"error": {
"code": "slot_unavailable",
"message": "The requested time is not available",
"details": {
"requested": "10:30",
"available_times": ["11:00", "11:30"]
}
}
}Branch on error.code rather than on the message text. Codes are stable; wording may be improved over time. The details object is present only where it adds something, such as which fields failed validation.
Error codes
| Status | Code | Meaning |
|---|---|---|
| 400 | validation_failed | A field is missing or malformed. The details object names the offending fields. |
| 401 | missing_credentials | No API key was sent on the request. |
| 401 | invalid_api_key | The key is not recognised, or the secret does not match. |
| 401 | api_key_expired | The key was valid but has passed its expiry date. Create a new one. |
| 401 | api_key_revoked | The key was revoked from the dashboard. |
| 403 | insufficient_scope | The key is valid but was not granted the permission this endpoint needs. |
| 403 | api_disabled | API access is not enabled for this business. |
| 404 | not_found | The endpoint does not exist, or the service or worker id does not belong to your business. |
| 409 | slot_unavailable | The requested time is no longer free. The details object lists the times that are. |
| 409 | conflict | The request could not be completed in the current state. |
| 429 | rate_limited | Too many requests. Slow down and retry. |
| 500 | internal_error | Something went wrong on our side. Safe to retry a read; see below for writes. |
Retrying safely
Read endpoints can be retried freely. Booking is not idempotent, so if a booking request fails without a clear answer, read the availability for that slot before retrying — if the slot is gone, your booking probably succeeded.
Rate limits
Limits are counted per API key, so one integration cannot exhaust another's budget. Exceeding a limit returns 429 rate_limited.
| Counted by | Limit | Applies to |
|---|---|---|
| Per API key | 120 requests / minute | All endpoints |
| Per API key | 30 requests / minute | Booking only |
| Per IP address | 600 requests / minute | Abuse protection, applied before your key is checked |
If you expect sustained traffic above these limits, get in touch before you build against them.
Support
Questions about the API, or need higher limits or a permission that does not exist yet? Contact us through the dashboard and include the key id (the torli_sk_… prefix shown in your settings) — never the full key.