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
An appointment booked through the API behaves exactly like one booked in the dashboard: it appears in the calendar, the customer gets the usual WhatsApp confirmation, and reminders are scheduled as normal.

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.

Your API key can read your business data and book appointments on your behalf. Keep it on a server you control. Never put it in a mobile app, a browser page, or a public repository — anything sent to a browser can be read by anyone.

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.
The key is shown once, at the moment you create it. We store only a hash of it, so it cannot be shown again or recovered by support. If you lose it, revoke it and create another.

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.

PermissionAllows
services:readList the services the business offers
workers:readList the workers of the business
availability:readRead free appointment slots
appointments:writeBook 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 its id.
  • GET /workers — offer the workers whose service_ids contain that service.
  • GET /availability — show the free start times for the chosen worker, service and day.
  • POST /appointments — book one of those times.
Always book a time that came back from the availability call. Slots are not held while your customer decides, so if someone else books first you will get 409 slot_unavailable — the response includes the times that are still free, so you can offer them straight away rather than starting over.

Endpoints

GET/me

Confirms 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" }
  ]
}
GET/servicesrequires services:read

Lists 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
    }
  ]
}
GET/workersrequires workers:read

Lists 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"]
    }
  ]
}
GET/availabilityrequires availability:read

Returns 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

NameTypeDescription
service_idrequiredstringId of the service, from GET /services.
worker_idrequiredstringId of the worker, from GET /workers.
daterequiredstringThe day to check, as YYYY-MM-DD.
splitTimeoptionalnumberGranularity 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"]
}
POST/appointmentsrequires appointments:write

Books 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

NameTypeDescription
service_idrequiredstringId of the service being booked.
worker_idrequiredstringId of the worker. Must be a worker who performs the chosen service.
appointment_timerequiredstringStart 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.
customerPhonerequiredstringThe customer's phone number, for example 0501234567 or +972501234567.
customerNameoptionalstringUsed when creating a new customer record. Ignored if the phone number is already known.
customer_noteoptionalstringA 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

StatusCodeMeaning
400validation_failedA field is missing or malformed. The details object names the offending fields.
401missing_credentialsNo API key was sent on the request.
401invalid_api_keyThe key is not recognised, or the secret does not match.
401api_key_expiredThe key was valid but has passed its expiry date. Create a new one.
401api_key_revokedThe key was revoked from the dashboard.
403insufficient_scopeThe key is valid but was not granted the permission this endpoint needs.
403api_disabledAPI access is not enabled for this business.
404not_foundThe endpoint does not exist, or the service or worker id does not belong to your business.
409slot_unavailableThe requested time is no longer free. The details object lists the times that are.
409conflictThe request could not be completed in the current state.
429rate_limitedToo many requests. Slow down and retry.
500internal_errorSomething 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 byLimitApplies to
Per API key120 requests / minuteAll endpoints
Per API key30 requests / minuteBooking only
Per IP address600 requests / minuteAbuse 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.