{"components":{"headers":{"Retry-After":{"description":"Seconds to wait before the next request.","schema":{"example":42,"type":"integer"}},"X-RateLimit-Limit":{"description":"Requests allowed per window for this API key.","schema":{"example":120,"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"example":117,"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"example":42,"type":"integer"}}},"parameters":{"Limit":{"description":"Page size. Values below 1 fall back to 50; values over 200 are capped at 200.","in":"query","name":"limit","schema":{"default":50,"maximum":200,"type":"integer"}},"Offset":{"description":"How many items to skip. Negative values count as 0.","in":"query","name":"offset","schema":{"default":0,"minimum":0,"type":"integer"}}},"responses":{"AppointmentNotFound":{"content":{"application/json":{"example":{"key":"NOT_FOUND","message":"appointment not found"},"schema":{"$ref":"#/components/schemas/Error"}}},"description":"No appointment with this `id` for this assistant."},"BadRequest":{"content":{"application/json":{"examples":{"invalidJSON":{"value":{"key":"BAD_REQUEST","message":"invalid JSON body"}},"validation":{"value":{"key":"VALIDATION_ERROR","message":"service_id is required"}}},"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`BAD_REQUEST` when the body is not valid JSON, `VALIDATION_ERROR` when a field is missing or malformed."},"Forbidden":{"content":{"application/json":{"example":{"key":"FORBIDDEN","message":"API access comes with the Team plan. Switch to Team at https://dashboard.switchly.ai/settings/billing to use this key again."},"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The key is valid, but the business's plan no longer includes API access. Switching back to the Team plan under Settings → Billing in the dashboard restores it."},"InternalError":{"content":{"application/json":{"example":{"key":"INTERNAL_ERROR","message":"failed to book appointment"},"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Something failed on our side."},"RateLimited":{"content":{"application/json":{"example":{"key":"RATE_LIMITED","message":"rate limit exceeded"},"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Too many requests.","headers":{"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"Unauthorized":{"content":{"application/json":{"example":{"key":"UNAUTHORIZED","message":"invalid API key"},"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The API key is missing, unknown or revoked."},"ValidationError":{"content":{"application/json":{"example":{"key":"VALIDATION_ERROR","message":"service_id is required"},"schema":{"$ref":"#/components/schemas/Error"}}},"description":"A parameter is missing or malformed."}},"schemas":{"Appointment":{"example":{"calendar_id":"d1c9m2fh2vk2s8ot1a20","confirmed":true,"created_at":"2026-09-29T11:02:09+02:00","customer":{"email":"","mobile":"+420777123456","name":"Jana Nováková"},"duration_minutes":45,"end_time":"2026-10-05T09:45:00+02:00","id":"d3fh7ifh2vk2rg80af9g","secure_id":"88YD","service":{"id":"d1c9m2fh2vk2s8ot1a10","name":"Haircut","price":450},"start_time":"2026-10-05T09:00:00+02:00","status":"booked","title":""},"properties":{"calendar_id":{"example":"d1c9m2fh2vk2s8ot1a20","type":"string"},"cancelled":{"$ref":"#/components/schemas/Cancellation"},"confirmed":{"description":"Always true for appointments booked through the API.","type":"boolean"},"created_at":{"example":"2026-09-29T11:02:09+02:00","format":"date-time","type":"string"},"customer":{"$ref":"#/components/schemas/Customer"},"duration_minutes":{"example":45,"type":"integer"},"end_time":{"example":"2026-10-05T09:45:00+02:00","format":"date-time","type":"string"},"id":{"example":"d3fh7ifh2vk2rg80af9g","type":"string"},"modified_at":{"description":"When the appointment was last moved. Absent when it never was.","example":"2026-09-30T08:15:00+02:00","format":"date-time","type":"string"},"secure_id":{"description":"The short Secure ID the customer receives and can quote on the phone to change or cancel the booking.","example":"88YD","type":"string"},"service":{"$ref":"#/components/schemas/AppointmentService"},"start_time":{"example":"2026-10-05T09:00:00+02:00","format":"date-time","type":"string"},"status":{"description":"`modified` once the appointment has been moved; `cancelled` wins over `modified`.","enum":["booked","modified","cancelled"],"type":"string"},"title":{"description":"Free-text title. Usually empty for bookings.","example":"","type":"string"}},"required":["id","secure_id","status","title","calendar_id","start_time","end_time","duration_minutes","customer","service","confirmed","created_at"],"type":"object"},"AppointmentList":{"allOf":[{"$ref":"#/components/schemas/ListMeta"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/Appointment"},"type":"array"}},"required":["data"],"type":"object"}]},"AppointmentService":{"description":"The service as it was when booked or last moved.","properties":{"id":{"example":"d1c9m2fh2vk2s8ot1a10","type":"string"},"name":{"example":"Haircut","type":"string"},"price":{"example":450,"type":"number"}},"required":["id","name","price"],"type":"object"},"Assistant":{"properties":{"business_type":{"example":"Barbershop","type":"string"},"currency":{"description":"Currency of the service prices.","example":"CZK","type":"string"},"enabled":{"description":"Whether the assistant answers calls.","type":"boolean"},"id":{"example":"darat4b9f78s7bkrsdh0","type":"string"},"name":{"description":"Business name.","example":"Noah's Barbershop","type":"string"},"opening_hours":{"description":"The weekly opening hours, one entry per day.","items":{"$ref":"#/components/schemas/OpeningHours"},"type":"array"},"phone_number":{"description":"The number the assistant answers, E.164. Empty when none is assigned.","example":"+420228880123","type":"string"},"sms_number":{"description":"The number SMS go out from. Empty when SMS use a provider sender instead.","example":"","type":"string"},"timezone":{"description":"IANA timezone every time and date is interpreted in.","example":"Europe/Prague","type":"string"}},"required":["id","name","enabled","business_type","timezone","currency","phone_number","sms_number","opening_hours"],"type":"object"},"AssistantSummary":{"properties":{"business_type":{"example":"Barbershop","type":"string"},"enabled":{"description":"Whether the assistant answers calls.","type":"boolean"},"id":{"example":"darat4b9f78s7bkrsdh0","type":"string"},"name":{"description":"Business name.","example":"Noah's Barbershop","type":"string"},"phone_number":{"description":"The number the assistant answers, E.164. Empty when none is assigned.","example":"+420228880123","type":"string"},"timezone":{"description":"IANA timezone of the business.","example":"Europe/Prague","type":"string"}},"required":["id","name","enabled","timezone","phone_number","business_type"],"type":"object"},"Availability":{"example":{"days":[{"date":"2026-10-05","slots":[{"end":"2026-10-05T09:45:00+02:00","start":"2026-10-05T09:00:00+02:00"},{"end":"2026-10-05T10:30:00+02:00","start":"2026-10-05T09:45:00+02:00"}]},{"date":"2026-10-06","slots":[]}],"duration_minutes":45,"service_id":"d1c9m2fh2vk2s8ot1a10","timezone":"Europe/Prague"},"properties":{"days":{"description":"Every day of the requested range, in order.","items":{"$ref":"#/components/schemas/AvailabilityDay"},"type":"array"},"duration_minutes":{"example":45,"type":"integer"},"service_id":{"example":"d1c9m2fh2vk2s8ot1a10","type":"string"},"timezone":{"example":"Europe/Prague","type":"string"}},"required":["timezone","service_id","duration_minutes","days"],"type":"object"},"AvailabilityDay":{"properties":{"date":{"example":"2026-10-05","format":"date","type":"string"},"slots":{"description":"Free starts of the day, in order. Empty when the day has no room.","items":{"$ref":"#/components/schemas/Slot"},"type":"array"}},"required":["date","slots"],"type":"object"},"Calendar":{"properties":{"color":{"description":"Display color, as set in the dashboard. May be empty.","example":"#7c3aed","type":"string"},"description":{"example":"By the window","type":"string"},"id":{"example":"d1c9m2fh2vk2s8ot1a20","type":"string"},"name":{"example":"Chair 1","type":"string"}},"required":["id","name","description","color"],"type":"object"},"CalendarList":{"allOf":[{"$ref":"#/components/schemas/ListMeta"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/Calendar"},"type":"array"}},"required":["data"],"type":"object"}]},"Cancellation":{"description":"Present only on a cancelled appointment.","properties":{"at":{"example":"2026-09-30T08:15:00+02:00","format":"date-time","type":"string"}},"required":["at"],"type":"object"},"CreateAppointmentRequest":{"description":"`customer_email` or `customer_mobile` is required; both are fine.","example":{"customer_mobile":"+420777123456","customer_name":"Jana Nováková","service_id":"d1c9m2fh2vk2s8ot1a10","start_time":"2026-10-05T09:00:00+02:00"},"properties":{"calendar_id":{"description":"Book into this calendar. Without it the first free calendar is used.","type":"string"},"customer_email":{"example":"jana@example.com","format":"email","type":"string"},"customer_mobile":{"description":"International format (E.164). The SMS confirmation goes here.","example":"+420777123456","type":"string"},"customer_name":{"example":"Jana Nováková","type":"string"},"service_id":{"description":"An active service.","example":"d1c9m2fh2vk2s8ot1a10","type":"string"},"start_time":{"description":"A start from `GET /v1/availability`. Must not be in the past.","example":"2026-10-05T09:00:00+02:00","format":"date-time","type":"string"}},"required":["service_id","start_time","customer_name"],"type":"object"},"Customer":{"properties":{"email":{"description":"May be empty.","example":"jana@example.com","type":"string"},"mobile":{"description":"May be empty.","example":"+420777123456","type":"string"},"name":{"example":"Jana Nováková","type":"string"}},"required":["name","email","mobile"],"type":"object"},"Error":{"example":{"key":"CONFLICT","message":"requested time slot is not available"},"properties":{"key":{"description":"Stable, machine-readable error key.","enum":["BAD_REQUEST","UNAUTHORIZED","FORBIDDEN","NOT_FOUND","VALIDATION_ERROR","CONFLICT","RATE_LIMITED","INTERNAL_ERROR"],"type":"string"},"message":{"description":"Human-readable explanation. May be reworded; do not parse it.","type":"string"}},"required":["key","message"],"type":"object"},"Health":{"properties":{"app":{"example":"api","type":"string"},"appname":{"type":"string"},"builtat":{"type":"string"},"commit":{"description":"Link to the running commit.","type":"string"},"env":{"example":"production","type":"string"},"hostName":{"example":"api.switchly.ai","type":"string"},"message":{"description":"Message of the running commit.","type":"string"},"status":{"example":"ok","type":"string"},"uptime":{"example":"3h12m5.2s","type":"string"},"version":{"description":"Commit hash of the running build.","type":"string"}},"required":["status"],"type":"object"},"Info":{"properties":{"app":{"example":"api","type":"string"},"appname":{"type":"string"},"builtat":{"type":"string"},"env":{"example":"production","type":"string"},"hostName":{"example":"api.switchly.ai","type":"string"},"version":{"description":"Commit hash of the running build.","type":"string"}},"type":"object"},"ListMeta":{"properties":{"limit":{"example":50,"type":"integer"},"offset":{"example":0,"type":"integer"},"total":{"description":"Items matching the request, across all pages.","example":1,"type":"integer"}},"required":["total","limit","offset"],"type":"object"},"Me":{"properties":{"assistant":{"$ref":"#/components/schemas/AssistantSummary"},"email":{"example":"owner@example.com","format":"email","type":"string"},"name":{"example":"Noah Novák","type":"string"},"user_id":{"example":"cq7k2mfh2vk2s8ot0a00","type":"string"}},"required":["user_id","email","name","assistant"],"type":"object"},"OpeningHours":{"properties":{"closed":{"description":"The business is closed the whole day.","type":"boolean"},"day":{"description":"English weekday name.","example":"Monday","type":"string"},"slots":{"description":"Open intervals of the day; more than one when the business closes for lunch.","items":{"$ref":"#/components/schemas/TimeSlot"},"type":"array"}},"required":["day","closed","slots"],"type":"object"},"RescheduleAppointmentRequest":{"description":"At least one field is required.","example":{"start_time":"2026-10-05T11:00:00+02:00"},"properties":{"service_id":{"description":"Switch to this active service; the length follows it.","example":"d1c9m2fh2vk2s8ot1a11","type":"string"},"start_time":{"description":"The new start. Must not be in the past.","example":"2026-10-05T11:00:00+02:00","format":"date-time","type":"string"}},"type":"object"},"Service":{"properties":{"aliases":{"description":"Other names customers use for the service. May be null.","example":["men's cut"],"items":{"type":"string"},"nullable":true,"type":"array"},"currency":{"example":"CZK","type":"string"},"description":{"example":"Wash, cut and styling.","type":"string"},"duration_minutes":{"example":45,"type":"integer"},"id":{"example":"d1c9m2fh2vk2s8ot1a10","type":"string"},"name":{"example":"Haircut","type":"string"},"price":{"example":450,"type":"number"}},"required":["id","name","description","duration_minutes","price","currency","aliases"],"type":"object"},"ServiceList":{"allOf":[{"$ref":"#/components/schemas/ListMeta"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/Service"},"type":"array"}},"required":["data"],"type":"object"}]},"Slot":{"properties":{"end":{"example":"2026-10-05T09:45:00+02:00","format":"date-time","type":"string"},"start":{"description":"Send this as `start_time` to book it.","example":"2026-10-05T09:00:00+02:00","format":"date-time","type":"string"}},"required":["start","end"],"type":"object"},"TimeSlot":{"properties":{"close":{"description":"Local time, `HH:MM`.","example":"17:30","type":"string"},"open":{"description":"Local time, `HH:MM`.","example":"09:00","type":"string"}},"required":["open","close"],"type":"object"}},"securitySchemes":{"ApiKeyHeader":{"description":"An API key from Settings → API keys in the Switchly dashboard (Team plan). It starts with `ak_live_`.","in":"header","name":"X-API-Key","type":"apiKey"},"BearerAuth":{"description":"The same API key, sent as `Authorization: Bearer ak_live_…`.","scheme":"bearer","type":"http"}}},"info":{"contact":{"name":"Switchly","url":"https://switchly.ai/#contact"},"description":"The Switchly API reads the availability of a business and books, moves and\ncancels its appointments. It runs on the same booking engine as the AI\nreceptionist that answers the phone: the same opening hours, the same\ncalendars, the same conflict checks and the same SMS confirmations to the\ncustomer.\n\n## Getting an API key\n\nAPI keys come with the **Team** plan.\n\n1. Sign in to the Switchly dashboard at \u003chttps://dashboard.switchly.ai\u003e.\n2. Open **Settings → API keys** and choose **Create API key**.\n3. Copy the key. It starts with `ak_live_` and is shown **once**; Switchly\n   keeps only its hash.\n\nA key belongs to one assistant (one business). Everything the API returns\nor changes is scoped to that assistant; an ID of another business answers\n`404`. Revoke a key in the same place when it leaks or is no longer used.\nKeep keys on your server: a key in browser code is readable by anyone.\n\n## Authentication\n\nSend the key in the `X-API-Key` header, or as a bearer token:\n\n```\nX-API-Key: ak_live_…\nAuthorization: Bearer ak_live_…\n```\n\nA missing, unknown or revoked key answers `401 UNAUTHORIZED`, with no hint\nof which one it was. API access is part of the Team plan: when the business\nmoves to a lower plan its keys stay, but every request answers\n`403 FORBIDDEN` until it is back on Team. The plan is changed in the\ndashboard under **Settings → Billing**\n(\u003chttps://dashboard.switchly.ai/settings/billing\u003e).\n\n## Conventions\n\n- Times are RFC 3339 timestamps. Responses write them in the assistant's\n  timezone (`GET /v1/assistant` → `timezone`); requests may use any offset.\n- Dates (`YYYY-MM-DD`) are calendar days in the assistant's timezone.\n- Field names are `snake_case`. Bodies are JSON; send\n  `Content-Type: application/json`.\n- Collections share one envelope: `{ \"data\": [], \"total\", \"limit\", \"offset\" }`.\n  Page with `limit` (default 50, at most 200) and `offset`.\n\n## Rate limits\n\n120 requests per minute per API key. Every authenticated response carries\n`X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`\n(seconds until the window resets). Over the limit the API answers\n`429 RATE_LIMITED` with `Retry-After`. Requests without a valid key are\nlimited per IP address, at ten times the per-key limit.\n\n## Errors\n\nEvery error has the same body: a stable machine-readable `key` and a\nhuman-readable `message`. Branch on `key` and the HTTP status; the message\nmay be reworded.\n\n| Status | Key | Meaning |\n|---|---|---|\n| 400 | `BAD_REQUEST` | The body is not valid JSON. |\n| 400 | `VALIDATION_ERROR` | A parameter or field is missing or malformed. |\n| 401 | `UNAUTHORIZED` | Missing, unknown or revoked API key. |\n| 403 | `FORBIDDEN` | The key is valid, but the plan no longer includes API access. |\n| 404 | `NOT_FOUND` | No such route, or no such resource for this assistant. |\n| 409 | `CONFLICT` | The request clashes with the current state: the slot is taken, the appointment is cancelled, another booking holds the lock. |\n| 429 | `RATE_LIMITED` | Too many requests; wait `Retry-After` seconds. |\n| 500 | `INTERNAL_ERROR` | Something failed on our side. Safe to retry reads. |\n\n## Typical flow\n\n1. `GET /v1/assistant/services` — pick the service and keep its `id`.\n2. `GET /v1/availability?service_id=…\u0026from=…\u0026to=…` — offer the free starts.\n3. `POST /v1/appointments` — book one. `409` means somebody was faster:\n   ask for availability again.\n4. `PATCH` or `DELETE /v1/appointments/{id}` to move or cancel it later.\n","termsOfService":"https://switchly.ai/terms","title":"Switchly API","version":"1.0.0"},"openapi":"3.0.3","paths":{"/health":{"get":{"description":"Answers `200` while the service is up. No API key needed.","operationId":"getHealth","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}},"description":"The service is up."}},"security":[],"summary":"Health check","tags":["Service"]}},"/info":{"get":{"description":"Which build is running. No API key needed.","operationId":"getInfo","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Info"}}},"description":"The running build."}},"security":[],"summary":"Service info","tags":["Service"]}},"/v1/appointments":{"get":{"description":"Appointments of the assistant, newest start first.","operationId":"listAppointments","parameters":[{"description":"Only appointments starting at or after this time.","example":"2026-10-01T00:00:00+02:00","in":"query","name":"from","schema":{"format":"date-time","type":"string"}},{"description":"Only appointments starting at or before this time.","example":"2026-10-31T23:59:59+01:00","in":"query","name":"to","schema":{"format":"date-time","type":"string"}},{"description":"`booked` returns every appointment that is not cancelled, moved ones included; `cancelled` only the cancelled ones.","in":"query","name":"status","schema":{"enum":["booked","cancelled"],"type":"string"}},{"description":"Only appointments in this calendar.","in":"query","name":"calendar_id","schema":{"type":"string"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppointmentList"}}},"description":"A page of appointments."},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"summary":"List appointments","tags":["Appointments"]},"post":{"description":"Books a start returned by `GET /v1/availability`. The slot is checked\nagain under a per-business lock, so two clients racing for the last slot\nget one `201` and one `409`.\n\nWithout `calendar_id` the first calendar that is free at that time is\nused. When the business has SMS confirmations on and `customer_mobile`\nis given, the customer receives the confirmation with their Secure ID\n(`secure_id`); the owner gets the booking notification they set up.\n","operationId":"createAppointment","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAppointmentRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Appointment"}}},"description":"The booked appointment."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"content":{"application/json":{"example":{"key":"NOT_FOUND","message":"service not found"},"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unknown or inactive `service_id`, or unknown `calendar_id`."},"409":{"content":{"application/json":{"example":{"key":"CONFLICT","message":"requested time slot is not available"},"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The slot is taken or outside opening hours, the business has no calendar, or another booking is in progress."},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"summary":"Book an appointment","tags":["Appointments"]}},"/v1/appointments/{id}":{"delete":{"description":"Cancels the appointment. It stays readable, with `status` `cancelled`,\nand its slot is free again. The customer gets the cancellation SMS and\nthe owner the cancellation notification.\n","operationId":"cancelAppointment","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Appointment"}}},"description":"The cancelled appointment."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/AppointmentNotFound"},"409":{"content":{"application/json":{"example":{"key":"CONFLICT","message":"appointment is already cancelled"},"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The appointment is already cancelled or has already ended."},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"summary":"Cancel an appointment","tags":["Appointments"]},"get":{"operationId":"getAppointment","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Appointment"}}},"description":"The appointment."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/AppointmentNotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"summary":"Get an appointment","tags":["Appointments"]},"parameters":[{"description":"The appointment `id`.","example":"d3fh7ifh2vk2rg80af9g","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"patch":{"description":"Moves the appointment to a new start, a different service, or both.\nIt stays in its calendar and keeps its customer and `secure_id`. The\nnew slot must be free in that calendar, not counting the appointment\nitself, so a move by half an hour into its own time works.\n\nThe customer gets the modification SMS and the owner the modification\nnotification, the same as after a phone call. A request that changes\nnothing returns the appointment as it is and sends nothing.\n","operationId":"rescheduleAppointment","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RescheduleAppointmentRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Appointment"}}},"description":"The moved appointment, with `status` `modified`."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"content":{"application/json":{"example":{"key":"NOT_FOUND","message":"appointment not found"},"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unknown appointment, or unknown or inactive `service_id`."},"409":{"content":{"application/json":{"example":{"key":"CONFLICT","message":"requested time slot is not available"},"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The appointment is cancelled or has ended, the new slot is taken or outside opening hours, or another booking is in progress."},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"summary":"Move an appointment","tags":["Appointments"]}},"/v1/assistant":{"get":{"description":"The business the key belongs to — its name, timezone, currency, phone numbers and weekly opening hours.","operationId":"getAssistant","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Assistant"}}},"description":"The assistant."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"summary":"Get the assistant profile","tags":["Assistant"]}},"/v1/assistant/calendars":{"get":{"description":"The calendars appointments are booked into — typically one per person,\nchair or room. Their `id` is what `calendar_id` takes. The list is never\npaginated: `limit` equals `total`.\n","operationId":"listCalendars","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalendarList"}}},"description":"The calendars."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"summary":"List calendars","tags":["Assistant"]}},"/v1/assistant/services":{"get":{"description":"The services the business offers. Their `id` is what `service_id` takes\neverywhere else. The list is never paginated: `limit` equals `total`.\n","operationId":"listServices","parameters":[{"description":"Also return services switched off in the dashboard. They cannot be booked.","in":"query","name":"include_inactive","schema":{"default":false,"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceList"}}},"description":"The services."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"summary":"List bookable services","tags":["Assistant"]}},"/v1/availability":{"get":{"description":"Free start times for one service, day by day, for up to 31 days. A\nstart is free when the whole service fits into the opening hours and\nnothing overlaps it: no Switchly booking and no event in a connected\nGoogle calendar.\n\nWithout `calendar_id` a start is listed when **any** calendar can take\nit — booking without `calendar_id` then picks the first calendar that\nis free. Every day of the range is in the answer; a day without room\nhas an empty `slots` list. Starts that have already passed are left\nout.\n\nUse `from`/`to` for a range, or `date` for a single day. Without any of\nthem the answer is today.\n","operationId":"getAvailability","parameters":[{"description":"An active service of the assistant.","example":"d1c9m2fh2vk2s8ot1a10","in":"query","name":"service_id","required":true,"schema":{"type":"string"}},{"description":"First day, inclusive. Defaults to today.","example":"2026-10-05","in":"query","name":"from","schema":{"format":"date","type":"string"}},{"description":"Last day, inclusive. Defaults to `from`. At most 31 days after `from`, counting both.","example":"2026-10-09","in":"query","name":"to","schema":{"format":"date","type":"string"}},{"description":"A single day. Cannot be combined with `from` or `to`.","in":"query","name":"date","schema":{"format":"date","type":"string"}},{"description":"Only this calendar.","in":"query","name":"calendar_id","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Availability"}}},"description":"The free starts, day by day."},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"content":{"application/json":{"example":{"key":"NOT_FOUND","message":"service not found"},"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unknown or inactive `service_id`, or unknown `calendar_id`."},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"summary":"Get free start times","tags":["Availability"]}},"/v1/me":{"get":{"description":"The user who owns the assistant, and a summary of the assistant the key belongs to. A cheap way to check that a key works.","operationId":"getMe","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Me"}}},"description":"The account.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"summary":"Get the account behind the API key","tags":["Account"]}}},"security":[{"ApiKeyHeader":[]},{"BearerAuth":[]}],"servers":[{"description":"Production","url":"https://api.switchly.ai"}],"tags":[{"description":"Who the API key belongs to.","name":"Account"},{"description":"The business behind the key, its services and calendars.","name":"Assistant"},{"description":"Free start times, computed from opening hours, bookings and connected calendars.","name":"Availability"},{"description":"Booking, reading, moving and cancelling appointments.","name":"Appointments"},{"description":"Unauthenticated service endpoints.","name":"Service"}]}