{
  "openapi": "3.1.1",
  "info": {
    "title": "Business Management",
    "version": "3.0.0-preview",
    "termsOfService": "https://alteg.io/en/info/terms/",
    "license": {
      "name": "Altegio API Agreement",
      "url": "/en/api-license-agreement"
    },
    "description": "> ⚠️ **Preview — this API is not live yet.** Planned release: **October 2026**. The endpoint surface is not deployed; paths and schemas may change before the contract freeze, and try-it and production calls are unavailable.\n\n# Overview\n\nThe Business Management API V3 is Altegio's next-generation public REST API.\nThis document is a **preview of the V3 contract**: it describes the target API surface so\npartners and tooling can review it before the endpoints go live.\n\n**Preview status — please read first:**\n\n- This is a **contract preview, not a live production API**. The endpoint surface is not\n  deployed yet; try-it consoles and production calls are unavailable.\n- Paths, schemas, and field names **may change before the contract freeze** without\n  backward-compatibility guarantees.\n- Every operation carries an `x-altegio-status`. Three values are defined:\n  `planned` — designed, not scheduled for the first production release;\n  `preview` — contract published for review, not deployed;\n  `available` — live in production and covered by the October 2026 release promise.\n  **Today every operation is `preview`** — none of them is deployed yet. The October\n  release covers only operations marked `available`, so publishing the full target\n  surface here is not a promise that all of it ships at once.\n  `available` is enforced by CI in `biz.erp`: an `available` operation without a registered\n  route fails the OpenAPI conformance gate.\n- Where a design decision is still pending, the affected element also carries\n  `x-altegio-assumption` describing the open question.\n\n**Base URL (resource API):** `https://api.alteg.io/api/v3`\n\nAll resources are under `/api/v3` and scoped to a Location:\n`/api/v3/locations/{location_id}/...`. The `location_id` in the path is verified against\nthe credential used for the call — a Location the credential cannot access responds with\n`404`. OAuth and discovery endpoints live on the authorization host root\n`https://api.alteg.io` (no `/api/v3` prefix) — see the Authorization tag.\n\n**Canonical terms.** V3 uses one public name per entity:\n\n| Term | Meaning |\n|---|---|\n| `location` | A business location (branch). |\n| `team_member` | A person on the Location's team who provides services. |\n| `service` | A bookable service offered at a Location. |\n| `client` | A person served by a Location — the person appointments are scheduled for. |\n| `appointment` | A scheduled visit entry for one Team Member. |\n| `visit` | The billing unit that groups appointments and carries items and payments. |\n\n**Response format.** V3 is flat resource REST. Single resources are plain\nJSON objects with integer `id` and a string `object` type marker. Lists use one predictable\nenvelope. V3 does **not** use JSON:API (`data.attributes`) or wrapper envelopes such as `{success,data,meta}`\nenvelopes.\n\n**Object types.** Every resource declares its kind in the `object` field — the full\ndictionary of this preview:\n\n| `object` | What it is |\n|---|---|\n| `location` | A business location and its settings. |\n| `service` / `service_category` | Catalog: a bookable service (simple or package) and its category. |\n| `team_member` / `position` | A person on the team and the position dictionary. |\n| `client` | A person served by a Location. |\n| `availability_slot` | A bookable time slot (computed, read-only). |\n| `resource` / `resource_instance` | Bookable resources (rooms, equipment) and their instances. |\n| `appointment` | A scheduled entry for one Team Member. |\n| `visit` / `visit_item` | The billing unit grouping appointments; its service/product lines. |\n| `payment` / `refund` | A payment applied to a visit and its local reversal. |\n| `payment_method` / `account` | Payment configuration dictionaries of the Location. |\n| `product` | A sellable product from the Location catalog (minimal P0 read). |\n| `accessible_resource` | A Location or chain grant visible on the current token. |\n| `team_member_access` | System-access status and role for a Team Member. |\n| `appointment_create_result` | Package booking result with multiple appointments. |\n| `list` | The list envelope wrapping any collection response. |\n\n# Authorization\n\nV3 accepts exactly one credential shape on every call:\n\n```\nAuthorization: Bearer <token>\n```\n\nTokens are **opaque** strings — never parse or decode them. Access tokens are short-lived\n(about 15 minutes). There is **no public password grant**: applications never collect\nAltegio passwords. Three ways to obtain a credential are supported.\n\n## Client Credentials + `location_id` (marketplace, machine-to-machine)\n\nFor marketplace applications acting autonomously (backends, background jobs, agents):\n\n1. A business installs your application in a Location and approves the scopes your\n   application requests.\n2. Your backend calls `POST /oauth/token` with `grant_type=client_credentials`, HTTP Basic\n   client authentication (`client_id` / `client_secret`), and the explicit `location_id` you\n   want to act in. One token is bound to **one** Location — an application installed in\n   twenty Locations requests twenty tokens, and there is no account-wide token.\n3. The server resolves your installation from the authenticated OAuth client plus that\n   Location, verifies the installation is active, and issues a short-lived opaque access\n   token. The token **acts on behalf of the installation** — writes are allowed within\n   the scopes the business granted at install time.\n4. Call the API with `Authorization: Bearer <access_token>`. This flow has **no refresh\n   token** — request a new access token when the current one expires.\n\n**Not every scope exists in every credential class.** The `scopes` list published under\neach OAuth flow is the set that flow can actually be granted — requesting anything outside\nit fails with `invalid_scope`. Restricted keys use the same public catalog, except for\ncredential-class exclusions called out here:\n\n| Scope | First-iteration availability |\n|---|---|\n| `chain_*` (4 scopes) | Authorization Code and restricted keys may hold chain grants. A chain-wide marketplace installation is not part of the first iteration, so Client Credentials cannot receive them. |\n| `locations:create` | Authorization Code only. Creating a Location is account-level; Client Credentials always act inside one existing Location, and restricted keys cannot create new Locations. |\n| `team_members:manage_access` | Authorization Code only in the first iteration. Client Credentials and restricted keys cannot grant, revoke, invite, or change ERP access. |\n\nScopes are added to a flow or credential class, never removed: adding one later is\nbackward-compatible, so the published lists start conservative.\n\n## Authorization Code + PKCE (user-delegated)\n\nFor applications and agent hosts acting on behalf of a specific Business User:\n\n1. Redirect the user's browser to `GET /oauth/authorize` with `client_id`, `redirect_uri`,\n   `scope`, `state`, and a PKCE `code_challenge` (S256).\n2. The user logs in with their Altegio account.\n3. A **consent screen** lists the requested scopes; the user grants or declines access.\n4. The browser is redirected back to your `redirect_uri` with a short-lived, one-time\n   authorization `code` (and your `state`).\n5. Exchange the code at `POST /oauth/token` with `grant_type=authorization_code` and the\n   PKCE `code_verifier` — the response contains an access token and a **rotating refresh\n   token**.\n6. Refresh with `grant_type=refresh_token`. Every use rotates the refresh token; replaying\n   an already-used refresh token revokes the whole token family.\n\n`GET /oauth/authorize` is a **browser web flow** (HTML login and consent pages), not a JSON\noperation — it is therefore documented here in prose only. The delegated token acts with\nthe permissions of the user who granted it.\n\nOAuth client registration: official and known integrations are pre-registered as OAuth\nclients. MCP hosts and other OAuth clients can self-onboard through Dynamic Client\nRegistration (`POST /oauth/register`); a `client_id` given as an HTTPS URL pointing to a\nclient metadata document (CIMD) is also accepted. Registration creates an **OAuth client\nidentity only** — it does not create a marketplace application, an installation, a business\ngrant, or any access to data; those still come from an install or from user consent.\n\n**`client_id` means two different things depending on the surface** — the word `client` is\noverloaded, so this contract keeps them apart:\n\n| Surface | Entity | Identifier |\n|---|---|---|\n| Resource API (`/clients`) | the person the business serves | `client_id` of a `/clients` resource |\n| OAuth (`/oauth/*`, `/.well-known/*`) | OAuth client registration | OAuth `client_id`, e.g. `altg_oauth_9c41` or a CIMD URL |\n| Marketplace / Developer Portal | the developer's application | application id — never sent on the wire in V3 |\n\nOAuth `client_id` is the standard field name from RFC 6749 and is not renamed; prose in the\nauthorization sections always says **OAuth client** when it means the protocol entity.\n\n## Restricted key (single-business integrations)\n\nFor a business integrating its own scripts, services, or an owned MCP server:\n\n1. A business administrator creates a **restricted key** in the Altegio cabinet, choosing\n   its scopes and expiry. The key value is shown exactly once.\n2. The key itself is the Bearer credential — there is **no token exchange**:\n   `Authorization: Bearer <restricted_key>`.\n3. Restricted keys use the same public scope catalog, minus credential-class exclusions\n   such as `team_members:manage_access` in the first iteration, and can be revoked in the\n   cabinet at any time.\n\n# Access model\n\nEvery request is resolved through the same runtime formula:\n\n```\neffective access = token scopes\n                 ∩ scopes granted to the installation / key\n                 ∩ business permissions of the token subject\n                 ∩ tenant (Location)\n```\n\n- A **scope opens an endpoint**; the subject's business permissions then decide which rows and\n  fields are visible or writable. Having a scope does not bypass the permission system of\n  the business.\n- **Location binding:** resources live under `/api/v3/locations/{location_id}/...`; the\n  `location_id` in the route is checked against the credential. A foreign Location returns\n  `404` — existence is not leaked.\n- **Scopes are credential-class bounded.** A credential can use only scopes allowed for its\n  class. An installation (machine-to-machine) token acts with the permissions granted to the\n  installation; a user-delegated token acts with the permissions of that specific Business\n  User; a restricted key acts with the scopes and resource grants set on the key.\n- **No step-up in this preview.** Dangerous operations in this preview are protected by\n  explicit scopes, the subject's business permissions, idempotency, audit, and rate limits.\n  A future human-confirmation flow will be added only with a complete challenge contract.\n\n# Conventions\n\n- **Resource shape:** a flat JSON object with integer `id`, a string `object` type marker,\n  and domain fields. No envelopes, no prefixes on ids.\n- **List shape:** `{\"object\": \"list\", \"data\": [...], \"has_more\": false, \"next_cursor\": null}`.\n  Pagination is cursor-based: pass `limit` and the `next_cursor` value from the previous\n  page as `cursor`. Cursors are opaque, expire, and only work for the credential and filter\n  set that produced them. Page/offset pagination does not exist in V3.\n- **Field naming:** all JSON fields, query parameters, and path segments are `snake_case`;\n  `operationId` values are `lowerCamelCase`.\n- **Dates and times:** RFC 3339 everywhere.\n  - Audit instants (`created_at`, `updated_at`, `cancelled_at`) are UTC: `2026-07-14T09:20:31Z`.\n  - Appointment times (`starts_at`, `ends_at`) carry the Location's local UTC offset:\n    `2026-07-09T11:30:00+01:00`.\n  - Date-only values are `YYYY-MM-DD`; wall-clock times (working hours, schedule slots)\n    are `HH:MM` in the Location's timezone.\n  - Naive datetimes and Unix timestamps never appear.\n- **Money:** integer **minor units** of the Location currency (`1250` = one thousand two hundred fifty minor units). The\n  `currency` (ISO 4217) is server-controlled, read-only, and repeated in money-carrying\n  resources. Monetary field names use the `_minor` suffix. Floating-point amounts are rejected.\n- **Partial updates:** `PATCH` bodies are `application/merge-patch+json`. A missing field\n  means \"leave unchanged\"; `null` clears a nullable field; arrays are cleared with an\n  explicit `[]`, never `null`. Clearing a non-nullable field fails with `422\n  field_not_nullable`; writing a read-only field fails with `422 field_immutable`. Unknown\n  request fields are rejected; unknown response fields must be ignored by clients.\n- **`expand[]`:** related objects are returned as ids by default. Documented relations can\n  be expanded one level deep with `expand[]=<relation>` (allowlist per endpoint, depth 1).\n  An unknown expand value fails with `422`. Expansion never bypasses field-level\n  permissions — see PII below.\n\n# Errors\n\nEvery error from a resource operation is `application/problem+json` (RFC 9457 hybrid):\n\n| Field | Meaning |\n|---|---|\n| `type` | Stable URL identifying the problem type (links to documentation). |\n| `title` | Human-readable summary of the problem type. |\n| `status` | HTTP status code. |\n| `detail` | Human-readable explanation of this occurrence. |\n| `code` | **Stable machine-readable code** — branch on this, never on text. |\n| `errors[]` | Field-level issues: `code`, `source`, `pointer`, `message`. `pointer` is a JSON Pointer for body errors and a parameter name for query/path/header errors. |\n| `instance` | Request identifier for support and log correlation. Same value as the `X-Request-Id` header of that response, so quoting either one is enough. |\n\nThe OAuth protocol and discovery endpoints are the one exception: `POST /oauth/token`,\n`POST /oauth/revoke`, `POST /oauth/register` and `/.well-known/*` return the standard OAuth 2.0\nerror object (`{\"error\": \"…\", \"error_description\": \"…\"}`) defined by RFC 6749, 7009 and 7591 —\nnot `application/problem+json`. The `GET /oauth/authorize` browser flow reports errors on its\nown HTML page or as OAuth error parameters on the redirect, never as JSON. `GET\n/oauth/accessible-resources` is the opposite case: despite the path it is an ordinary\nBearer-authed read and uses `application/problem+json` like the rest of the API.\n\nEverything else in this section (stable codes, field-level `errors[]`, request correlation)\ndescribes resource operations. Rate limiting is the one rule both surfaces share: every `429`\ncarries `Retry-After`.\n\nTwo conventions keep this table stable:\n\n- **Promotion.** Only public machine-readable codes that clients are expected to branch on\n  enter this table. Adding a code means editing this table and registering it in the biz.erp\n  `V3ProblemCatalog` in the same cycle — the CI conformance gate keeps both sides in sync.\n  Internal exceptions are never promoted implicitly: an unregistered code degrades to the\n  generic status default and nothing internal leaks.\n- **Naming.** `invalid_*` means the supplied value does not exist or fails validation\n  (`invalid_token`, `invalid_city`, `invalid_business_type`); `unsupported_*` means the value\n  is valid but not supported by the product or policy (`unsupported_country`,\n  `unsupported_media_type`, `unsupported_token_subject`).\n\nStable codes used by this preview (branch on these; domain codes below are equally stable):\n\n| Code | HTTP | When |\n|---|---:|---|\n| `bad_request` | 400 | Generic client reject without a more specific registered code (status default for bare 400 and unknown 4xx). Does **not** mean broken JSON. |\n| `malformed_request` | 400 | Protocol-only: broken JSON / unparseable body / wire-format syntax. Thrown explicitly — never the catch-all for every 400. |\n| `invalid_token` | 401 | Missing, expired, or revoked token. |\n| `missing_scope` | 403 | The token does not carry the required scope. |\n| `permission_denied` | 403 | Scope present, but the subject lacks the business permission. |\n| `user_not_verified` | 403 | Location create blocked: the user account is not verified. |\n| `location_creation_forbidden` | 403 | Location create is not allowed for this subject. |\n| `unsupported_token_subject` | 403 | This credential class cannot perform the operation (e.g. installation token on `locations:create`). |\n| `resource_not_found` | 404 | Generic 404 fallback: unknown `/api/v3/*` path, gate/FF hide, or tenant policy without a safe resource-specific code. |\n| `{resource}_not_found` | 404 | Resource absent or hidden by tenant policy (e.g. `appointment_not_found`, `location_not_found`). |\n| `method_not_allowed` | 405 | HTTP method is not supported on an existing v3 route. Emitted by routing before any operation is selected, so it is deliberately not declared per operation; the body is the same `Problem` shape as every other error. The response carries `Allow` with the methods the route does accept, e.g. `Allow: GET`. |\n| `not_acceptable` | 406 | `Accept` cannot be satisfied. |\n| `conflict` | 409 | Generic 409 fallback when the domain exception has no more specific registered code. |\n| `slot_taken` | 409 | Appointment create lost the race for the requested time slot. |\n| `idempotency_key_reused` | 409 | Same `Idempotency-Key` with a different request body. |\n| `idempotency_request_in_progress` | 409 | Same key while the first request is still running (`Retry-After` included). |\n| `duplicate_location` | 409 | Location create conflicts with an existing Location. |\n| `last_location_owner` | 409 | Cannot revoke the last owner of the Location. |\n| `invitation_already_pending` | 409 | A distinct pending invitation already exists. |\n| `access_already_active` | 409 | Team Member already has active system access. |\n| `visit_already_settled` | 409 | Visit is fully paid; adding a line would leave an unpaid remainder. |\n| `stale_version` | 412 | `If-Match` did not match — the resource was changed by another writer. |\n| `unsupported_media_type` | 415 | Request `Content-Type` is not supported. |\n| `validation_failed` | 422 | Semantic field validation failed (details in `errors[]`). |\n| `phone_taken` | 422 | Client phone already belongs to another client of the Location (under `clients:write`; audited and rate-limited). |\n| `exceeds_unpaid` | 422 | Payment amount exceeds the visit's unpaid remainder. |\n| `immutable_after_finance` | 422 | Country/currency cannot change after financial data exists. |\n| `field_not_nullable` | 422 | Merge-patch tried to `null` a non-nullable field. |\n| `field_immutable` | 422 | Merge-patch tried to change a read-only field. |\n| `role_not_assignable` | 422 | Requested access role is not assignable in this Location. |\n| `invitation_contact_required` | 422 | Invitation delivery needs a contact and none is available. |\n| `invalid_city` | 422 | `city_id` is invalid for location create. |\n| `unsupported_country` | 422 | Country derived from city is not supported. |\n| `invalid_business_type` | 422 | `business_type_id` is invalid. |\n| `location_quota_exceeded` | 422 | Account location quota exceeded. |\n| `instrument_not_applicable` | 422 | Gift card / membership / loyalty instrument is not usable for this visit (unified; does not distinguish unknown vs wrong tenant). |\n| `precondition_required` | 428 | Required `If-Match` header is missing. |\n| `rate_limit_exceeded` | 429 | Rate limit hit (`Retry-After` included). |\n| `location_creation_rate_limited` | 429 | Location create rate limit hit. |\n| `internal_error` | 500 | Unexpected server error. |\n| `dependency_unavailable` | 503 | A required dependency is down; the write was not performed (fail closed). |\n\nField-level codes inside `errors[]` (not top-level `code`) include `expired_cursor`,\n`invalid_cursor`, `foreign_cursor`, and validation codes such as `invalid_datetime`.\n\nExample — validation failure (`422`):\n\n```json\n{\n  \"type\": \"https://developer.alteg.io/problems/validation-error\",\n  \"title\": \"Request validation failed\",\n  \"status\": 422,\n  \"detail\": \"One or more fields are invalid.\",\n  \"instance\": \"urn:altegio:request:req_abc123\",\n  \"code\": \"validation_failed\",\n  \"errors\": [\n    {\n      \"code\": \"invalid_datetime\",\n      \"source\": \"body\",\n      \"pointer\": \"/starts_at\",\n      \"message\": \"starts_at must be a valid RFC 3339 datetime.\"\n    }\n  ]\n}\n```\n\nExample — stale optimistic-concurrency version (`412`):\n\n```json\n{\n  \"type\": \"https://developer.alteg.io/problems/stale-version\",\n  \"title\": \"Resource version is stale\",\n  \"status\": 412,\n  \"detail\": \"The resource was modified after the version supplied in If-Match. Re-read it and retry.\",\n  \"instance\": \"urn:altegio:request:req_def456\",\n  \"code\": \"stale_version\"\n}\n```\n\nRetry guidance depends on the code, not on a generic flag: fix the request on `422`;\nre-read the resource on `412`; retry the same `Idempotency-Key` after `Retry-After` on\n`409 idempotency_request_in_progress`; back off on `429`/`503`; after a network timeout on\na create or command, retry **only with the same `Idempotency-Key`**.\n\n# Idempotency & concurrency\n\n**`Idempotency-Key`** is required on every `POST` create and command where a retry could\nduplicate a side effect (creating appointments, clients, payments; cancel/status commands;\nrefunds).\n\n- Generate a unique key per logical request (UUID recommended).\n- A retry with the same key and the same body returns the **original result** — including\n  the case where the first attempt committed but its response was lost.\n- The same key with a different body fails with `409 idempotency_key_reused`.\n- A concurrent duplicate fails with `409 idempotency_request_in_progress` and `Retry-After`.\n- Keys are retained for **at least 24 hours**, scoped to the credential, Location (when\n  present), and operation. After expiry the same key value may start a **new** logical\n  request (it is not `idempotency_key_reused`).\n- If the idempotency store is unavailable, the write is not performed: `503\n  dependency_unavailable`.\n\n**ETag / `If-Match`** protects mutable resources from two writers silently overwriting each\nother (for example, a human and an agent editing the same appointment):\n\n1. `GET` a mutable resource — the response carries an `ETag` header (creates and updates\n   return the fresh `ETag` too).\n2. Send `PATCH` or `DELETE` with `If-Match: <etag>`.\n3. On success the response carries the new `ETag`. If the resource changed in between, the\n   call fails with `412 stale_version` — re-read, reapply your change, retry. A missing\n   `If-Match` where it is required fails with `428 precondition_required`.\n\n# PII & redaction\n\nClient contact fields — `phone`, `email`, `additional_phone` — are sensitive:\n\n- Reading them requires the `clients:read_contact` scope **and** the corresponding business\n  permission of the token subject.\n- When access is refused, a contact field is returned as `null` and its name is listed in `redacted_fields` of the same object. It is never replaced with a mask, a placeholder or an empty string, so an integration cannot write a redacted value back.\n- `expand[]=client` follows the same rule: expanding a client on an appointment or visit\n  without contact access returns the client with redacted contact fields. Expansion never\n  widens access.\n- **`422 phone_taken` on create/update** is part of `clients:write` (no extra\n  `clients:read_contact`). It only signals that the phone is already owned by another\n  client of this Location — it does not return that client's identity or contact payload.\n  Phone-bearing writes are audited and share an anti-enumeration rate limit with exact\n  contact search so the existence signal cannot be bulk-probed.\n\n# HTTP negotiation and caching\n\nClients should send `Accept: application/json` for successful resource responses and `application/problem+json` for errors; unsupported response media types fail with `406 not_acceptable`. JSON is UTF-8; `Content-Type` may include `charset=utf-8`. Read operations may include `Cache-Control` appropriate to the resource (`no-store` for PII-bearing reads, short `private` or `public` max-age for dictionaries and metadata). Mutable resource reads return strong ETags suitable for `If-Match`; computed/list/read-model responses may use weak ETags only for cache validation and not for write preconditions. `If-Match: *` is not accepted on PATCH/DELETE in V3 preview; clients must re-read and send the concrete strong ETag.\n"
  },
  "x-altegio-status": "preview",
  "servers": [
    {
      "url": "https://api.alteg.io/api/v3",
      "description": "Preview resource API contract - endpoint surface is not live yet"
    }
  ],
  "tags": [
    {
      "name": "Authorization",
      "description": "Step 0 of every integration: obtain a Bearer credential. Machine-to-machine\nintegrations exchange client credentials for an installation token, user-facing\napplications walk the Authorization Code + PKCE browser flow, and single-business\nscripts use a restricted key created in the cabinet. These endpoints live outside\n`/api/v3` on the authorization server host.\n"
    },
    {
      "name": "Locations",
      "description": "The Location is the root of the V3 resource tree. Read it to learn derived timezone, country, currency, locale, and business settings; update only the partner-relevant allowlist."
    },
    {
      "name": "Services",
      "description": "The service catalog of a Location: services and their categories. Services define what\ncan be scheduled, for how long, and at what price; per-team-member overrides link the\ncatalog to the people who deliver it. A service must be linked to at least one bookable\nTeam Member to appear in availability.\n"
    },
    {
      "name": "Team Members",
      "description": "Team Members deliver services and own the calendars that appointments are scheduled into.\nThis section covers the Team Member directory and each member's working schedule — the\nconfigured working days that availability search is computed from.\n"
    },
    {
      "name": "Products",
      "description": "Minimal product catalog read: enough to put a product line on a visit (`id`, title, price, unit). Inventory levels, categories, cost price and product management are outside the first iteration."
    },
    {
      "name": "Positions",
      "description": "Positions are the job-title directory of the Team Member domain (for example\n\"Barber\" or \"Senior stylist\"). Create positions first, then reference them from Team\nMembers via `position_id`.\n"
    },
    {
      "name": "Clients",
      "description": "The client base of a Location. Appointments can reference an existing client, resolve an\ninline client attach-only, or be clientless. Contact fields are PII and require the\n`clients:read_contact` scope on top of `clients:read`.\n"
    },
    {
      "name": "Availability",
      "description": "Computed free appointment slots. Availability is derived from Team Member\nschedules, existing appointments, and service durations — query it right before\ncreating an appointment to offer valid start times.\n"
    },
    {
      "name": "Resources",
      "description": "Bookable resources of a Location (rooms, chairs, equipment) and their instances.\nLocations that require resources for certain services pass `resource_instance_ids`\nwhen creating appointments. Read-only in the preview scope.\n"
    },
    {
      "name": "Appointments",
      "description": "The appointment workflow: create, list, read, reschedule, cancel, and change appointment status. The appointment id is also the public key for visit/payment operations because appointments without a client can still be paid."
    },
    {
      "name": "Visits",
      "description": "A visit groups appointment billing state: line items, applicable payment methods, payments from money and loyalty sources, and the remaining `amount_to_pay_minor`."
    },
    {
      "name": "Payments",
      "description": "Money and loyalty flow for an appointment's visit: discover payment methods and accounts, search loyalty instruments, record payments, and locally reverse a payment."
    }
  ],
  "x-tagGroups": [
    {
      "name": "Authorization",
      "tags": [
        "Authorization"
      ]
    },
    {
      "name": "Location Setup",
      "tags": [
        "Locations",
        "Services",
        "Products",
        "Team Members",
        "Positions"
      ]
    },
    {
      "name": "Clients",
      "tags": [
        "Clients"
      ]
    },
    {
      "name": "Scheduling",
      "tags": [
        "Availability",
        "Resources",
        "Appointments"
      ]
    },
    {
      "name": "Visits & Payments",
      "tags": [
        "Visits",
        "Payments"
      ]
    }
  ],
  "paths": {
    "/.well-known/oauth-authorization-server": {
      "servers": [
        {
          "url": "https://api.alteg.io",
          "description": "Preview authorization server - endpoint surface is not live yet"
        }
      ],
      "get": {
        "operationId": "getOAuthAuthorizationServerMetadata",
        "summary": "Get authorization server metadata",
        "description": "Machine-readable OAuth 2.0 authorization server metadata (RFC 8414): endpoint URLs,\nsupported grant types, PKCE methods, and scopes. Agents and MCP hosts use this\ndocument to discover the authorization and token endpoints automatically.\n",
        "tags": [
          "Authorization"
        ],
        "security": [],
        "x-altegio-status": "preview",
        "responses": {
          "200": {
            "description": "Authorization server metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthAuthorizationServerMetadata"
                },
                "example": {
                  "issuer": "https://api.alteg.io",
                  "authorization_endpoint": "https://api.alteg.io/oauth/authorize",
                  "token_endpoint": "https://api.alteg.io/oauth/token",
                  "revocation_endpoint": "https://api.alteg.io/oauth/revoke",
                  "registration_endpoint": "https://api.alteg.io/oauth/register",
                  "response_types_supported": [
                    "code"
                  ],
                  "grant_types_supported": [
                    "authorization_code",
                    "client_credentials",
                    "refresh_token"
                  ],
                  "code_challenge_methods_supported": [
                    "S256"
                  ],
                  "token_endpoint_auth_methods_supported": [
                    "client_secret_basic",
                    "none"
                  ],
                  "scopes_supported": [
                    "locations:read",
                    "locations:write",
                    "locations:create",
                    "services:read",
                    "services:write",
                    "products:read",
                    "team_members:read",
                    "team_members:write",
                    "team_members:manage_access",
                    "clients:read",
                    "clients:read_contact",
                    "clients:write",
                    "availability:read",
                    "appointments:read",
                    "appointments:create",
                    "appointments:write",
                    "visits:read",
                    "visits:write",
                    "payments:read",
                    "payments:capture",
                    "payments:refund",
                    "finance:read",
                    "loyalty:read",
                    "chain_services:read",
                    "chain_services:write",
                    "chain_clients:read",
                    "chain_loyalty:read"
                  ]
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Cache-Control": {
                "description": "Public metadata may be cached briefly; clients should revalidate before long-lived use.",
                "schema": {
                  "type": "string"
                },
                "example": "public, max-age=3600"
              }
            }
          },
          "400": {
            "description": "Invalid metadata request (OAuth error object).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "404": {
            "description": "Metadata document is not available."
          },
          "429": {
            "description": "Too many metadata requests from this address. Discovery is cacheable — honour\n`Cache-Control` instead of polling. Retry after the interval in `Retry-After`.\n",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                },
                "example": 60
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                },
                "example": {
                  "error": "temporarily_unavailable",
                  "error_description": "Metadata request rate limit exceeded. Retry later."
                }
              }
            }
          },
          "500": {
            "description": "Metadata document cannot be generated (OAuth error object).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "503": {
            "description": "Authorization metadata dependency is unavailable (OAuth error object).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/token": {
      "servers": [
        {
          "url": "https://api.alteg.io",
          "description": "Preview authorization server - endpoint surface is not live yet"
        }
      ],
      "post": {
        "operationId": "createOAuthToken",
        "summary": "Issue an access token",
        "description": "OAuth 2.0 token endpoint (RFC 6749). Supports three grants:\n\n- `authorization_code` — exchange a code obtained from the browser\n  `GET /oauth/authorize` flow (PKCE `code_verifier` required). Returns an access\n  token and a rotating refresh token.\n- `client_credentials` — machine-to-machine token for a marketplace installation.\n  Always requires the explicit `location_id`; the installation is resolved from the\n  authenticated OAuth client plus that Location. No refresh token is returned.\n- `refresh_token` — rotate a refresh token from the Authorization Code flow.\n  The previous refresh token is invalidated; replaying it revokes the token family.\n\nConfidential clients authenticate with HTTP Basic (`client_secret_basic`); public\nclients (PKCE-only) send their `client_id` in the form body. There is **no password\ngrant**.\n\nErrors from this endpoint use the standard OAuth 2.0 error object\n(`{\"error\": \"...\", \"error_description\": \"...\"}`), not `application/problem+json`.\n",
        "tags": [
          "Authorization"
        ],
        "security": [],
        "x-altegio-status": "preview",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/OAuthTokenRequest"
              },
              "examples": {
                "clientCredentials": {
                  "summary": "Marketplace M2M token for one Location",
                  "value": {
                    "grant_type": "client_credentials",
                    "location_id": 90211,
                    "scope": "appointments:read appointments:create clients:read"
                  }
                },
                "authorizationCode": {
                  "summary": "Exchange an authorization code (PKCE)",
                  "value": {
                    "grant_type": "authorization_code",
                    "code": "SplxlOBeZQQYbYS6WxSbIA",
                    "redirect_uri": "https://partner.example.com/oauth/callback",
                    "client_id": "altg_oauth_2f6d",
                    "code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token issued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthTokenResponse"
                },
                "example": {
                  "access_token": "altg_at_7f3a1c9e0b2d4f68a1c9e0b2",
                  "token_type": "Bearer",
                  "expires_in": 900,
                  "refresh_token": "altg_rt_5b8e2d4f68a1c9e0b2d4f68a",
                  "scope": "appointments:read appointments:create clients:read"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or grant (RFC 6749 error object).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                },
                "example": {
                  "error": "invalid_grant",
                  "error_description": "Authorization code is expired or already used."
                }
              }
            }
          },
          "401": {
            "description": "Client authentication failed (RFC 6749 error object).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                },
                "example": {
                  "error": "invalid_client",
                  "error_description": "Unknown client or invalid client credentials."
                }
              }
            }
          },
          "429": {
            "description": "Too many token requests for this client, subject or address. Failed grant attempts\ncount against the same budget. Retry after the interval in `Retry-After`; do not\nretry a rejected `authorization_code` — start a new authorization.\n",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                },
                "example": 60
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                },
                "example": {
                  "error": "temporarily_unavailable",
                  "error_description": "Token request rate limit exceeded. Retry later."
                }
              }
            }
          },
          "500": {
            "description": "Token could not be issued because of an unexpected server error (OAuth error object).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                },
                "example": {
                  "error": "server_error",
                  "error_description": "Token could not be issued. Retry later."
                }
              }
            }
          },
          "503": {
            "description": "An authorization dependency is unavailable (OAuth error object). Back off and retry;\nalready-issued access tokens keep working until they expire.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                },
                "example": {
                  "error": "temporarily_unavailable",
                  "error_description": "Authorization server dependency is unavailable. Retry later."
                }
              }
            }
          }
        }
      }
    },
    "/oauth/revoke": {
      "servers": [
        {
          "url": "https://api.alteg.io",
          "description": "Preview authorization server - endpoint surface is not live yet"
        }
      ],
      "post": {
        "operationId": "revokeOAuthToken",
        "summary": "Revoke a token",
        "description": "OAuth 2.0 token revocation (RFC 7009). Revokes an access token or a refresh token;\nrevoking a refresh token revokes its whole token family. The endpoint returns `200`\neven when the token is already invalid, as required by the RFC.\n",
        "tags": [
          "Authorization"
        ],
        "security": [],
        "x-altegio-status": "preview",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/OAuthRevokeRequest"
              },
              "example": {
                "token": "altg_rt_5b8e2d4f68a1c9e0b2d4f68a",
                "token_type_hint": "refresh_token"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The token has been revoked (or was already invalid)."
          },
          "401": {
            "description": "Client authentication failed (RFC 6749 error object).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                },
                "example": {
                  "error": "invalid_client",
                  "error_description": "Unknown client or invalid client credentials."
                }
              }
            }
          },
          "429": {
            "description": "Too many revocation requests for this client or address. Retry after the interval in\n`Retry-After`; a `429` does not mean the token survived — re-send the same request.\n",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                },
                "example": 60
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                },
                "example": {
                  "error": "temporarily_unavailable",
                  "error_description": "Revocation rate limit exceeded. Retry later."
                }
              }
            }
          },
          "500": {
            "description": "Revocation could not be completed because of an unexpected server error (OAuth error\nobject). The token may still be valid — retry until the endpoint returns `200`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                },
                "example": {
                  "error": "server_error",
                  "error_description": "Revocation could not be completed. Retry later."
                }
              }
            }
          },
          "503": {
            "description": "A revocation dependency is unavailable (OAuth error object). The token may still be\nvalid — back off and retry until the endpoint returns `200`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                },
                "example": {
                  "error": "temporarily_unavailable",
                  "error_description": "Revocation dependency is unavailable. Retry later."
                }
              }
            }
          }
        }
      }
    },
    "/oauth/register": {
      "servers": [
        {
          "url": "https://api.alteg.io",
          "description": "Preview authorization server - endpoint surface is not live yet"
        }
      ],
      "post": {
        "operationId": "registerOAuthClient",
        "summary": "Register an OAuth client (DCR)",
        "description": "Minimal Dynamic Client Registration (RFC 7591) for MCP hosts and other clients that\nare not pre-registered. Registration only issues a `client_id` — access to any data\nstill requires the user login/consent flow and granted scopes.\n\nPublic clients (native apps, local MCP packages, agent hosts without a secure\nbackend) receive **no `client_secret`** and must use Authorization Code + PKCE.\nA `client_id` supplied as an HTTPS URL pointing to a Client ID Metadata Document\n(CIMD) is also accepted at the authorization endpoint without prior registration.\n\nRegistration is rate-limited and monitored for abuse; redirect URIs are validated.\n",
        "tags": [
          "Authorization"
        ],
        "security": [],
        "x-altegio-status": "preview",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OAuthClientRegistrationRequest"
              },
              "example": {
                "client_name": "Example MCP Host",
                "redirect_uris": [
                  "https://mcp-host.example.com/oauth/callback",
                  "http://127.0.0.1/callback"
                ],
                "grant_types": [
                  "authorization_code",
                  "refresh_token"
                ],
                "token_endpoint_auth_method": "none"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Client registered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthClientRegistrationResponse"
                },
                "example": {
                  "client_id": "altg_oauth_9c41",
                  "client_id_issued_at": 1784900000,
                  "client_name": "Example MCP Host",
                  "redirect_uris": [
                    "https://mcp-host.example.com/oauth/callback",
                    "http://127.0.0.1/callback"
                  ],
                  "grant_types": [
                    "authorization_code",
                    "refresh_token"
                  ],
                  "token_endpoint_auth_method": "none"
                }
              }
            }
          },
          "400": {
            "description": "Invalid client metadata (RFC 7591 error object).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                },
                "example": {
                  "error": "invalid_redirect_uri",
                  "error_description": "redirect_uris must be absolute HTTPS URLs or RFC 8252 loopback addresses."
                }
              }
            }
          },
          "429": {
            "description": "Too many registration attempts from this client or address. Anonymous registration is\nrate-limited per address and per registered client, and repeated invalid metadata counts\nagainst the same budget. Retry after the interval in `Retry-After`.\n",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                },
                "example": 60
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                },
                "example": {
                  "error": "temporarily_unavailable",
                  "error_description": "Registration rate limit exceeded. Retry later."
                }
              }
            }
          },
          "503": {
            "description": "Dynamic registration is not available. Returned when anonymous registration is switched\noff for the environment; pre-registered clients and the CIMD `client_id` URL form keep\nworking.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                },
                "example": {
                  "error": "temporarily_unavailable",
                  "error_description": "Dynamic client registration is disabled."
                }
              }
            }
          }
        }
      }
    },
    "/oauth/accessible-resources": {
      "servers": [
        {
          "url": "https://api.alteg.io",
          "description": "Preview authorization server - endpoint surface is not live yet"
        }
      ],
      "get": {
        "operationId": "listAccessibleResources",
        "summary": "List the resources the current token can access",
        "description": "Returns the resources **this token** is actually granted — not everything the user or\napplication could reach. Each entry carries the scopes granted on that resource, so a\nclient knows which `location_id` to use in a path and which chain-level calls it may make.\n\nA consent may grant several resources at once; each becomes an independent entry that can be\nrevoked on its own. A `chain` entry does not imply its locations: chain grants and\nlocation grants are separate, and a new location added to a chain later is not covered\nautomatically.",
        "tags": [
          "Authorization"
        ],
        "security": [
          {
            "OAuth2": []
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "responses": {
          "200": {
            "description": "Resources accessible with the current token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccessibleResourceList"
                },
                "examples": {
                  "oneChainTwoLocations": {
                    "summary": "A chain grant plus two location grants",
                    "value": {
                      "object": "list",
                      "data": [
                        {
                          "object": "accessible_resource",
                          "type": "chain",
                          "id": 42,
                          "parent_chain_id": null,
                          "scopes": [
                            "chain_services:read"
                          ]
                        },
                        {
                          "object": "accessible_resource",
                          "type": "location",
                          "id": 10,
                          "parent_chain_id": 42,
                          "scopes": [
                            "appointments:read",
                            "appointments:create",
                            "visits:read"
                          ]
                        },
                        {
                          "object": "accessible_resource",
                          "type": "location",
                          "id": 11,
                          "parent_chain_id": 42,
                          "scopes": [
                            "appointments:read"
                          ]
                        }
                      ]
                    }
                  }
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Cache-Control": {
                "description": "Grants change independently of the token; do not cache beyond the token lifetime.",
                "schema": {
                  "type": "string"
                },
                "example": "no-store"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/reference/cities": {
      "get": {
        "operationId": "listCities",
        "summary": "List cities",
        "description": "Cursor-paginated list. Default ordering: country_id, name, id tie-breaker. Cursors use the `cursor` query parameter, expire after 15 minutes, and are bound to the credential, tenant, and filters. Expired, malformed, or foreign cursors fail with `422 validation_failed` using field codes `expired_cursor`, `invalid_cursor`, or `foreign_cursor`.",
        "tags": [
          "Locations"
        ],
        "security": [
          {
            "OAuth2": []
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/CountryId"
          },
          {
            "$ref": "#/components/parameters/Query"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CityList"
                }
              }
            },
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/reference/business_types": {
      "get": {
        "operationId": "listBusinessTypes",
        "summary": "List business types",
        "description": "Cursor-paginated list. Default ordering: title, id tie-breaker. Cursors use the `cursor` query parameter, expire after 15 minutes, and are bound to the credential, tenant, and filters. Expired, malformed, or foreign cursors fail with `422 validation_failed` using field codes `expired_cursor`, `invalid_cursor`, or `foreign_cursor`.",
        "tags": [
          "Locations"
        ],
        "security": [
          {
            "OAuth2": []
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BusinessTypeList"
                }
              }
            },
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations": {
      "post": {
        "operationId": "createLocation",
        "summary": "Create a location",
        "description": "Creates a standalone Location for the current authenticated user.\n\nAuthorization is user-delegated only (Authorization Code + PKCE); installation\n(client credentials) tokens and restricted keys are rejected. `country_code`,\n`timezone`, and `currency` are derived from `city_id` and are not writable.\n\nThe `201` response carries `provisioning_status`: `ready` means base accounts and\npayment methods already exist, `pending` means defaults are still being provisioned.\nRequires `Idempotency-Key`.\n\nErrors: `403 user_not_verified` / `location_creation_forbidden` /\n`unsupported_token_subject`; `409 duplicate_location`; `422 invalid_city` /\n`unsupported_country` / `invalid_business_type` / `location_quota_exceeded`;\n`429 location_creation_rate_limited`.\n",
        "tags": [
          "Locations"
        ],
        "security": [
          {
            "OAuth2": [
              "locations:create"
            ]
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LocationCreate"
              },
              "example": {
                "name": "Lisbon Center",
                "city_id": 6201,
                "business_type_id": 1,
                "language": "en-PT",
                "datetime_format": "DD.MM.YYYY HH:mm"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LocationCreated"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        }
      ],
      "get": {
        "operationId": "getLocation",
        "summary": "Get a location",
        "description": "Returns the Location resource: name, address, contacts, timezone, and\ncurrency. The response carries an `ETag` header for a subsequent `PATCH`.\n",
        "tags": [
          "Locations"
        ],
        "security": [
          {
            "OAuth2": [
              "locations:read"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "responses": {
          "200": {
            "description": "The Location.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Location"
                },
                "examples": {
                  "location": {
                    "$ref": "#/components/examples/LocationExample"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      },
      "patch": {
        "operationId": "updateLocation",
        "summary": "Update a location",
        "description": "Partially updates partner-relevant Location settings with a merge-patch body.\nRequires `If-Match` with the `ETag` from a previous read.\n\n`timezone`, `country`, and `currency` are read-only and intentionally absent from the\nupdate schema — they follow `city_id`. Changing `city_id` to a city that would alter the\ncountry or currency is rejected with `422 immutable_after_finance` once financial data\nexists for the Location; before that the move is allowed and the derived values follow.\n",
        "tags": [
          "Locations"
        ],
        "security": [
          {
            "OAuth2": [
              "locations:write"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/merge-patch+json": {
              "schema": {
                "$ref": "#/components/schemas/LocationUpdate"
              },
              "example": {
                "name": "Lisbon Center",
                "phones": [
                  "+351210000000"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated Location.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Location"
                },
                "examples": {
                  "location": {
                    "$ref": "#/components/examples/LocationExample"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/services": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        }
      ],
      "get": {
        "operationId": "listServices",
        "summary": "List services",
        "description": "Returns the services of the Location as a cursor-paginated list. Prices are integer\nminor units; `duration_seconds` is the default duration, and `team_members[]` carries\nper-member price/duration overrides.\n\n\nCursor-paginated list. Default ordering: category_id, name, id tie-breaker. Cursors use the `cursor` query parameter, expire after 15 minutes, and are bound to the credential, tenant, and filters. Expired, malformed, or foreign cursors fail with `422 validation_failed` using field codes `expired_cursor`, `invalid_cursor`, or `foreign_cursor`.",
        "tags": [
          "Services"
        ],
        "security": [
          {
            "OAuth2": [
              "services:read"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of services.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceList"
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "id": 100,
                      "object": "service",
                      "type": "simple",
                      "name": "Men's haircut",
                      "category_id": 205,
                      "duration_seconds": 3600,
                      "currency": "EUR",
                      "is_bookable": true,
                      "team_members": [
                        {
                          "team_member_id": 321
                        },
                        {
                          "team_member_id": 322,
                          "duration_seconds": 2700,
                          "price_minor": 300000
                        }
                      ],
                      "created_at": "2026-07-01T08:00:00Z",
                      "updated_at": "2026-07-10T09:30:00Z",
                      "price_minor": 250000
                    },
                    {
                      "id": 200,
                      "object": "service",
                      "type": "package",
                      "package": {
                        "exec_order": "sequential",
                        "pricing_type": "sum",
                        "price_discount_percent": 10,
                        "components": [
                          {
                            "service_id": 1501,
                            "position": 1
                          },
                          {
                            "service_id": 1502,
                            "position": 2
                          }
                        ]
                      },
                      "name": "Spa day",
                      "category_id": 205,
                      "duration_seconds": 7200,
                      "currency": "EUR",
                      "is_bookable": true,
                      "team_members": [
                        {
                          "team_member_id": 321
                        }
                      ],
                      "created_at": "2026-07-01T08:00:00Z",
                      "updated_at": "2026-07-10T09:30:00Z",
                      "price_minor": 500000
                    }
                  ],
                  "has_more": false,
                  "next_cursor": null
                }
              }
            },
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      },
      "post": {
        "operationId": "createService",
        "summary": "Create a service",
        "description": "Creates a service in the Location catalog. Link Team Members inline through\n`team_members[]` — a service without at least one bookable Team Member link does not\nappear in availability search. Requires `Idempotency-Key`.\n\nService packages (complex services made of multiple simple services) are created\nthrough this same operation: send `type: package` with a `package` block listing the\ncomponents.\n",
        "tags": [
          "Services"
        ],
        "security": [
          {
            "OAuth2": [
              "services:write"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ServiceCreate"
              },
              "examples": {
                "simple": {
                  "summary": "simple",
                  "value": {
                    "name": "Men's haircut",
                    "category_id": 205,
                    "type": "simple",
                    "duration_seconds": 3600,
                    "team_members": [
                      {
                        "team_member_id": 321
                      },
                      {
                        "team_member_id": 322,
                        "duration_seconds": 2700,
                        "price_minor": 300000
                      }
                    ],
                    "price_minor": 250000
                  }
                },
                "package": {
                  "summary": "package",
                  "value": {
                    "name": "Spa day",
                    "category_id": 205,
                    "type": "package",
                    "duration_seconds": 7200,
                    "package": {
                      "exec_order": "sequential",
                      "pricing_type": "sum",
                      "price_discount_percent": 10,
                      "components": [
                        {
                          "service_id": 1501,
                          "position": 1
                        },
                        {
                          "service_id": 1502,
                          "position": 2
                        }
                      ]
                    },
                    "price_minor": 500000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created service.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Service"
                },
                "examples": {
                  "service": {
                    "$ref": "#/components/examples/ServiceExample"
                  },
                  "package": {
                    "$ref": "#/components/examples/ServicePackageExample"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/services/{service_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        },
        {
          "$ref": "#/components/parameters/ServiceId"
        }
      ],
      "get": {
        "operationId": "getService",
        "summary": "Get a service",
        "description": "Returns a single service with its category, pricing, duration, Team Member links, and\npackage components when `type` is `package`.\nThe response carries an `ETag` header for a subsequent `PATCH` or `DELETE`.\n",
        "tags": [
          "Services"
        ],
        "security": [
          {
            "OAuth2": [
              "services:read"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "responses": {
          "200": {
            "description": "The service.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Service"
                },
                "examples": {
                  "service": {
                    "$ref": "#/components/examples/ServiceExample"
                  },
                  "package": {
                    "$ref": "#/components/examples/ServicePackageExample"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      },
      "patch": {
        "operationId": "updateService",
        "summary": "Update a service",
        "description": "Partially updates a service with a merge-patch body. `team_members[]`, when present,\nreplaces the full set of Team Member links (use `[]` to unlink everyone). `type` is\nimmutable; for package services, `package.components` replaces the full component\nlist when present. Requires `If-Match`.\n",
        "tags": [
          "Services"
        ],
        "security": [
          {
            "OAuth2": [
              "services:write"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/merge-patch+json": {
              "schema": {
                "$ref": "#/components/schemas/ServiceUpdate"
              },
              "example": {
                "price_minor": 280000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated service.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Service"
                },
                "examples": {
                  "service": {
                    "$ref": "#/components/examples/ServiceExample"
                  },
                  "package": {
                    "$ref": "#/components/examples/ServicePackageExample"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      },
      "delete": {
        "operationId": "deleteService",
        "summary": "Delete a service",
        "description": "Deletes a service from the catalog. Requires `If-Match` with the version last seen.",
        "tags": [
          "Services"
        ],
        "security": [
          {
            "OAuth2": [
              "services:write"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "responses": {
          "204": {
            "description": "The service has been deleted.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/service_categories": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        }
      ],
      "get": {
        "operationId": "listServiceCategories",
        "summary": "List service categories",
        "description": "Returns the service categories of the Location. Use a category `id` as `category_id`\nwhen creating services.\n\n\nCursor-paginated list. Default ordering: weight desc, name, id tie-breaker. Cursors use the `cursor` query parameter, expire after 15 minutes, and are bound to the credential, tenant, and filters. Expired, malformed, or foreign cursors fail with `422 validation_failed` using field codes `expired_cursor`, `invalid_cursor`, or `foreign_cursor`.",
        "tags": [
          "Services"
        ],
        "security": [
          {
            "OAuth2": [
              "services:read"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of service categories.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceCategoryList"
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "id": 205,
                      "object": "service_category",
                      "name": "Haircuts",
                      "weight": 10
                    }
                  ],
                  "has_more": false,
                  "next_cursor": null
                }
              }
            },
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      },
      "post": {
        "operationId": "createServiceCategory",
        "summary": "Create a service category",
        "description": "Creates a service category. Requires `Idempotency-Key`. The `201` response carries an\n`ETag` header for subsequent updates.\n",
        "tags": [
          "Services"
        ],
        "security": [
          {
            "OAuth2": [
              "services:write"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ServiceCategoryCreate"
              },
              "example": {
                "name": "Haircuts",
                "weight": 10
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created service category.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceCategory"
                },
                "example": {
                  "id": 205,
                  "object": "service_category",
                  "name": "Haircuts",
                  "weight": 10
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/service_categories/{service_category_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        },
        {
          "$ref": "#/components/parameters/ServiceCategoryId"
        }
      ],
      "patch": {
        "operationId": "updateServiceCategory",
        "summary": "Update a service category",
        "description": "Partially updates a service category with a merge-patch body. Requires `If-Match`.",
        "tags": [
          "Services"
        ],
        "security": [
          {
            "OAuth2": [
              "services:write"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/merge-patch+json": {
              "schema": {
                "$ref": "#/components/schemas/ServiceCategoryUpdate"
              },
              "example": {
                "name": "Cuts & styling"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated service category.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceCategory"
                },
                "example": {
                  "id": 205,
                  "object": "service_category",
                  "name": "Cuts & styling",
                  "weight": 10
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      },
      "delete": {
        "operationId": "deleteServiceCategory",
        "summary": "Delete a service category",
        "description": "Deletes a service category. Requires `If-Match` with the version last seen.",
        "tags": [
          "Services"
        ],
        "security": [
          {
            "OAuth2": [
              "services:write"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "responses": {
          "204": {
            "description": "The service category has been deleted.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/team_members": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        }
      ],
      "get": {
        "operationId": "listTeamMembers",
        "summary": "List team members",
        "description": "Returns the Team Members of the Location as a cursor-paginated list. The base\nrepresentation contains name, position, and bookable services; Team Member personal\ncontact data is not exposed in the preview scope.\n\n\nCursor-paginated list. Default ordering: name, id tie-breaker. Cursors use the `cursor` query parameter, expire after 15 minutes, and are bound to the credential, tenant, and filters. Expired, malformed, or foreign cursors fail with `422 validation_failed` using field codes `expired_cursor`, `invalid_cursor`, or `foreign_cursor`.",
        "tags": [
          "Team Members"
        ],
        "security": [
          {
            "OAuth2": [
              "team_members:read"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of team members.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamMemberList"
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "id": 321,
                      "object": "team_member",
                      "name": "Aigerim Bekova",
                      "specialization": "Hairdresser",
                      "position_id": 15,
                      "service_ids": [
                        100
                      ],
                      "is_bookable": true,
                      "created_at": "2026-06-01T08:00:00Z",
                      "updated_at": "2026-07-01T08:00:00Z"
                    }
                  ],
                  "has_more": false,
                  "next_cursor": null
                }
              }
            },
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      },
      "post": {
        "operationId": "createTeamMember",
        "summary": "Create a team member",
        "description": "Creates a Team Member in the Location. After creating, set the working schedule\n(`PATCH .../schedule`) and link services — otherwise the member does not appear in\navailability search. Requires `Idempotency-Key`.\n\n`access_invitation` is an atomic create-and-invite convenience for user-delegated\nAuthorization Code credentials only: when present, the token must also carry\n`team_members:manage_access`. Client Credentials and restricted keys cannot use it in\nthe first iteration. If the extra scope or credential class is not allowed, the request\nfails before creating the Team Member with `403 missing_scope`.\n",
        "tags": [
          "Team Members"
        ],
        "security": [
          {
            "OAuth2": [
              "team_members:write"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TeamMemberCreate"
              },
              "example": {
                "name": "Aigerim Bekova",
                "specialization": "Hairdresser",
                "position_id": 15,
                "services": [
                  {
                    "service_id": 100
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created team member.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamMember"
                },
                "examples": {
                  "team_member": {
                    "$ref": "#/components/examples/TeamMemberExample"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/team_members/{team_member_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        },
        {
          "$ref": "#/components/parameters/TeamMemberId"
        }
      ],
      "get": {
        "operationId": "getTeamMember",
        "summary": "Get a team member",
        "description": "Returns a single Team Member. The response carries an `ETag` header for a subsequent\n`PATCH` or `DELETE`.\n",
        "tags": [
          "Team Members"
        ],
        "security": [
          {
            "OAuth2": [
              "team_members:read"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "responses": {
          "200": {
            "description": "The team member.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamMember"
                },
                "examples": {
                  "team_member": {
                    "$ref": "#/components/examples/TeamMemberExample"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      },
      "patch": {
        "operationId": "updateTeamMember",
        "summary": "Update a team member",
        "description": "Partially updates a Team Member with a merge-patch body. `services`, when present,\nreplaces the full set of bookable service links; each link is keyed by `service_id` and\ncarries the same override fields as on Service. Missing `services` leaves links unchanged,\n`services: []` unlinks every service, and `null` override fields inherit the Service\ndefault. Requires `If-Match`; changing links moves the ETag on both the Team Member and\naffected Service resources.\n",
        "tags": [
          "Team Members"
        ],
        "security": [
          {
            "OAuth2": [
              "team_members:write"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/merge-patch+json": {
              "schema": {
                "$ref": "#/components/schemas/TeamMemberUpdate"
              },
              "example": {
                "position_id": 16
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated team member.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamMember"
                },
                "examples": {
                  "team_member": {
                    "$ref": "#/components/examples/TeamMemberExample"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      },
      "delete": {
        "operationId": "deleteTeamMember",
        "summary": "Delete a team member",
        "description": "Removes a Team Member from the Location. Requires `If-Match` with the version last\nseen.\n",
        "tags": [
          "Team Members"
        ],
        "security": [
          {
            "OAuth2": [
              "team_members:write"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "responses": {
          "204": {
            "description": "The team member has been deleted.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/team_members/{team_member_id}/schedule": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        },
        {
          "$ref": "#/components/parameters/TeamMemberId"
        }
      ],
      "get": {
        "operationId": "getTeamMemberSchedule",
        "summary": "Get a team member's schedule",
        "description": "Returns the **configured working schedule** of a Team Member for a date range — the\ndays and working slots set by the business. This is not availability: free bookable\nslots come from `GET /locations/{location_id}/availability`.\n\nThe response carries an `ETag` header for a subsequent schedule `PATCH`.\n",
        "tags": [
          "Team Members"
        ],
        "security": [
          {
            "OAuth2": [
              "team_members:read"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "name": "date_from",
            "in": "query",
            "required": true,
            "description": "First date of the range (inclusive), `YYYY-MM-DD`.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "date_to",
            "in": "query",
            "required": true,
            "description": "Last date of the range (inclusive), `YYYY-MM-DD`.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Working days within the requested range.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamMemberSchedule"
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "date": "2026-07-24",
                      "slots": [
                        {
                          "from": "09:00",
                          "to": "13:00"
                        },
                        {
                          "from": "14:00",
                          "to": "18:00"
                        }
                      ]
                    },
                    {
                      "date": "2026-07-25",
                      "slots": [
                        {
                          "from": "10:00",
                          "to": "16:00"
                        }
                      ]
                    }
                  ],
                  "has_more": false,
                  "next_cursor": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      },
      "patch": {
        "operationId": "updateTeamMemberSchedule",
        "summary": "Update a team member's schedule",
        "description": "Updates the working schedule as a **merge-patch map keyed by date**: each key is a\n`YYYY-MM-DD` date, the value sets the working slots of that day, and `null` clears the\nday. Dates absent from the body are left unchanged. Times are wall-clock `HH:MM` in\nthe Location's timezone. Requires `If-Match` with the `ETag` from the schedule read.\n",
        "tags": [
          "Team Members"
        ],
        "security": [
          {
            "OAuth2": [
              "team_members:write"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/merge-patch+json": {
              "schema": {
                "$ref": "#/components/schemas/TeamMemberSchedulePatch"
              },
              "example": {
                "2026-07-24": {
                  "slots": [
                    {
                      "from": "09:00",
                      "to": "18:00"
                    }
                  ]
                },
                "2026-07-25": null
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The days updated by this request.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamMemberSchedule"
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "date": "2026-07-24",
                      "slots": [
                        {
                          "from": "09:00",
                          "to": "18:00"
                        }
                      ]
                    },
                    {
                      "date": "2026-07-25",
                      "slots": []
                    }
                  ],
                  "has_more": false,
                  "next_cursor": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/access_roles": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        }
      ],
      "get": {
        "operationId": "listLocationAccessRoles",
        "summary": "List location access roles",
        "description": "Returns public role ids and titles only; internal permission slugs are never exposed.\n\nCursor-paginated list. Default ordering: title, role_id tie-breaker. Cursors use the `cursor` query parameter, expire after 15 minutes, and are bound to the credential, tenant, and filters. Expired, malformed, or foreign cursors fail with `422 validation_failed` using field codes `expired_cursor`, `invalid_cursor`, or `foreign_cursor`.",
        "tags": [
          "Team Members"
        ],
        "security": [
          {
            "OAuth2": [
              "team_members:manage_access"
            ]
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LocationAccessRoleList"
                }
              }
            },
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/team_members/{team_member_id}/access": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        },
        {
          "$ref": "#/components/parameters/TeamMemberId"
        }
      ],
      "get": {
        "operationId": "getTeamMemberAccess",
        "summary": "Get team member access",
        "description": "Returns current system-access status for a Team Member: `none`, `invited`, or `active`, plus public `role_id` and pending invitation timestamps. The response carries a strong `ETag` for a subsequent access `PATCH`.",
        "tags": [
          "Team Members"
        ],
        "security": [
          {
            "OAuth2": [
              "team_members:manage_access"
            ]
          }
        ],
        "x-altegio-status": "preview",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamMemberAccess"
                }
              }
            },
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      },
      "patch": {
        "operationId": "updateTeamMemberAccess",
        "summary": "Update team member access",
        "description": "Merge-patch access settings. Only `role_id` is mutable; unavailable roles fail with `422 role_not_assignable`. Requires `If-Match` with the `ETag` from `getTeamMemberAccess` (or a prior successful write).",
        "tags": [
          "Team Members"
        ],
        "security": [
          {
            "OAuth2": [
              "team_members:manage_access"
            ]
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/merge-patch+json": {
              "schema": {
                "$ref": "#/components/schemas/TeamMemberAccessUpdate"
              },
              "example": {
                "role_id": 7
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamMemberAccess"
                }
              }
            },
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      },
      "delete": {
        "operationId": "revokeTeamMemberAccess",
        "summary": "Revoke team member access",
        "description": "Revokes pending or active system access and returns the Team Member to `none`; the Team Member record is not deleted. The last location owner cannot be revoked (`409 last_location_owner`). Requires `Idempotency-Key`. The `200` response carries a fresh access `ETag`.",
        "tags": [
          "Team Members"
        ],
        "security": [
          {
            "OAuth2": [
              "team_members:manage_access"
            ]
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamMemberAccess"
                }
              }
            },
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/team_members/{team_member_id}/invitation": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        },
        {
          "$ref": "#/components/parameters/TeamMemberId"
        }
      ],
      "post": {
        "operationId": "sendTeamMemberInvitation",
        "summary": "Send team member invitation",
        "description": "Sends or resends delivery for the single pending invitation of this Team Member.\nDelivery contact is set only at Team Member create via write-only `access_invitation.phone` /\n`access_invitation.email`; this resend uses the stored delivery target and does not accept\na contact override in the body. `422 invitation_contact_required` when no delivery contact\nexists; `409 invitation_already_pending` when a distinct pending invitation already exists;\n`409 access_already_active` when the Team Member already has active access. Requires\n`Idempotency-Key`.\n",
        "tags": [
          "Team Members"
        ],
        "security": [
          {
            "OAuth2": [
              "team_members:manage_access"
            ]
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TeamMemberInvitationCreate"
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamMemberAccess"
                }
              }
            },
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/positions": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        }
      ],
      "get": {
        "operationId": "listPositions",
        "summary": "List positions",
        "description": "Returns the position directory of the Location. Reference a position from a Team\nMember via `position_id`.\n\n\nCursor-paginated list. Default ordering: weight desc, title, id tie-breaker. Cursors use the `cursor` query parameter, expire after 15 minutes, and are bound to the credential, tenant, and filters. Expired, malformed, or foreign cursors fail with `422 validation_failed` using field codes `expired_cursor`, `invalid_cursor`, or `foreign_cursor`.",
        "tags": [
          "Positions"
        ],
        "security": [
          {
            "OAuth2": [
              "team_members:read"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of positions.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PositionList"
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "id": 15,
                      "object": "position",
                      "title": "Barber"
                    }
                  ],
                  "has_more": false,
                  "next_cursor": null
                }
              }
            },
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      },
      "post": {
        "operationId": "createPosition",
        "summary": "Create a position",
        "description": "Creates a position. Requires `Idempotency-Key`.",
        "tags": [
          "Positions"
        ],
        "security": [
          {
            "OAuth2": [
              "team_members:write"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PositionCreate"
              },
              "example": {
                "title": "Barber"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created position.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Position"
                },
                "example": {
                  "id": 15,
                  "object": "position",
                  "title": "Barber"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/positions/{position_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        },
        {
          "$ref": "#/components/parameters/PositionId"
        }
      ],
      "get": {
        "operationId": "getPosition",
        "summary": "Get a position",
        "description": "Returns a single position. The response carries an `ETag` header for a subsequent\n`PATCH` or `DELETE`.\n",
        "tags": [
          "Positions"
        ],
        "security": [
          {
            "OAuth2": [
              "team_members:read"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "responses": {
          "200": {
            "description": "The position.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Position"
                },
                "example": {
                  "id": 15,
                  "object": "position",
                  "title": "Barber"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      },
      "patch": {
        "operationId": "updatePosition",
        "summary": "Update a position",
        "description": "Partially updates a position with a merge-patch body. Requires `If-Match`.",
        "tags": [
          "Positions"
        ],
        "security": [
          {
            "OAuth2": [
              "team_members:write"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/merge-patch+json": {
              "schema": {
                "$ref": "#/components/schemas/PositionUpdate"
              },
              "example": {
                "title": "Senior barber"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated position.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Position"
                },
                "example": {
                  "id": 15,
                  "object": "position",
                  "title": "Senior barber"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      },
      "delete": {
        "operationId": "deletePosition",
        "summary": "Delete a position",
        "description": "Deletes a position. Requires `If-Match` with the version last seen.",
        "tags": [
          "Positions"
        ],
        "security": [
          {
            "OAuth2": [
              "team_members:write"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "responses": {
          "204": {
            "description": "The position has been deleted.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/clients": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        }
      ],
      "get": {
        "operationId": "listClients",
        "summary": "List and search clients",
        "description": "Searches the client base of the Location. Exact `phone` and `email` contact searches require `clients:read_contact` plus the subject contact permission; without that scope the API returns `403 missing_scope`/`permission_denied`, not an empty result. Free-text `query` remains under `clients:read`. Contact fields use the redaction contract when contact access is absent. This endpoint is rate-limited.\\n\\nCursor-paginated list. Default ordering: display_name, id tie-breaker. Cursors use the `cursor` query parameter, expire after 15 minutes, and are bound to the credential, tenant, and filters. Expired, malformed, or foreign cursors fail with `422 validation_failed` using field codes `expired_cursor`, `invalid_cursor`, or `foreign_cursor`.",
        "tags": [
          "Clients"
        ],
        "security": [
          {
            "OAuth2": [
              "clients:read"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "name": "phone",
            "in": "query",
            "required": false,
            "description": "Exact phone number to search for, in international format. Requires `clients:read_contact`; without it the response is `403`, not an empty list.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "email",
            "in": "query",
            "required": false,
            "description": "Exact email to search for. Requires `clients:read_contact`; without it the response is `403`, not an empty list.",
            "schema": {
              "type": "string",
              "format": "email"
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "description": "Free-text search across non-sensitive client display fields; contact-field exact search uses `phone`/`email` and requires `clients:read_contact`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of clients matching the search.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientList"
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "id": 789,
                      "object": "client",
                      "name": "Demo",
                      "surname": "Client",
                      "patronymic": null,
                      "display_name": "Demo Client",
                      "phone": "+351210000175",
                      "additional_phone": null,
                      "email": "demo.client@example.com",
                      "birth_date": "1990-04-12",
                      "comment": null,
                      "discount": 5,
                      "visits_count": 12,
                      "first_visit_at": "2025-11-02T10:00:00+00:00",
                      "last_visit_at": "2026-07-09T11:30:00+01:00",
                      "currency": "EUR",
                      "created_at": "2025-11-02T09:57:15Z",
                      "updated_at": "2026-07-09T09:57:15Z",
                      "sold_amount_minor": 1250000
                    }
                  ],
                  "has_more": false,
                  "next_cursor": null
                }
              }
            },
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      },
      "post": {
        "operationId": "createClient",
        "summary": "Create a client",
        "description": "Creates a client. `name` and `phone` are required. The phone number is deduplicated\nwithin the Location: if another (non-deleted) client already owns it, the request\nfails with `422 phone_taken`. That signal is part of `clients:write` (no\n`clients:read_contact` required); it does not reveal the other client's id or contacts.\nPhone-bearing writes are audited and rate-limited against enumeration. Requires\n`Idempotency-Key`.\n\nContact fields in the response follow the redaction contract: without\n`clients:read_contact` they are returned as `null` and listed in `redacted_fields`,\nnever as a placeholder string.\n",
        "tags": [
          "Clients"
        ],
        "security": [
          {
            "OAuth2": [
              "clients:write"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientCreate"
              },
              "example": {
                "name": "Demo",
                "surname": "Client",
                "phone": "+351210000175",
                "email": "demo.client@example.com",
                "birth_date": "1990-04-12"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created client.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Client"
                },
                "examples": {
                  "client": {
                    "$ref": "#/components/examples/ClientExample"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "description": "Validation failed, or the phone number already belongs to another client (`phone_taken`).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "phone_taken": {
                    "summary": "Phone already belongs to another client",
                    "value": {
                      "type": "https://developer.alteg.io/problems/phone-taken",
                      "title": "Phone number already in use",
                      "status": 422,
                      "detail": "Another client of this location already uses this phone number.",
                      "instance": "urn:altegio:request:req_c1x9",
                      "code": "phone_taken"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/clients/{client_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        },
        {
          "$ref": "#/components/parameters/ClientId"
        }
      ],
      "get": {
        "operationId": "getClient",
        "summary": "Get a client",
        "description": "Returns a single client. Contact fields (`phone`, `email`, `additional_phone`)\nrequire `clients:read_contact` plus the subject's contact permission — otherwise they\nare returned as `null` and listed in `redacted_fields` instead of exposing a contact string. The response carries an\n`ETag` header for a subsequent `PATCH` or `DELETE`.\n",
        "tags": [
          "Clients"
        ],
        "security": [
          {
            "OAuth2": [
              "clients:read"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "responses": {
          "200": {
            "description": "The client.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Client"
                },
                "examples": {
                  "client": {
                    "$ref": "#/components/examples/ClientExample"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      },
      "patch": {
        "operationId": "updateClient",
        "summary": "Update a client",
        "description": "Partially updates a client with a merge-patch body. Requires `If-Match`. Changing the\nphone to a number owned by another client fails with `422 phone_taken` — the same\n`clients:write` signal as on create (audited and rate-limited; no other client identity\nis returned). Clearing a nullable field is done with `null`; clearing contact fields\nrequires contact write access.\n",
        "tags": [
          "Clients"
        ],
        "security": [
          {
            "OAuth2": [
              "clients:write"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/merge-patch+json": {
              "schema": {
                "$ref": "#/components/schemas/ClientUpdate"
              },
              "example": {
                "email": "new.email@example.com",
                "comment": "Prefers morning visits"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated client.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Client"
                },
                "examples": {
                  "client": {
                    "$ref": "#/components/examples/ClientExample"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "description": "Validation failed, the phone is taken (`phone_taken`), or merge-patch semantics were violated (`field_not_nullable`, `field_immutable`).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "phone_taken": {
                    "summary": "Phone already belongs to another client",
                    "value": {
                      "type": "https://developer.alteg.io/problems/phone-taken",
                      "title": "Phone number already in use",
                      "status": 422,
                      "detail": "Another client of this location already uses this phone number.",
                      "instance": "urn:altegio:request:req_c4x2",
                      "code": "phone_taken"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      },
      "delete": {
        "operationId": "deleteClient",
        "summary": "Delete a client",
        "description": "Deletes a client (soft delete). A client with the same phone can be created again\nafterwards. Requires `If-Match` with the version last seen.\n",
        "tags": [
          "Clients"
        ],
        "security": [
          {
            "OAuth2": [
              "clients:write"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "responses": {
          "204": {
            "description": "The client has been deleted.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/availability": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        }
      ],
      "get": {
        "operationId": "getAvailability",
        "summary": "Search availability slots",
        "description": "Computes available start times for the given services within a date range —\noptionally narrowed to one Team Member. Slots are derived from configured schedules,\nexisting appointments, and service durations; `starts_at` values carry the Location's\nlocal UTC offset and can be passed directly to `createAppointment`.\n\nThis is a computed read-only aggregate: slots have no ids and can disappear at any\nmoment when another request schedules an overlapping appointment (a lost race on create\nreturns `409 slot_taken`).\n",
        "tags": [
          "Availability"
        ],
        "security": [
          {
            "OAuth2": [
              "availability:read"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "name": "service_ids",
            "in": "query",
            "required": true,
            "description": "Services the appointment will consist of.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "minItems": 1,
              "items": {
                "type": "integer"
              }
            }
          },
          {
            "name": "team_member_id",
            "in": "query",
            "required": false,
            "description": "Restrict the search to one Team Member.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "date_from",
            "in": "query",
            "required": true,
            "description": "First date of the range (inclusive), `YYYY-MM-DD`.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "date_to",
            "in": "query",
            "required": true,
            "description": "Last date of the range (inclusive), `YYYY-MM-DD`.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Free slots for the requested services and range.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Availability"
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "availability_slot",
                      "team_member_id": 321,
                      "starts_at": "2026-07-24T11:30:00+01:00",
                      "is_bookable": true
                    },
                    {
                      "object": "availability_slot",
                      "team_member_id": 321,
                      "starts_at": "2026-07-24T12:30:00+01:00",
                      "is_bookable": true
                    }
                  ],
                  "has_more": false,
                  "next_cursor": null
                }
              }
            },
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/resources": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        }
      ],
      "get": {
        "operationId": "listResources",
        "summary": "List resources",
        "description": "Returns the bookable resources of the Location (rooms, chairs, equipment) with their\ninstances. Pass instance ids as `resource_instance_ids` when creating an appointment\nfor services that require a resource. Resource management (create/update/delete) is\nnot part of the preview scope.\n\n\nCursor-paginated list. Default ordering: title, id tie-breaker. Cursors use the `cursor` query parameter, expire after 15 minutes, and are bound to the credential, tenant, and filters. Expired, malformed, or foreign cursors fail with `422 validation_failed` using field codes `expired_cursor`, `invalid_cursor`, or `foreign_cursor`.",
        "tags": [
          "Resources"
        ],
        "security": [
          {
            "OAuth2": [
              "locations:read"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of resources.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResourceList"
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "id": 41,
                      "object": "resource",
                      "title": "Massage room",
                      "instances": [
                        {
                          "id": 411,
                          "object": "resource_instance",
                          "title": "Room 1"
                        },
                        {
                          "id": 412,
                          "object": "resource_instance",
                          "title": "Room 2"
                        }
                      ]
                    }
                  ],
                  "has_more": false,
                  "next_cursor": null
                }
              }
            },
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/appointments": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        }
      ],
      "post": {
        "operationId": "createAppointment",
        "summary": "Create an appointment",
        "description": "Creates an appointment for a client with a Team Member at a chosen start time.\n\n- **A client is optional.** Pass `client_id` of an existing client, an inline `client` object resolved attach-only by normalized phone without `clients:write`, or neither for a clientless appointment.\n- With a client the `201` response carries `visit_id > 0`; a clientless appointment has `visit_id: null`. Payment and visit operations are keyed by `appointment_id`, not `visit_id`.\n- Slot conflicts are checked server-side: if a parallel request takes the slot first,\n  the call fails with `409 slot_taken` — pick a new slot from availability and retry\n  with a **new** `Idempotency-Key`.\n- Requires `Idempotency-Key`; a retry of the same request returns the original\n  result instead of recording a duplicate appointment.\n\n- For a package service, pass `components[{package_component_id, team_member_id}]` from availability; the `201` response returns all created appointments plus their shared `visit_id`.\n- Inline client resolution does not reveal whether a client was found or created.\n",
        "tags": [
          "Appointments"
        ],
        "security": [
          {
            "OAuth2": [
              "appointments:create"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppointmentCreate"
              },
              "examples": {
                "withExistingClient": {
                  "summary": "Book for an existing client",
                  "value": {
                    "client_id": 789,
                    "team_member_id": 321,
                    "service_ids": [
                      100
                    ],
                    "starts_at": "2026-07-24T11:30:00+01:00"
                  }
                },
                "withInlineClient": {
                  "summary": "Book with an inline client",
                  "value": {
                    "client": {
                      "name": "Demo Client",
                      "phone": "+351210000175"
                    },
                    "team_member_id": 321,
                    "service_ids": [
                      100
                    ],
                    "starts_at": "2026-07-24T11:30:00+01:00",
                    "comment": "First visit"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created appointment.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/Appointment"
                    },
                    {
                      "$ref": "#/components/schemas/AppointmentCreateResponse"
                    }
                  ]
                },
                "examples": {
                  "appointment": {
                    "$ref": "#/components/examples/AppointmentExample"
                  },
                  "package": {
                    "summary": "Package appointment result",
                    "value": {
                      "object": "appointment_create_result",
                      "visit_id": 5001,
                      "appointments": []
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "description": "The requested slot was taken by a parallel appointment creation (`slot_taken`), or the `Idempotency-Key` conflicts with an earlier request (`idempotency_key_reused`, `idempotency_request_in_progress`).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "slot_taken": {
                    "summary": "Slot lost to a parallel appointment creation",
                    "value": {
                      "type": "https://developer.alteg.io/problems/slot-taken",
                      "title": "Time slot is no longer available",
                      "status": 409,
                      "detail": "The requested time slot was taken by another appointment request.",
                      "instance": "urn:altegio:request:req_ap1x",
                      "code": "slot_taken"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      },
      "get": {
        "operationId": "listAppointments",
        "summary": "List appointments",
        "description": "Returns the appointments of the Location as a cursor-paginated list, newest window\nfirst by start time with a stable tie-breaker. Client contact data inside\n`expand[]=client` uses redacted contact fields without `clients:read_contact`.\n\nCancelled appointments are included; filter them out on the `status` field. There is no\nstatus query parameter in the first iteration.\n\n\nCursor-paginated list. Default ordering: starts_at desc, id tie-breaker. Cursors use the `cursor` query parameter, expire after 15 minutes, and are bound to the credential, tenant, and filters. Expired, malformed, or foreign cursors fail with `422 validation_failed` using field codes `expired_cursor`, `invalid_cursor`, or `foreign_cursor`.",
        "tags": [
          "Appointments"
        ],
        "security": [
          {
            "OAuth2": [
              "appointments:read"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "name": "starts_at_from",
            "in": "query",
            "required": false,
            "description": "Only appointments starting at or after this RFC 3339 datetime.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "starts_at_to",
            "in": "query",
            "required": false,
            "description": "Only appointments starting before this RFC 3339 datetime.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "team_member_id",
            "in": "query",
            "required": false,
            "description": "Only appointments of this Team Member.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "client_id",
            "in": "query",
            "required": false,
            "description": "Only appointments of this client.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/ExpandAppointment"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of appointments.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppointmentList"
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "id": 12345,
                      "object": "appointment",
                      "location_id": 4321,
                      "client_id": 789,
                      "visit_id": 5001,
                      "team_member_id": 321,
                      "service_ids": [
                        100
                      ],
                      "resource_instance_ids": [],
                      "status": "waiting",
                      "starts_at": "2026-07-24T11:30:00+01:00",
                      "ends_at": "2026-07-24T12:30:00+01:00",
                      "duration_seconds": 3600,
                      "is_paid": false,
                      "comment": null,
                      "cancelled_at": null,
                      "cancellation_reason": null,
                      "created_at": "2026-07-20T09:57:15Z",
                      "updated_at": "2026-07-20T09:57:15Z"
                    }
                  ],
                  "has_more": true,
                  "next_cursor": "cur_appt_01J3K6Y8Q4W5E9R2T7A1B0C3D4"
                }
              }
            },
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/appointments/{appointment_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        },
        {
          "$ref": "#/components/parameters/AppointmentId"
        }
      ],
      "get": {
        "operationId": "getAppointment",
        "summary": "Get an appointment",
        "description": "Returns a single appointment. `visit_id` links to the billing visit (it is `null`\nonly on appointments created without a client). Re-read the appointment to obtain a\nfresh `visit_id` — visit grouping may change when the appointments of the day are edited. The\nresponse carries an `ETag` header for a subsequent `PATCH`.\n",
        "tags": [
          "Appointments"
        ],
        "security": [
          {
            "OAuth2": [
              "appointments:read"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/ExpandAppointment"
          }
        ],
        "responses": {
          "200": {
            "description": "The appointment.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Appointment"
                },
                "examples": {
                  "appointment": {
                    "$ref": "#/components/examples/AppointmentExample"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      },
      "patch": {
        "operationId": "updateAppointment",
        "summary": "Update (reschedule) an appointment",
        "description": "Edits or reschedules an appointment with a merge-patch body: start time, Team Member,\nservices, duration, comment. The appointment status is **not** editable here — use the\n`status` command. Cancellation is a separate `cancel` command.\n\n`If-Match` is required: appointments are the most contended resource (a human and an\nagent often edit the same one), and a stale version fails with `412 stale_version`\ninstead of silently overwriting someone's change.\n",
        "tags": [
          "Appointments"
        ],
        "security": [
          {
            "OAuth2": [
              "appointments:write"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/merge-patch+json": {
              "schema": {
                "$ref": "#/components/schemas/AppointmentUpdate"
              },
              "example": {
                "starts_at": "2026-07-24T15:00:00+01:00",
                "team_member_id": 322
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated appointment.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "ETag": {
                "$ref": "#/components/headers/ETag"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Appointment"
                },
                "examples": {
                  "appointment": {
                    "$ref": "#/components/examples/AppointmentExample"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/appointments/{appointment_id}/cancel": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        },
        {
          "$ref": "#/components/parameters/AppointmentId"
        }
      ],
      "post": {
        "operationId": "cancelAppointment",
        "summary": "Cancel an appointment",
        "description": "Cancels an appointment as a command: the response is the appointment with\n`status: \"cancelled\"` and `cancelled_at` set. Optionally stores a `reason` and\nwhether to notify the client.\n\nRequires `Idempotency-Key`. Cancelling an already cancelled appointment with the same\nkey is a no-op returning the same result.\n\nA cancelled appointment stays readable: `getAppointment` returns it with\n`status: \"cancelled\"` rather than `404`, and `listAppointments` keeps returning it, so a\nclient synchronising the journal sees the cancellation instead of silently losing the row.\nFilter on the `status` field to hide them. In a visit built from several appointments,\ncancelling one does not cancel the visit: the remaining appointments and any payments\nalready recorded stay, and `amount_to_pay_minor` is recomputed when the visit is read.\n",
        "tags": [
          "Appointments"
        ],
        "security": [
          {
            "OAuth2": [
              "appointments:write"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppointmentCancelRequest"
              },
              "example": {
                "reason": "client_request",
                "notify_client": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The cancelled appointment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Appointment"
                },
                "example": {
                  "id": 12345,
                  "object": "appointment",
                  "location_id": 4321,
                  "client_id": 789,
                  "visit_id": 5001,
                  "team_member_id": 321,
                  "service_ids": [
                    100
                  ],
                  "resource_instance_ids": [],
                  "status": "cancelled",
                  "starts_at": "2026-07-24T11:30:00+01:00",
                  "ends_at": "2026-07-24T12:30:00+01:00",
                  "duration_seconds": 3600,
                  "is_paid": false,
                  "comment": null,
                  "cancelled_at": "2026-07-22T05:15:00Z",
                  "cancellation_reason": "client_request",
                  "created_at": "2026-07-20T09:57:15Z",
                  "updated_at": "2026-07-22T05:15:00Z"
                }
              }
            },
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/appointments/{appointment_id}/status": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        },
        {
          "$ref": "#/components/parameters/AppointmentId"
        }
      ],
      "post": {
        "operationId": "setAppointmentStatus",
        "summary": "Set appointment status",
        "description": "Moves an appointment through its status lifecycle: `waiting` → `confirmed` →\n`arrived` / `no_show`. Marking a past visit as `arrived` finalizes it when nothing\nis left to pay (a fully paid or free visit).\n\nRequires `Idempotency-Key`. Setting the status the appointment already has is a\nno-op returning the current state.\n\n\nThe appointment identifies the visit; the submitted status is applied to the whole visit and all of its appointments. The response shows the resulting appointment status. An appointment created without a client has no visit (`visit_id` is `null`), so the status applies to that appointment alone - there is nothing to widen to.",
        "tags": [
          "Appointments"
        ],
        "security": [
          {
            "OAuth2": [
              "appointments:write"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppointmentStatusRequest"
              },
              "example": {
                "status": "arrived"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The appointment with the new status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Appointment"
                },
                "example": {
                  "id": 12345,
                  "object": "appointment",
                  "location_id": 4321,
                  "client_id": 789,
                  "visit_id": 5001,
                  "team_member_id": 321,
                  "service_ids": [
                    100
                  ],
                  "resource_instance_ids": [],
                  "status": "arrived",
                  "starts_at": "2026-07-24T11:30:00+01:00",
                  "ends_at": "2026-07-24T12:30:00+01:00",
                  "duration_seconds": 3600,
                  "is_paid": true,
                  "comment": null,
                  "cancelled_at": null,
                  "cancellation_reason": null,
                  "created_at": "2026-07-20T09:57:15Z",
                  "updated_at": "2026-07-24T13:05:00Z"
                }
              }
            },
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/payment_methods": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        }
      ],
      "get": {
        "operationId": "listPaymentMethods",
        "summary": "List payment methods",
        "description": "Returns the payment methods configured for the Location - what the business accepts and how\neach method settles. This is the only source of `payment_method_id` for `createPayment`.\n\nIt does not say whether a method can pay a particular visit: read `visit.payment_methods`\nfor that, or `searchLoyaltyInstrument` for a gift card or membership held by number. Both\nreturn `ApplicablePaymentMethod`, a different shape from this one.\n\nCursor-paginated list. Default ordering: name, id tie-breaker. Cursors use the `cursor` query parameter, expire after 15 minutes, and are bound to the credential, tenant, and filters. Expired, malformed, or foreign cursors fail with `422 validation_failed` using field codes `expired_cursor`, `invalid_cursor`, or `foreign_cursor`.",
        "tags": [
          "Payments"
        ],
        "security": [
          {
            "OAuth2": [
              "payments:read"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of payment methods.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentMethodList"
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "id": 71,
                      "object": "payment_method",
                      "slug": "card",
                      "name": "Bank card",
                      "default_account_id": 12,
                      "fee_rate": 1.5,
                      "card_brands": [
                        {
                          "id": 1,
                          "title": "Visa"
                        },
                        {
                          "id": 2,
                          "title": "Mastercard"
                        }
                      ],
                      "installments": []
                    }
                  ],
                  "has_more": false,
                  "next_cursor": null
                }
              }
            },
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/accounts": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        }
      ],
      "get": {
        "operationId": "listAccounts",
        "summary": "List accounts",
        "description": "Returns the cash/settlement accounts of the Location. Use an account's `id` as\n`account_id` in `createPayment` — either as the target of an `account` payment or as\nan override of a payment method's default account.\n\n\nCursor-paginated list. Default ordering: title, id tie-breaker. Cursors use the `cursor` query parameter, expire after 15 minutes, and are bound to the credential, tenant, and filters. Expired, malformed, or foreign cursors fail with `422 validation_failed` using field codes `expired_cursor`, `invalid_cursor`, or `foreign_cursor`.",
        "tags": [
          "Payments"
        ],
        "security": [
          {
            "OAuth2": [
              "finance:read"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccountList"
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "id": 12,
                      "object": "account",
                      "title": "Main cash desk",
                      "type_slug": "cash",
                      "is_acquiring": false
                    },
                    {
                      "id": 13,
                      "object": "account",
                      "title": "Acquiring",
                      "type_slug": "cashless",
                      "is_acquiring": true
                    }
                  ],
                  "has_more": false,
                  "next_cursor": null
                }
              }
            },
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/products": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        }
      ],
      "get": {
        "operationId": "listProducts",
        "summary": "List products",
        "description": "Returns the minimal product catalog fields available in P0: `id`, `title`,\n`price_minor`, and `unit`. Inventory levels, balances, cost, and standalone product\nsales are not part of the preview scope.\n\nCursor-paginated list. Default ordering: title, id tie-breaker. Cursors use the `cursor` query parameter, expire after 15 minutes, and are bound to the credential, tenant, and filters. Expired, malformed, or foreign cursors fail with `422 validation_failed` using field codes `expired_cursor`, `invalid_cursor`, or `foreign_cursor`.",
        "tags": [
          "Products"
        ],
        "security": [
          {
            "OAuth2": [
              "products:read"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/Query"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductList"
                }
              }
            },
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/appointments/{appointment_id}/payment_methods/search": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        },
        {
          "$ref": "#/components/parameters/AppointmentId"
        }
      ],
      "get": {
        "operationId": "searchLoyaltyInstrument",
        "summary": "Find a gift card or membership by number",
        "description": "Looks up a gift card or membership by its number and reports whether it can pay the visit\nresolved from `appointment_id`.\n\nThis is for instruments the client does not hold in Altegio: a gift card received as a\npresent, or a membership bought at another location of the chain. Instruments already\nattached to the client come back from `getVisit` in `payment_methods` and need no search.\nResults carry the same applicability fields, so both are passed to `createPayment` the\nsame way.\n\n**Anti-enumeration:** unknown, foreign-tenant, expired, and not-applicable numbers share one\nclient-visible outcome — empty result or `422 instrument_not_applicable` — and never distinct\ncodes that would reveal whether a number exists elsewhere. This search and number-bearing\n`createPayment` attempts share a per-credential, per-Location rate limit (`429` +\n`Retry-After`).",
        "tags": [
          "Payments"
        ],
        "security": [
          {
            "OAuth2": [
              "loyalty:read"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/Number"
          },
          {
            "$ref": "#/components/parameters/LoyaltyInstrumentType"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApplicablePaymentMethodList"
                }
              }
            },
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/appointments/{appointment_id}/visit": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        },
        {
          "$ref": "#/components/parameters/AppointmentId"
        }
      ],
      "get": {
        "operationId": "getVisit",
        "summary": "Get the visit for an appointment",
        "description": "Returns the billing state resolved from `appointment_id`: service and products, payment methods applicable to this visit, payments from money and loyalty sources, and `amount_to_pay_minor`. `visit_id` remains a response field but is not a path key because appointments without a client may not have a visit id while still being payable. The first call may materialize the billing document; repeated calls resolve to the same document.",
        "tags": [
          "Visits"
        ],
        "security": [
          {
            "OAuth2": [
              "visits:read"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/ExpandVisit"
          }
        ],
        "responses": {
          "200": {
            "description": "The visit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Visit"
                },
                "examples": {
                  "visit": {
                    "$ref": "#/components/examples/VisitExample"
                  }
                }
              }
            },
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/appointments/{appointment_id}/visit/items": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        },
        {
          "$ref": "#/components/parameters/AppointmentId"
        }
      ],
      "post": {
        "operationId": "addVisitItem",
        "summary": "Add a product line to the appointment's visit",
        "description": "Adds one product line to the visit resolved from `appointment_id` — the \"came for a haircut,\nbought shampoo, one receipt\" case.\n\nEach call appends a single line and never replaces the existing ones, so a client that does not\nknow about lines added elsewhere cannot remove them. Service lines are derived from the\nappointment itself and cannot be created here. Adding a line increases `amount_to_pay_minor`\nand changes the visit ETag. Inventory levels are not checked — the product catalog and inventory\nare outside this contract. Requires `Idempotency-Key`; a retry returns the visit as it stood\nafter the first call instead of adding the line twice.",
        "tags": [
          "Visits"
        ],
        "security": [
          {
            "OAuth2": [
              "visits:write"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VisitItemCreate"
              },
              "examples": {
                "product": {
                  "summary": "Add one product to the visit",
                  "value": {
                    "product_id": 4417,
                    "quantity": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The updated visit, including the new line.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Visit"
                },
                "examples": {
                  "visit": {
                    "$ref": "#/components/examples/VisitExample"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "description": "The line cannot be added. Stable codes: `visit_already_settled` (the visit is fully paid, so adding a line would leave a settled visit with an unpaid remainder), `idempotency_key_reused`, `idempotency_request_in_progress`.\n",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/appointments/{appointment_id}/payments": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        },
        {
          "$ref": "#/components/parameters/AppointmentId"
        }
      ],
      "post": {
        "operationId": "createPayment",
        "summary": "Record a payment on the appointment's visit",
        "description": "Records a money, deposit, loyalty, gift card, membership, or referral-program payment against the visit resolved from `appointment_id`. This records that payment was received (cash, card terminal, cashless, or a loyalty instrument) — Altegio does not move funds through a payment provider on this call. `amount_minor` must not exceed `amount_to_pay_minor`; for `membership`, `amount_minor` is `0` and coverage is reported through `covers_items`. Gift card and membership addressed by `number` use the same anti-enumeration policy as `searchLoyaltyInstrument` (`422 instrument_not_applicable`, shared rate limit). Requires `Idempotency-Key`; a retry returns the original payment instead of recording it twice. A free visit (`amount_to_pay_minor` is `0`) needs no payment; finalize it through `setAppointmentStatus`.",
        "tags": [
          "Payments"
        ],
        "security": [
          {
            "OAuth2": [
              "payments:capture"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PaymentCreate"
              },
              "examples": {
                "cardViaMethod": {
                  "summary": "Card payment via a configured method",
                  "value": {
                    "type": "payment_method",
                    "payment_method_id": 71,
                    "account_id": 13,
                    "amount_minor": 250000,
                    "card_brand_id": 1
                  }
                },
                "directToAccount": {
                  "summary": "Direct payment to an account",
                  "value": {
                    "type": "account",
                    "account_id": 12,
                    "amount_minor": 100000
                  }
                },
                "membershipByNumber": {
                  "summary": "Spend a membership session (no amount - sessions, not money)",
                  "value": {
                    "type": "membership",
                    "number": "472671"
                  }
                },
                "membershipWithoutCode": {
                  "summary": "Spend a membership that carries no code",
                  "value": {
                    "type": "membership",
                    "membership_id": 5298007
                  }
                },
                "depositPartial": {
                  "summary": "Part of the amount from the client's deposit",
                  "value": {
                    "type": "deposit",
                    "deposit_id": 862771,
                    "amount_minor": 900
                  }
                },
                "referralProgram": {
                  "summary": "Apply one of the referral programs the client is eligible for",
                  "value": {
                    "type": "referral_program",
                    "referral_program_id": 30411,
                    "referrer_phone": "+5511999999999",
                    "amount_minor": 900
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The registered payment.",
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "Location": {
                "$ref": "#/components/headers/Location"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                },
                "examples": {
                  "payment": {
                    "$ref": "#/components/examples/PaymentExample"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "description": "Validation failed, amount exceeds unpaid remainder (`exceeds_unpaid`), or a number-addressed loyalty instrument is not usable (`instrument_not_applicable`).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "exceeds_unpaid": {
                    "summary": "Amount exceeds the unpaid remainder",
                    "value": {
                      "type": "https://developer.alteg.io/problems/exceeds-unpaid",
                      "title": "Payment exceeds unpaid amount",
                      "status": 422,
                      "detail": "The payment amount is greater than the remaining unpaid amount of the visit.",
                      "instance": "urn:altegio:request:req_v2x7",
                      "code": "exceeds_unpaid"
                    }
                  },
                  "instrument_not_applicable": {
                    "summary": "Loyalty instrument not usable for this visit",
                    "value": {
                      "type": "https://developer.alteg.io/problems/instrument-not-applicable",
                      "title": "Instrument not applicable",
                      "status": 422,
                      "detail": "The gift card or membership cannot be applied to this visit.",
                      "instance": "urn:altegio:request:req_v2x8",
                      "code": "instrument_not_applicable"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    },
    "/locations/{location_id}/appointments/{appointment_id}/payments/{payment_id}/refund": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LocationId"
        },
        {
          "$ref": "#/components/parameters/PaymentId"
        },
        {
          "$ref": "#/components/parameters/AppointmentId"
        }
      ],
      "post": {
        "operationId": "refundPayment",
        "summary": "Reverse a recorded payment",
        "description": "Locally reverses one payment action in full for the visit resolved from `appointment_id`.\nPartial reversal is not part of the contract. This is a local cancellation of posting in\nAltegio, not a payment-provider refund. Requires `Idempotency-Key`.\n\nNo step-up confirmation is part of this preview contract. Refund authorization is the\nexplicit `payments:refund` scope plus the subject's finance permission, with idempotency\nand audit on every call. A future human-confirmation flow will be added only with a\ncomplete challenge contract.\n",
        "tags": [
          "Payments"
        ],
        "security": [
          {
            "OAuth2": [
              "payments:refund"
            ]
          },
          {
            "RestrictedKey": []
          }
        ],
        "x-altegio-status": "preview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The payment after local reversal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                },
                "example": {
                  "id": "t9001",
                  "object": "payment",
                  "visit_id": 5001,
                  "type": "payment_method",
                  "payment_method_id": 71,
                  "account_id": 13,
                  "amount_minor": 250000,
                  "fee_minor": 2500,
                  "currency": "EUR",
                  "card_brand_id": 1,
                  "installment_count": null,
                  "status": "refunded",
                  "covers_items": [],
                  "refund": {
                    "id": "t9001",
                    "object": "refund",
                    "payment_id": "t9001",
                    "amount_minor": 250000,
                    "created_at": "2026-07-24T14:10:00Z"
                  },
                  "created_at": "2026-07-24T13:00:00Z"
                }
              }
            },
            "headers": {
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "406": {
            "$ref": "#/components/responses/NotAcceptable"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "OAuth2": {
        "type": "oauth2",
        "description": "OAuth 2.0 with **opaque** Bearer tokens (access tokens live about 15 minutes).\nMachine-to-machine integrations use the Client Credentials flow with an explicit\n`location_id`; user-facing applications use Authorization Code + PKCE with a\nrotating refresh token. There is no public password grant. See the Authorization\nsection of the API description for full walkthroughs.\n\n\nA grant `X:write` includes `X:read`; `read_contact` and action-scopes (`create`, `capture`, `refund`, `manage_access`) are never implied.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://api.alteg.io/oauth/authorize",
            "tokenUrl": "https://api.alteg.io/oauth/token",
            "refreshUrl": "https://api.alteg.io/oauth/token",
            "scopes": {
              "locations:read": "View location info",
              "locations:write": "Manage location settings",
              "locations:create": "Create locations",
              "services:read": "View services and service categories",
              "services:write": "Manage services and service categories. Also covers bills of materials when they ship",
              "products:read": "View product catalog",
              "team_members:read": "View team members, schedules, and positions",
              "team_members:write": "Add and update team members, schedules, and positions",
              "team_members:manage_access": "Manage team member system access and roles",
              "clients:read": "View clients. Does not include client files, comments, or labels — those get their own scopes",
              "clients:read_contact": "View client contact details",
              "clients:write": "Manage clients. Does not include client files, comments, or labels — those get their own scopes",
              "availability:read": "View bookable time slots, including which team member is free",
              "appointments:read": "View appointments. Also covers the waiting list and appointment custom fields when they ship",
              "appointments:create": "Create appointments",
              "appointments:write": "Manage appointments (edit, cancel, status). Also covers visit grouping and waiting-list management when they ship",
              "visits:read": "View visit and payment state",
              "visits:write": "Add items to a visit",
              "payments:read": "View payment methods",
              "payments:capture": "Record payments",
              "payments:refund": "Reverse payments locally",
              "finance:read": "View cash registers and financial accounts",
              "loyalty:read": "View loyalty instruments",
              "chain_services:read": "View chain service catalog",
              "chain_services:write": "Manage chain service catalog",
              "chain_clients:read": "View chain client base",
              "chain_loyalty:read": "View chain loyalty instruments"
            }
          },
          "clientCredentials": {
            "tokenUrl": "https://api.alteg.io/oauth/token",
            "scopes": {
              "locations:read": "View location info",
              "locations:write": "Manage location settings",
              "services:read": "View services and service categories",
              "services:write": "Manage services and service categories. Also covers bills of materials when they ship",
              "products:read": "View product catalog",
              "team_members:read": "View team members, schedules, and positions",
              "team_members:write": "Add and update team members, schedules, and positions",
              "clients:read": "View clients. Does not include client files, comments, or labels — those get their own scopes",
              "clients:read_contact": "View client contact details",
              "clients:write": "Manage clients. Does not include client files, comments, or labels — those get their own scopes",
              "availability:read": "View bookable time slots, including which team member is free",
              "appointments:read": "View appointments. Also covers the waiting list and appointment custom fields when they ship",
              "appointments:create": "Create appointments",
              "appointments:write": "Manage appointments (edit, cancel, status). Also covers visit grouping and waiting-list management when they ship",
              "visits:read": "View visit and payment state",
              "visits:write": "Add items to a visit",
              "payments:read": "View payment methods",
              "payments:capture": "Record payments",
              "payments:refund": "Reverse payments locally",
              "finance:read": "View cash registers and financial accounts",
              "loyalty:read": "View loyalty instruments"
            }
          }
        }
      },
      "RestrictedKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "A restricted key created in the Altegio cabinet for single-business integrations.\nThe key itself is the Bearer credential — no token exchange. Restricted keys carry\nscopes from the same public catalog as OAuth tokens (for example `payments:refund`\nwhen granted), except credential-class exclusions such as `team_members:manage_access`\nin the first iteration. They can be revoked in the cabinet at any time.\n"
      }
    },
    "parameters": {
      "LocationId": {
        "name": "location_id",
        "in": "path",
        "required": true,
        "description": "Identifier of the Location. Verified against the credential on every call — a Location the credential cannot access responds with `404`.",
        "schema": {
          "type": "integer"
        }
      },
      "ServiceId": {
        "name": "service_id",
        "in": "path",
        "required": true,
        "description": "Identifier of the service.",
        "schema": {
          "type": "integer"
        }
      },
      "ServiceCategoryId": {
        "name": "service_category_id",
        "in": "path",
        "required": true,
        "description": "Identifier of the service category.",
        "schema": {
          "type": "integer"
        }
      },
      "TeamMemberId": {
        "name": "team_member_id",
        "in": "path",
        "required": true,
        "description": "Identifier of the Team Member.",
        "schema": {
          "type": "integer"
        }
      },
      "PositionId": {
        "name": "position_id",
        "in": "path",
        "required": true,
        "description": "Identifier of the position.",
        "schema": {
          "type": "integer"
        }
      },
      "ClientId": {
        "name": "client_id",
        "in": "path",
        "required": true,
        "description": "Identifier of the client.",
        "schema": {
          "type": "integer"
        }
      },
      "AppointmentId": {
        "name": "appointment_id",
        "in": "path",
        "required": true,
        "description": "Identifier of the appointment.",
        "schema": {
          "type": "integer"
        }
      },
      "PaymentId": {
        "name": "payment_id",
        "in": "path",
        "required": true,
        "description": "Opaque payment id: `t<number>` for money transactions, `l<number>` for loyalty/deposit operations. Stable between requests.",
        "schema": {
          "type": "string"
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Page size for cursor pagination. Default is 25; maximum is 100.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 25
        }
      },
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "required": false,
        "description": "Opaque cursor from `next_cursor`. Cursors expire after 15 minutes and are bound to credential, tenant, filters, and ordering; expired, malformed, or foreign cursors fail with `422 validation_failed`.",
        "schema": {
          "type": "string"
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "Client-generated unique key for this logical request (UUID recommended). A retry with the same key and body returns the original result; the same key with a different body fails with `409 idempotency_key_reused`. Keys are retained at least 24 hours and are scoped to credential, Location, and operation; after expiry the value may be reused for a new logical request.",
        "schema": {
          "type": "string",
          "maxLength": 255
        }
      },
      "IfMatch": {
        "name": "If-Match",
        "in": "header",
        "required": true,
        "description": "`ETag` value of the resource version the change is based on. A stale value fails with `412 stale_version`; a missing header fails with `428 precondition_required`.",
        "schema": {
          "type": "string"
        }
      },
      "ExpandAppointment": {
        "name": "expand[]",
        "in": "query",
        "required": false,
        "description": "Relations to embed, one level deep. Expanding `client` never bypasses PII rules — contact fields are `null` and listed in `redacted_fields` without `clients:read_contact`. Unknown values fail with `422`.",
        "style": "form",
        "explode": true,
        "schema": {
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "client",
              "team_member"
            ]
          }
        }
      },
      "ExpandVisit": {
        "name": "expand[]",
        "in": "query",
        "required": false,
        "description": "Relations to embed, one level deep. Expanding `client` never bypasses PII rules — contact fields are `null` and listed in `redacted_fields` without `clients:read_contact`. Unknown values fail with `422`.",
        "style": "form",
        "explode": true,
        "schema": {
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "client"
            ]
          }
        }
      },
      "CountryId": {
        "name": "country_id",
        "in": "query",
        "required": false,
        "description": "Country dictionary identifier used to filter cities.",
        "schema": {
          "type": "integer"
        }
      },
      "Query": {
        "name": "query",
        "in": "query",
        "required": false,
        "description": "Search query.",
        "schema": {
          "type": "string"
        }
      },
      "Number": {
        "name": "number",
        "in": "query",
        "required": false,
        "description": "Loyalty instrument number to search.",
        "schema": {
          "type": "string"
        }
      },
      "LoyaltyInstrumentType": {
        "name": "type",
        "in": "query",
        "required": false,
        "description": "Loyalty instrument type.",
        "schema": {
          "type": "string",
          "enum": [
            "gift_card",
            "membership"
          ]
        }
      }
    },
    "headers": {
      "ETag": {
        "description": "Version tag of the returned resource. Send it back in `If-Match` on the next `PATCH` or `DELETE`.",
        "schema": {
          "type": "string"
        }
      },
      "Location": {
        "description": "URL of the created resource.",
        "schema": {
          "type": "string"
        }
      },
      "RateLimitPolicy": {
        "description": "Most constrained policy applied to the request, as `<limit>;w=<window in seconds>`.",
        "schema": {
          "type": "string"
        },
        "example": "10;w=60"
      },
      "RateLimitRemaining": {
        "description": "Requests left in the most constrained policy window that applies to this request.",
        "schema": {
          "type": "integer",
          "format": "int32",
          "minimum": 0
        }
      },
      "RateLimitReset": {
        "description": "On `429`, seconds until the most constrained policy frees up; it equals `Retry-After` there.\nOn a successful response it reports the **length** of that policy's window rather than the\ntime left in the current one, so treat it as the window size, not as a countdown — the\ncounter to pace against is `RateLimit-Remaining`.",
        "schema": {
          "type": "integer",
          "format": "int32",
          "minimum": 0
        }
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying.",
        "schema": {
          "type": "integer",
          "format": "int32",
          "minimum": 0
        }
      },
      "XRequestId": {
        "description": "Request identifier for support and log correlation.",
        "schema": {
          "type": "string"
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "The request has no valid Bearer credential (`invalid_token`).",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://developer.alteg.io/problems/invalid-token",
              "title": "Invalid or expired token",
              "status": 401,
              "detail": "The access token is missing, expired, or revoked.",
              "instance": "urn:altegio:request:req_401x",
              "code": "invalid_token"
            }
          }
        },
        "headers": {
          "WWW-Authenticate": {
            "description": "Bearer challenge.",
            "schema": {
              "type": "string"
            },
            "example": "Bearer error=\"invalid_token\""
          },
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        }
      },
      "Forbidden": {
        "description": "The token lacks the required scope (`missing_scope`), or the scope is present but the token subject lacks the business permission (`permission_denied`).",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "examples": {
              "missing_scope": {
                "summary": "Token scope is insufficient",
                "value": {
                  "type": "https://developer.alteg.io/problems/missing-scope",
                  "title": "Missing scope",
                  "status": 403,
                  "detail": "The access token does not carry the scope required by this operation.",
                  "instance": "urn:altegio:request:req_403a",
                  "code": "missing_scope"
                }
              },
              "permission_denied": {
                "summary": "Subject lacks the business permission",
                "value": {
                  "type": "https://developer.alteg.io/problems/permission-denied",
                  "title": "Permission denied",
                  "status": 403,
                  "detail": "The token subject does not have the required permission in this location.",
                  "instance": "urn:altegio:request:req_403b",
                  "code": "permission_denied"
                }
              }
            }
          }
        },
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        }
      },
      "NotAcceptable": {
        "description": "Requested `Accept` media type cannot be satisfied (`not_acceptable`).",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://developer.alteg.io/problems/not-acceptable",
              "title": "Not acceptable",
              "status": 406,
              "detail": "Use Accept application/json for resources and application/problem+json for errors.",
              "instance": "urn:altegio:request:req_406x",
              "code": "not_acceptable"
            }
          }
        }
      },
      "NotFound": {
        "description": "The resource does not exist or is hidden by tenant policy. Prefer a resource-specific\ncode (`appointment_not_found`, `location_not_found`, …). Routing/gate/unknown-path 404s\nuse the generic `resource_not_found` (same shape — do not distinguish “no pin” vs “missing”).\n",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "examples": {
              "resource_not_found": {
                "summary": "Generic 404 (routing / gate / hidden)",
                "value": {
                  "type": "https://developer.alteg.io/problems/resource-not-found",
                  "title": "Resource not found",
                  "status": 404,
                  "detail": "The requested resource was not found.",
                  "instance": "urn:altegio:request:req_404a",
                  "code": "resource_not_found"
                }
              },
              "appointment_not_found": {
                "summary": "Resource-specific 404",
                "value": {
                  "type": "https://developer.alteg.io/problems/appointment-not-found",
                  "title": "Appointment not found",
                  "status": 404,
                  "detail": "The requested resource does not exist in this location.",
                  "instance": "urn:altegio:request:req_404b",
                  "code": "appointment_not_found"
                }
              }
            }
          }
        },
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        }
      },
      "IdempotencyConflict": {
        "description": "The `Idempotency-Key` was already used with a different body (`idempotency_key_reused`), or the first request with this key is still running (`idempotency_request_in_progress`, retry after `Retry-After`).",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          },
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "examples": {
              "idempotency_key_reused": {
                "summary": "Same key, different body",
                "value": {
                  "type": "https://developer.alteg.io/problems/idempotency-key-reused",
                  "title": "Idempotency key reused",
                  "status": 409,
                  "detail": "This Idempotency-Key was already used with a different request body.",
                  "instance": "urn:altegio:request:req_409a",
                  "code": "idempotency_key_reused"
                }
              },
              "idempotency_request_in_progress": {
                "summary": "First request still in progress",
                "value": {
                  "type": "https://developer.alteg.io/problems/idempotency-request-in-progress",
                  "title": "Request in progress",
                  "status": 409,
                  "detail": "A request with this Idempotency-Key is still being processed. Retry later with the same key.",
                  "instance": "urn:altegio:request:req_409b",
                  "code": "idempotency_request_in_progress"
                }
              }
            }
          }
        }
      },
      "PreconditionFailed": {
        "description": "The `If-Match` version is stale — the resource was changed by another writer (`stale_version`). Re-read the resource and retry deliberately.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://developer.alteg.io/problems/stale-version",
              "title": "Resource version is stale",
              "status": 412,
              "detail": "The resource was modified after the version supplied in If-Match. Re-read it and retry.",
              "instance": "urn:altegio:request:req_412x",
              "code": "stale_version"
            }
          }
        },
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        }
      },
      "ValidationFailed": {
        "description": "Semantic validation failed (`validation_failed`); field-level details are in `errors[]`. Merge-patch violations use `field_not_nullable` / `field_immutable`.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://developer.alteg.io/problems/validation-error",
              "title": "Request validation failed",
              "status": 422,
              "detail": "One or more fields are invalid.",
              "instance": "urn:altegio:request:req_422x",
              "code": "validation_failed",
              "errors": [
                {
                  "code": "invalid_datetime",
                  "pointer": "/starts_at",
                  "message": "starts_at must be a valid RFC 3339 datetime.",
                  "source": "body"
                }
              ]
            }
          }
        },
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        }
      },
      "PreconditionRequired": {
        "description": "The required `If-Match` header is missing (`precondition_required`).",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://developer.alteg.io/problems/precondition-required",
              "title": "Precondition required",
              "status": 428,
              "detail": "This operation requires the If-Match header with the last seen ETag.",
              "instance": "urn:altegio:request:req_428x",
              "code": "precondition_required"
            }
          }
        },
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded (`rate_limit_exceeded`). Retry after `Retry-After`.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimitPolicy"
          },
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://developer.alteg.io/problems/rate-limit-exceeded",
              "title": "Rate limit exceeded",
              "status": 429,
              "detail": "Too many requests. Retry after the interval in Retry-After.",
              "instance": "urn:altegio:request:req_429x",
              "code": "rate_limit_exceeded"
            }
          }
        }
      },
      "DependencyUnavailable": {
        "description": "A dependency required for safe processing (e.g. the idempotency store) is unavailable; the write was **not** performed (`dependency_unavailable`). Retry with the same `Idempotency-Key` after a backoff.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://developer.alteg.io/problems/dependency-unavailable",
              "title": "Dependency unavailable",
              "status": 503,
              "detail": "A required dependency is unavailable. The operation was not performed.",
              "instance": "urn:altegio:request:req_503x",
              "code": "dependency_unavailable"
            }
          }
        },
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        }
      },
      "BadRequest": {
        "description": "Client error without a more specific registered code (`bad_request` — status default for\nbare 400 and unknown 4xx), or protocol/syntax failure (`malformed_request` — broken JSON /\nunparseable body only; must be thrown explicitly, never used as a synonym for every 400).\nSemantic field validation is `422 validation_failed`, not 400.\n",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "examples": {
              "bad_request": {
                "summary": "Generic client reject (status default)",
                "value": {
                  "type": "https://developer.alteg.io/problems/bad-request",
                  "title": "Bad request",
                  "status": 400,
                  "detail": "The request could not be processed.",
                  "instance": "urn:altegio:request:req_400a",
                  "code": "bad_request"
                }
              },
              "malformed_request": {
                "summary": "Broken JSON / protocol only",
                "value": {
                  "type": "https://developer.alteg.io/problems/malformed-request",
                  "title": "Malformed request",
                  "status": 400,
                  "detail": "The request body is not valid JSON.",
                  "instance": "urn:altegio:request:req_400b",
                  "code": "malformed_request"
                }
              }
            }
          }
        }
      },
      "UnsupportedMediaType": {
        "description": "Unsupported request media type.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://developer.alteg.io/problems/unsupported-media-type",
              "title": "Unsupported media type",
              "status": 415,
              "detail": "Use application/json or application/merge-patch+json as documented.",
              "instance": "urn:altegio:request:req_415x",
              "code": "unsupported_media_type"
            }
          }
        }
      },
      "Conflict": {
        "description": "Domain conflict (`slot_taken`, `duplicate_location`, `last_location_owner`), idempotency conflict, or generic `conflict` when no more specific registered code applies.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://developer.alteg.io/problems/conflict",
              "title": "Conflict",
              "status": 409,
              "detail": "The requested change conflicts with current resource state.",
              "instance": "urn:altegio:request:req_409x",
              "code": "conflict"
            }
          }
        }
      },
      "InternalError": {
        "description": "Unexpected server error (`internal_error`).",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://developer.alteg.io/problems/internal-error",
              "title": "Internal error",
              "status": 500,
              "detail": "An unexpected error occurred.",
              "instance": "urn:altegio:request:req_500x",
              "code": "internal_error"
            }
          }
        }
      }
    },
    "examples": {
      "LocationExample": {
        "summary": "A Location",
        "value": {
          "id": 4321,
          "object": "location",
          "name": "Demo Beauty Studio Lisbon",
          "description": null,
          "business_type_id": 1,
          "address": "Av. da Liberdade 100, Lisbon",
          "coordinates": {
            "latitude": 38.7223,
            "longitude": -9.1393
          },
          "phones": [
            "+351210000000"
          ],
          "site": "https://demo.example.com",
          "timezone": "Europe/Lisbon",
          "currency": "EUR",
          "created_at": "2025-01-15T10:00:00Z",
          "updated_at": "2026-07-01T08:00:00Z",
          "city_id": 6201,
          "language": "en-PT",
          "datetime_format": "DD.MM.YYYY HH:mm",
          "unique_visit_time_interval": 0,
          "country_code": "PT"
        }
      },
      "ServiceExample": {
        "summary": "A service",
        "value": {
          "id": 100,
          "object": "service",
          "type": "simple",
          "name": "Men's haircut",
          "category_id": 205,
          "duration_seconds": 3600,
          "currency": "EUR",
          "is_bookable": true,
          "team_members": [
            {
              "team_member_id": 321
            },
            {
              "team_member_id": 322,
              "duration_seconds": 2700,
              "price_minor": 300000
            }
          ],
          "created_at": "2026-07-01T08:00:00Z",
          "updated_at": "2026-07-10T09:30:00Z",
          "price_minor": 250000
        }
      },
      "ServicePackageExample": {
        "summary": "A service package",
        "value": {
          "id": 200,
          "object": "service",
          "type": "package",
          "package": {
            "exec_order": "sequential",
            "pricing_type": "sum",
            "price_discount_percent": 10,
            "components": [
              {
                "service_id": 1501,
                "position": 1
              },
              {
                "service_id": 1502,
                "position": 2
              }
            ]
          },
          "name": "Spa day",
          "category_id": 205,
          "duration_seconds": 7200,
          "currency": "EUR",
          "is_bookable": true,
          "team_members": [
            {
              "team_member_id": 321
            }
          ],
          "created_at": "2026-07-01T08:00:00Z",
          "updated_at": "2026-07-10T09:30:00Z",
          "price_minor": 500000
        }
      },
      "TeamMemberExample": {
        "summary": "A Team Member",
        "value": {
          "id": 321,
          "object": "team_member",
          "name": "Aigerim Bekova",
          "specialization": "Hairdresser",
          "position_id": 15,
          "service_ids": [
            100
          ],
          "is_bookable": true,
          "created_at": "2026-06-01T08:00:00Z",
          "updated_at": "2026-07-01T08:00:00Z"
        }
      },
      "ClientExample": {
        "summary": "A client (contact fields visible)",
        "value": {
          "id": 789,
          "object": "client",
          "name": "Demo",
          "surname": "Client",
          "patronymic": null,
          "display_name": "Demo Client",
          "phone": "+351210000175",
          "additional_phone": null,
          "email": "demo.client@example.com",
          "birth_date": "1990-04-12",
          "comment": null,
          "discount": 5,
          "visits_count": 12,
          "first_visit_at": "2025-11-02T10:00:00+00:00",
          "last_visit_at": "2026-07-09T11:30:00+01:00",
          "currency": "EUR",
          "created_at": "2025-11-02T09:57:15Z",
          "updated_at": "2026-07-09T09:57:15Z",
          "sold_amount_minor": 1250000
        }
      },
      "AppointmentExample": {
        "summary": "An appointment",
        "value": {
          "id": 12345,
          "object": "appointment",
          "location_id": 4321,
          "client_id": 789,
          "visit_id": 5001,
          "team_member_id": 321,
          "service_ids": [
            100
          ],
          "resource_instance_ids": [],
          "status": "waiting",
          "starts_at": "2026-07-24T11:30:00+01:00",
          "ends_at": "2026-07-24T12:30:00+01:00",
          "duration_seconds": 3600,
          "is_paid": false,
          "comment": null,
          "cancelled_at": null,
          "cancellation_reason": null,
          "created_at": "2026-07-20T09:57:15Z",
          "updated_at": "2026-07-20T09:57:15Z"
        }
      },
      "VisitExample": {
        "summary": "A visit with one paid service",
        "value": {
          "id": 5001,
          "object": "visit",
          "location_id": 4321,
          "appointment_ids": [
            12345
          ],
          "status": "arrived",
          "items": [
            {
              "id": 8101,
              "object": "visit_item",
              "type": "service",
              "service_id": 100,
              "title": "Men's haircut",
              "quantity": 1,
              "price_minor": 250000,
              "amount_minor": 250000
            }
          ],
          "payments": [
            {
              "id": "t9001",
              "object": "payment",
              "visit_id": 5001,
              "type": "payment_method",
              "payment_method_id": 71,
              "account_id": 13,
              "amount_minor": 250000,
              "fee_minor": 2500,
              "currency": "EUR",
              "card_brand_id": 1,
              "installment_count": null,
              "status": "completed",
              "covers_items": [],
              "refund": null,
              "created_at": "2026-07-24T13:00:00Z"
            }
          ],
          "currency": "EUR",
          "created_at": "2026-07-24T11:30:00Z",
          "updated_at": "2026-07-24T13:00:00Z",
          "amount_to_pay_minor": 0,
          "payment_methods": [
            {
              "id": 71,
              "object": "applicable_payment_method",
              "type": "payment_method",
              "name": "Bank card",
              "payment_method_id": 71,
              "account_id": 13,
              "number": null,
              "is_applicable": true,
              "applicable_amount_minor": 250000,
              "is_partial_allowed": true
            }
          ]
        }
      },
      "PaymentExample": {
        "summary": "A registered payment",
        "value": {
          "id": "t9001",
          "object": "payment",
          "visit_id": 5001,
          "type": "payment_method",
          "payment_method_id": 71,
          "account_id": 13,
          "amount_minor": 250000,
          "fee_minor": 2500,
          "currency": "EUR",
          "card_brand_id": 1,
          "installment_count": null,
          "status": "completed",
          "covers_items": [],
          "refund": null,
          "created_at": "2026-07-24T13:00:00Z"
        }
      }
    },
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "Error body (RFC 9457 hybrid), always served as `application/problem+json`. Branch on the stable `code`, never on human-readable text.",
        "properties": {
          "type": {
            "type": "string",
            "description": "Stable URL identifying the problem type; links to documentation."
          },
          "title": {
            "type": "string",
            "description": "Human-readable summary of the problem type."
          },
          "status": {
            "type": "integer",
            "description": "HTTP status code of this response."
          },
          "detail": {
            "type": "string",
            "description": "Human-readable explanation of this occurrence."
          },
          "instance": {
            "type": "string",
            "description": "Request identifier for support and log correlation."
          },
          "code": {
            "type": "string",
            "description": "Stable machine-readable reason code."
          },
          "errors": {
            "type": "array",
            "description": "Field-level issues (present on validation problems).",
            "items": {
              "$ref": "#/components/schemas/ProblemFieldError"
            }
          }
        },
        "required": [
          "type",
          "title",
          "status",
          "code",
          "instance"
        ],
        "additionalProperties": false
      },
      "ProblemFieldError": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "description": "Stable machine-readable reason for this field."
          },
          "pointer": {
            "type": "string",
            "description": "JSON Pointer for `body`; parameter name for `query`, `path`, or `header`."
          },
          "message": {
            "type": "string",
            "description": "Human-readable explanation."
          },
          "source": {
            "type": "string",
            "enum": [
              "body",
              "query",
              "path",
              "header"
            ],
            "description": "Where the invalid value came from."
          }
        },
        "required": [
          "code",
          "source",
          "pointer",
          "message"
        ],
        "additionalProperties": false
      },
      "ListEnvelope": {
        "type": "object",
        "description": "Common list envelope. `data` holds the page items; pass `next_cursor` as the `cursor` query parameter to fetch the next page while `has_more` is `true`.",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "has_more": {
            "type": "boolean",
            "description": "Whether more items exist beyond this page."
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Opaque cursor for the next page; `null` on the last page."
          }
        },
        "required": [
          "object",
          "has_more",
          "next_cursor"
        ]
      },
      "OAuthAuthorizationServerMetadata": {
        "type": "object",
        "description": "OAuth 2.0 authorization server metadata (RFC 8414).",
        "properties": {
          "issuer": {
            "type": "string"
          },
          "authorization_endpoint": {
            "type": "string"
          },
          "token_endpoint": {
            "type": "string"
          },
          "revocation_endpoint": {
            "type": "string"
          },
          "registration_endpoint": {
            "type": "string"
          },
          "response_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "grant_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "code_challenge_methods_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "token_endpoint_auth_methods_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "scopes_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "issuer",
          "authorization_endpoint",
          "token_endpoint"
        ],
        "additionalProperties": false
      },
      "OAuthTokenRequest": {
        "description": "Token request (RFC 6749, form-encoded). `resource` selects REST vs MCP token audience (RFC 8707).",
        "oneOf": [
          {
            "type": "object",
            "properties": {
              "grant_type": {
                "type": "string",
                "enum": [
                  "authorization_code"
                ]
              },
              "code": {
                "type": "string"
              },
              "redirect_uri": {
                "type": "string"
              },
              "code_verifier": {
                "type": "string"
              },
              "client_id": {
                "type": "string"
              },
              "scope": {
                "type": "string"
              },
              "resource": {
                "type": "string",
                "enum": [
                  "https://api.alteg.io",
                  "https://mcp.alteg.io"
                ],
                "description": "RFC 8707 resource indicator selecting the REST API or MCP audience."
              }
            },
            "required": [
              "grant_type",
              "code",
              "redirect_uri",
              "code_verifier",
              "client_id"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "grant_type": {
                "type": "string",
                "enum": [
                  "refresh_token"
                ]
              },
              "refresh_token": {
                "type": "string"
              },
              "client_id": {
                "type": "string"
              },
              "scope": {
                "type": "string"
              },
              "resource": {
                "type": "string",
                "enum": [
                  "https://api.alteg.io",
                  "https://mcp.alteg.io"
                ],
                "description": "RFC 8707 resource indicator selecting the REST API or MCP audience."
              }
            },
            "required": [
              "grant_type",
              "refresh_token",
              "client_id"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "grant_type": {
                "type": "string",
                "enum": [
                  "client_credentials"
                ]
              },
              "location_id": {
                "type": "integer",
                "description": "The Location this token acts in. The server resolves the installation from the\nauthenticated OAuth client plus this Location, so one token is always bound to\nexactly one Location.\n"
              },
              "scope": {
                "type": "string"
              },
              "resource": {
                "type": "string",
                "enum": [
                  "https://api.alteg.io",
                  "https://mcp.alteg.io"
                ],
                "description": "RFC 8707 resource indicator selecting the REST API or MCP audience."
              }
            },
            "required": [
              "grant_type",
              "location_id"
            ],
            "additionalProperties": false
          }
        ]
      },
      "OAuthTokenResponse": {
        "type": "object",
        "properties": {
          "access_token": {
            "type": "string",
            "description": "Opaque access token — never parse it."
          },
          "token_type": {
            "type": "string",
            "enum": [
              "Bearer"
            ]
          },
          "expires_in": {
            "type": "integer",
            "description": "Access token lifetime in seconds (about 900)."
          },
          "refresh_token": {
            "type": "string",
            "description": "Rotating refresh token; only issued by the Authorization Code flow."
          },
          "scope": {
            "type": "string",
            "description": "Space-separated scopes carried by the token."
          }
        },
        "required": [
          "access_token",
          "token_type",
          "expires_in"
        ],
        "additionalProperties": false
      },
      "OAuthRevokeRequest": {
        "type": "object",
        "description": "Token revocation request (RFC 7009, form-encoded).",
        "properties": {
          "token": {
            "type": "string",
            "description": "The access or refresh token to revoke."
          },
          "token_type_hint": {
            "type": "string",
            "enum": [
              "access_token",
              "refresh_token"
            ]
          },
          "client_id": {
            "type": "string",
            "description": "OAuth client identifier (public clients)."
          }
        },
        "required": [
          "token"
        ],
        "additionalProperties": false
      },
      "OAuthClientRegistrationRequest": {
        "type": "object",
        "description": "Dynamic Client Registration request (RFC 7591).",
        "properties": {
          "client_name": {
            "type": "string",
            "description": "Human-readable OAuth client name shown on the consent screen."
          },
          "redirect_uris": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string",
              "format": "uri"
            },
            "description": "Redirect URIs, compared **exactly** — no wildcards, no subdomain substitution.\nTwo forms are accepted:\n\n- **Absolute HTTPS** for web-hosted callbacks: `https://app.example.com/oauth/callback`.\n- **Loopback for native and CLI clients** (RFC 8252): `http://127.0.0.1/callback` or\n  `http://[::1]/callback`. The **port is dynamic** — it is chosen at runtime and is\n  ignored when matching, so a client does not have to register every port it may bind.\n\n`localhost` is **not** accepted as an equivalent of the loopback address: it resolves\nthrough DNS and the hosts file, so it can be pointed elsewhere. Register the literal\nIP instead. Any other plain-HTTP host is rejected.\n",
            "maxItems": 10
          },
          "grant_types": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "authorization_code",
                "refresh_token"
              ]
            },
            "default": [
              "authorization_code"
            ]
          },
          "token_endpoint_auth_method": {
            "type": "string",
            "enum": [
              "none"
            ],
            "description": "`none` declares a public client (PKCE only, no secret)."
          },
          "scope": {
            "type": "string",
            "description": "Space-separated scopes requested by the client metadata."
          },
          "client_uri": {
            "type": "string",
            "format": "uri",
            "description": "Public client home page URI."
          },
          "logo_uri": {
            "type": "string",
            "format": "uri",
            "description": "Public logo URI for consent UI."
          }
        },
        "required": [
          "client_name",
          "redirect_uris"
        ],
        "additionalProperties": false
      },
      "OAuthClientRegistrationResponse": {
        "type": "object",
        "description": "Registered client metadata. Public clients receive no `client_secret` and must use Authorization Code + PKCE.",
        "properties": {
          "client_id": {
            "type": "string"
          },
          "client_id_issued_at": {
            "type": "integer",
            "description": "Unix timestamp of registration."
          },
          "client_name": {
            "type": "string"
          },
          "redirect_uris": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "grant_types": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "authorization_code",
                "refresh_token"
              ]
            }
          },
          "token_endpoint_auth_method": {
            "type": "string",
            "enum": [
              "none"
            ]
          }
        },
        "required": [
          "client_id",
          "client_id_issued_at",
          "redirect_uris",
          "grant_types",
          "token_endpoint_auth_method"
        ],
        "additionalProperties": false
      },
      "OAuthError": {
        "type": "object",
        "description": "OAuth 2.0 error object (RFC 6749 / RFC 7591) — used only by `/oauth/*` endpoints.",
        "properties": {
          "error": {
            "type": "string",
            "description": "OAuth error code (`invalid_request`, `invalid_grant`, `invalid_client`, ...)."
          },
          "error_description": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false
      },
      "AccessibleResource": {
        "type": "object",
        "description": "One resource the current token is granted, with the scopes granted on it.",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "accessible_resource"
            ],
            "readOnly": true
          },
          "type": {
            "type": "string",
            "enum": [
              "chain",
              "location"
            ],
            "description": "The grant level. Chain-level (`chain_*`) scopes only ever apply to `chain` entries; unprefixed scopes only to `location` entries."
          },
          "id": {
            "type": "integer"
          },
          "parent_chain_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The location's main chain, when the resource is a location. A location may belong to more than one chain; this reports its main one and does not imply a grant on any chain."
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Scopes granted on this resource. Always a subset of the token scopes."
          }
        },
        "required": [
          "object",
          "type",
          "id",
          "scopes"
        ],
        "additionalProperties": false
      },
      "AccessibleResourceList": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ],
            "readOnly": true
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AccessibleResource"
            }
          }
        },
        "required": [
          "object",
          "data"
        ],
        "additionalProperties": false
      },
      "Location": {
        "type": "object",
        "description": "A business Location. `timezone`, `country_code`, and `currency` are server-controlled and read-only; `currency` applies to every money field of nested resources.",
        "properties": {
          "id": {
            "type": "integer",
            "readOnly": true
          },
          "object": {
            "type": "string",
            "enum": [
              "location"
            ],
            "readOnly": true
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "business_type_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Business category of the Location."
          },
          "address": {
            "type": [
              "string",
              "null"
            ]
          },
          "coordinates": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "latitude": {
                "type": "number"
              },
              "longitude": {
                "type": "number"
              }
            },
            "required": [
              "latitude",
              "longitude"
            ],
            "additionalProperties": false
          },
          "phones": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Business contact phone numbers in international format."
          },
          "site": {
            "type": [
              "string",
              "null"
            ]
          },
          "timezone": {
            "type": "string",
            "readOnly": true,
            "description": "Canonical IANA timezone of the Location (e.g. `Europe/Lisbon`)."
          },
          "currency": {
            "type": "string",
            "readOnly": true,
            "description": "ISO 4217 currency of the Location. Server-controlled."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "UTC."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "UTC."
          },
          "city_id": {
            "type": "integer",
            "description": "City dictionary id."
          },
          "language": {
            "type": "string",
            "description": "BCP 47 language tag."
          },
          "datetime_format": {
            "type": "string"
          },
          "unique_visit_time_interval": {
            "type": "integer",
            "description": "Visit grouping interval in seconds: `-1` groups all appointments of the day, `0` keeps each appointment separate, positive values group by interval."
          },
          "country_code": {
            "type": "string",
            "readOnly": true,
            "description": "ISO 3166-1 alpha-2 country code. Derived from city."
          }
        },
        "required": [
          "id",
          "object",
          "name",
          "timezone",
          "country_code",
          "currency",
          "city_id",
          "language",
          "datetime_format",
          "unique_visit_time_interval"
        ]
      },
      "LocationUpdate": {
        "type": "object",
        "description": "Merge-patch body for a Location. `timezone`, `country_code`, and `currency` are read-only. `city_id` can only move within the same country.",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "business_type_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "address": {
            "type": [
              "string",
              "null"
            ]
          },
          "coordinates": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "latitude": {
                "type": "number"
              },
              "longitude": {
                "type": "number"
              }
            },
            "required": [
              "latitude",
              "longitude"
            ],
            "additionalProperties": false
          },
          "phones": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "site": {
            "type": [
              "string",
              "null"
            ]
          },
          "city_id": {
            "type": "integer",
            "description": "New city in the same country."
          },
          "language": {
            "type": "string",
            "description": "BCP 47 language tag."
          },
          "datetime_format": {
            "type": "string"
          },
          "unique_visit_time_interval": {
            "type": "integer",
            "description": "Visit grouping interval in seconds: `-1` groups all appointments of the day, `0` keeps each appointment separate, positive values group by interval."
          }
        },
        "additionalProperties": false
      },
      "Service": {
        "type": "object",
        "description": "A bookable service of the Location.",
        "properties": {
          "id": {
            "type": "integer",
            "readOnly": true
          },
          "object": {
            "type": "string",
            "enum": [
              "service"
            ],
            "readOnly": true
          },
          "type": {
            "type": "string",
            "enum": [
              "simple",
              "package"
            ],
            "default": "simple",
            "description": "Service kind. Immutable after creation (`422 field_immutable`). A package service bundles several simple services of the same Location; it appears in the same list and is scheduled by the same `service_id`."
          },
          "package": {
            "$ref": "#/components/schemas/ServicePackageDetails",
            "description": "Package settings and components. Present only when `type` is `package`."
          },
          "name": {
            "type": "string"
          },
          "category_id": {
            "type": "integer",
            "description": "Service category (`listServiceCategories`)."
          },
          "duration_seconds": {
            "type": "integer",
            "description": "Default duration in seconds."
          },
          "currency": {
            "type": "string",
            "readOnly": true,
            "description": "ISO 4217, server-controlled by the Location."
          },
          "is_bookable": {
            "type": "boolean",
            "readOnly": true,
            "description": "Derived — `true` when the service has at least one bookable Team Member link."
          },
          "team_members": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ServiceTeamMemberLink"
            },
            "description": "Team Members providing this service, with optional per-member overrides.",
            "x-altegio-assumption": "Public field name follows the `team_member` glossary canon; alternatives will be resolved before contract freeze."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "UTC."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "UTC."
          },
          "price_minor": {
            "type": "integer",
            "description": "Default price in integer minor units of the Location currency."
          }
        },
        "required": [
          "id",
          "object",
          "type",
          "name",
          "category_id",
          "duration_seconds",
          "price_minor",
          "currency"
        ]
      },
      "ServiceTeamMemberLink": {
        "type": "object",
        "properties": {
          "team_member_id": {
            "type": "integer"
          },
          "duration_seconds": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Per-member duration override in seconds. `null` uses the service default."
          },
          "price_minor": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Per-member price override, integer minor units. `null` uses the service default."
          },
          "technological_card_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Per-link technological card override; `null` uses the service default."
          },
          "is_bookable": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Per-link online scheduling flag override; `null` uses the service default."
          },
          "online_booking_enabled": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Per-link public scheduling availability override; `null` uses the service default."
          }
        },
        "required": [
          "team_member_id"
        ],
        "additionalProperties": false
      },
      "TeamMemberServiceLink": {
        "type": "object",
        "description": "Service link seen from the Team Member side; carries the same per-link overrides as `ServiceTeamMemberLink`.",
        "properties": {
          "service_id": {
            "type": "integer"
          },
          "duration_seconds": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Per-member duration override in seconds. `null` uses the service default."
          },
          "price_minor": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Per-member price override, integer minor units. `null` uses the service default."
          },
          "technological_card_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Per-link technological card override; `null` uses the service default."
          },
          "is_bookable": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Per-link online scheduling flag override; `null` uses the service default."
          },
          "online_booking_enabled": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Per-link public scheduling availability override; `null` uses the service default."
          }
        },
        "required": [
          "service_id"
        ],
        "additionalProperties": false
      },
      "ServiceCreate": {
        "type": "object",
        "description": "Request body for `createService`. Use the schema selector to switch between the `simple` and `package` variants. In the right code panel, switch the example dropdown between `simple` and `package` to view the corresponding curl payload.",
        "oneOf": [
          {
            "$ref": "#/components/schemas/SimpleServiceCreate"
          },
          {
            "$ref": "#/components/schemas/PackageServiceCreate"
          }
        ],
        "discriminator": {
          "propertyName": "type",
          "mapping": {
            "simple": "#/components/schemas/SimpleServiceCreate",
            "package": "#/components/schemas/PackageServiceCreate"
          }
        }
      },
      "SimpleServiceCreate": {
        "type": "object",
        "description": "A regular bookable service.",
        "properties": {
          "name": {
            "type": "string"
          },
          "category_id": {
            "type": "integer"
          },
          "type": {
            "type": "string",
            "enum": [
              "simple"
            ],
            "example": "simple",
            "description": "Service kind. Immutable after creation."
          },
          "duration_seconds": {
            "type": "integer"
          },
          "team_members": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ServiceTeamMemberLink"
            }
          },
          "price_minor": {
            "type": "integer",
            "description": "Integer minor units of the Location currency."
          }
        },
        "required": [
          "name",
          "category_id",
          "type"
        ],
        "additionalProperties": false
      },
      "PackageServiceCreate": {
        "type": "object",
        "description": "A complex service package made of simple-service components.",
        "properties": {
          "name": {
            "type": "string"
          },
          "category_id": {
            "type": "integer"
          },
          "type": {
            "type": "string",
            "enum": [
              "package"
            ],
            "example": "package",
            "description": "Service kind. Immutable after creation."
          },
          "duration_seconds": {
            "type": "integer"
          },
          "package": {
            "$ref": "#/components/schemas/ServicePackageDetails",
            "description": "Required for package services; lists the simple-service components."
          },
          "team_members": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ServiceTeamMemberLink"
            }
          },
          "price_minor": {
            "type": "integer",
            "description": "Integer minor units of the Location currency."
          }
        },
        "required": [
          "name",
          "category_id",
          "type",
          "package"
        ],
        "additionalProperties": false
      },
      "ServiceUpdate": {
        "type": "object",
        "description": "Merge-patch body for a service. `team_members`, when present, replaces the full link set (`[]` unlinks everyone). `type` is immutable (`422 field_immutable`). For package services, `package.components`, when present, replaces the full component list.",
        "properties": {
          "name": {
            "type": "string"
          },
          "category_id": {
            "type": "integer"
          },
          "duration_seconds": {
            "type": "integer"
          },
          "team_members": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ServiceTeamMemberLink"
            }
          },
          "package": {
            "$ref": "#/components/schemas/ServicePackageDetails",
            "description": "Only for package services; `components` replaces the whole list."
          },
          "price_minor": {
            "type": "integer",
            "description": "Integer minor units of the Location currency."
          }
        },
        "additionalProperties": false
      },
      "ServicePackageDetails": {
        "type": "object",
        "description": "Package-specific settings; present only on services with `type: package`.",
        "x-altegio-assumption": "Package components may contain one component today and leave the package not_configured; minimum publishable component count must be confirmed before freeze.",
        "properties": {
          "exec_order": {
            "type": "string",
            "enum": [
              "sequential",
              "sequential_multi",
              "parallel"
            ],
            "default": "sequential",
            "description": "Order in which component services are performed."
          },
          "pricing_type": {
            "type": "string",
            "enum": [
              "sum",
              "manual",
              "discount"
            ],
            "default": "sum",
            "description": "How the package price derives from its components."
          },
          "price_discount_percent": {
            "type": "number",
            "default": 0,
            "description": "Discount applied to the component sum; used only when `pricing_type` is `discount`, ignored otherwise."
          },
          "components": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/ServicePackageComponent"
            },
            "description": "Component services in execution order. On PATCH the array is replaced as a whole (merge-patch array semantics).",
            "maxItems": 10
          },
          "status": {
            "type": "string",
            "enum": [
              "not_configured",
              "ready"
            ],
            "readOnly": true,
            "description": "Read-only package configuration status."
          }
        },
        "required": [
          "components"
        ],
        "additionalProperties": false
      },
      "ServicePackageComponent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "readOnly": true,
            "description": "Stable package-component identifier used by availability and package appointment creation."
          },
          "service_id": {
            "type": "integer",
            "description": "A `simple` service of the same Location."
          },
          "position": {
            "type": "integer",
            "description": "1-based order of the component."
          }
        },
        "required": [
          "service_id"
        ],
        "additionalProperties": false
      },
      "ServiceCategory": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "readOnly": true
          },
          "object": {
            "type": "string",
            "enum": [
              "service_category"
            ],
            "readOnly": true
          },
          "name": {
            "type": "string"
          },
          "weight": {
            "type": "integer",
            "description": "Ordering weight (higher sorts first)."
          }
        },
        "required": [
          "id",
          "object",
          "name"
        ]
      },
      "ServiceCategoryCreate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "weight": {
            "type": "integer"
          }
        },
        "required": [
          "name"
        ],
        "additionalProperties": false
      },
      "ServiceCategoryUpdate": {
        "type": "object",
        "description": "Merge-patch body for a service category.",
        "properties": {
          "name": {
            "type": "string"
          },
          "weight": {
            "type": "integer"
          }
        },
        "additionalProperties": false
      },
      "TeamMember": {
        "type": "object",
        "description": "A Team Member of the Location. Personal contact data of Team Members is not exposed in the preview scope.",
        "properties": {
          "id": {
            "type": "integer",
            "readOnly": true
          },
          "object": {
            "type": "string",
            "enum": [
              "team_member"
            ],
            "readOnly": true
          },
          "name": {
            "type": "string"
          },
          "specialization": {
            "type": "string",
            "description": "Public profession label shown to clients (for example in online booking)."
          },
          "position_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Position from `listPositions`."
          },
          "service_ids": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Services this Team Member can be scheduled for."
          },
          "is_bookable": {
            "type": "boolean",
            "readOnly": true,
            "description": "Derived — whether the member can currently receive appointments."
          },
          "is_billable": {
            "type": "boolean",
            "description": "Whether this Team Member occupies a paid seat in the Location's licence, which is what\nthe Location is charged for. A non-billable member cannot hold schedule access, so\n`is_bookable` is always `false` for one. Locations still on the older licensing model\nreport `false` here and ignore the field on write — there the paid seat count follows\nschedule access instead."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "UTC."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "UTC."
          }
        },
        "required": [
          "id",
          "object",
          "name",
          "specialization"
        ]
      },
      "TeamMemberCreate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "specialization": {
            "type": "string",
            "minLength": 1,
            "description": "Public profession label shown to clients (for example in online booking)."
          },
          "position_id": {
            "type": "integer"
          },
          "services": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TeamMemberServiceLink"
            },
            "description": "Initial service links, the same link shape as on update."
          },
          "access_invitation": {
            "type": "object",
            "description": "Optional - atomically invite this Team Member to a system-access role on creation (role\nfrom `listLocationAccessRoles`). Requires an Authorization Code token with\n`team_members:manage_access` in addition to `team_members:write`; Client Credentials\nand restricted keys cannot use this convenience in the first iteration.\n\nAt least one of `phone` or `email` is required: the invitation is delivered to that\naddress, and without it `sendTeamMemberInvitation` fails with\n`422 invitation_contact_required`. These are the invited person's login contacts, used\nfor delivery only - they are write-only and are never returned by team member reads.",
            "properties": {
              "role_id": {
                "type": "integer"
              },
              "phone": {
                "type": "string",
                "writeOnly": true,
                "description": "International format. Delivery address for the invitation."
              },
              "email": {
                "type": "string",
                "format": "email",
                "writeOnly": true,
                "description": "Delivery address for the invitation."
              }
            },
            "required": [
              "role_id"
            ],
            "additionalProperties": false,
            "x-altegio-assumption": "Invitation delivery contact is modelled as write-only `phone`/`email` mapped onto the existing quick-create path (`user_phone`, `user_email`, `is_user_invite`). Whether the public contract should instead expose a separate team-member contact resource is open."
          },
          "is_billable": {
            "type": "boolean",
            "default": false,
            "description": "Whether the new Team Member takes a paid seat in the Location's licence. Defaults to\n`false`, so a plain create never changes what the Location is charged: the member is\ncreated without schedule access and is not bookable. Send `true` to create a bookable\nmember and accept the licence charge — the create fails with `422` when the licence has\nno free paid seat. Locations on the older licensing model ignore this field."
          }
        },
        "required": [
          "name",
          "specialization"
        ],
        "additionalProperties": false
      },
      "TeamMemberUpdate": {
        "type": "object",
        "description": "Merge-patch body for a Team Member. `services`, when present, replaces the full set of service links with the same override fields as on Service, keyed by `service_id`. Missing `services` leaves links unchanged; `services: []` unlinks all services; nullable override fields inside each link use `null` to fall back to the service default. Updating links moves the ETag on both the Team Member and affected Service resources.",
        "properties": {
          "name": {
            "type": "string"
          },
          "specialization": {
            "type": "string",
            "minLength": 1,
            "description": "Public profession label shown to clients (for example in online booking)."
          },
          "position_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "services": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TeamMemberServiceLink"
            }
          },
          "is_billable": {
            "type": "boolean",
            "description": "Moves the Team Member on or off a paid seat in the Location's licence, changing what the\nLocation is charged. Turning it on fails with `422` when the licence has no free paid\nseat; turning it off also drops schedule access, because a non-billable member cannot be\nbookable. Locations on the older licensing model ignore this field."
          }
        },
        "additionalProperties": false
      },
      "ScheduleSlot": {
        "type": "object",
        "properties": {
          "from": {
            "type": "string",
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$",
            "description": "Start of the working interval, wall-clock `HH:MM` in the Location timezone."
          },
          "to": {
            "type": "string",
            "pattern": "^(([01][0-9]|2[0-3]):[0-5][0-9]|24:00)$",
            "description": "End of the working interval, wall-clock `HH:MM` in the Location timezone. `24:00` denotes end of day, for shifts running until midnight."
          }
        },
        "required": [
          "from",
          "to"
        ],
        "additionalProperties": false
      },
      "ScheduleDay": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "slots": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ScheduleSlot"
            },
            "description": "Working intervals of the day; empty means a day off."
          }
        },
        "required": [
          "date",
          "slots"
        ]
      },
      "ScheduleDayPatch": {
        "type": "object",
        "properties": {
          "slots": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ScheduleSlot"
            }
          }
        },
        "required": [
          "slots"
        ],
        "additionalProperties": false
      },
      "TeamMemberSchedulePatch": {
        "type": "object",
        "description": "Merge-patch map keyed by `YYYY-MM-DD` date. A present date replaces that day's working slots; `null` clears the day. Dates absent from the body are unchanged.",
        "patternProperties": {
          "^[0-9]{4}-[0-9]{2}-[0-9]{2}$": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ScheduleDayPatch"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "additionalProperties": false
      },
      "Position": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "readOnly": true
          },
          "object": {
            "type": "string",
            "enum": [
              "position"
            ],
            "readOnly": true
          },
          "title": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "object",
          "title"
        ]
      },
      "PositionCreate": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          }
        },
        "required": [
          "title"
        ],
        "additionalProperties": false
      },
      "PositionUpdate": {
        "type": "object",
        "description": "Merge-patch body for a position.",
        "properties": {
          "title": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "Client": {
        "type": "object",
        "description": "A client of the Location. Contact fields (`phone`, `email`, `additional_phone`) are PII: without `clients:read_contact` plus the subject contact permission they are returned as `null` and their names are listed in `redacted_fields` instead of exposing a contact string.",
        "x-altegio-assumption": "Visit-statistics fields (visits_count, first/last visit, sold_amount_minor) are a preview draft; their final set will be confirmed before freeze.",
        "properties": {
          "id": {
            "type": "integer",
            "readOnly": true
          },
          "object": {
            "type": "string",
            "enum": [
              "client"
            ],
            "readOnly": true
          },
          "name": {
            "type": "string"
          },
          "surname": {
            "type": [
              "string",
              "null"
            ]
          },
          "patronymic": {
            "type": [
              "string",
              "null"
            ]
          },
          "display_name": {
            "type": "string",
            "readOnly": true
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "International phone number. `null` when absent or withheld — see `redacted_fields`."
          },
          "additional_phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Additional phone number. `null` when absent or withheld — see `redacted_fields`."
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "Email address. `null` when absent or withheld — see `redacted_fields`."
          },
          "birth_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "comment": {
            "type": [
              "string",
              "null"
            ],
            "description": "Private note about the client; requires comment access."
          },
          "discount": {
            "type": [
              "number",
              "null"
            ],
            "description": "Personal discount, percent."
          },
          "visits_count": {
            "type": "integer",
            "readOnly": true
          },
          "first_visit_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "readOnly": true,
            "description": "Location-local offset."
          },
          "last_visit_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "readOnly": true,
            "description": "Location-local offset."
          },
          "currency": {
            "type": "string",
            "readOnly": true,
            "description": "ISO 4217, server-controlled by the Location."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "UTC."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "UTC."
          },
          "sold_amount_minor": {
            "type": "integer",
            "readOnly": true,
            "description": "Lifetime paid total, integer minor units of the Location currency."
          },
          "redacted_fields": {
            "type": "array",
            "readOnly": true,
            "items": {
              "type": "string"
            },
            "description": "Names of fields withheld by the PII policy for this object, for example\n`[\"phone\", \"email\"]`. A withheld contact is always returned as `null` — never as a\nmasked or empty string, so an integration cannot write a mask back. An empty array\nmeans nothing was withheld and a `null` value simply means no value is stored.\n"
          }
        },
        "required": [
          "id",
          "object",
          "name",
          "phone"
        ]
      },
      "ClientCreate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "phone": {
            "type": "string",
            "description": "International format. Deduplicated within the Location (`422 phone_taken`)."
          },
          "surname": {
            "type": "string"
          },
          "patronymic": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "additional_phone": {
            "type": "string"
          },
          "birth_date": {
            "type": "string",
            "format": "date"
          },
          "comment": {
            "type": "string"
          },
          "discount": {
            "type": "number",
            "description": "Personal discount, percent."
          }
        },
        "required": [
          "name",
          "phone"
        ],
        "additionalProperties": false
      },
      "ClientUpdate": {
        "type": "object",
        "description": "Merge-patch body for a client. `null` clears a nullable field.",
        "properties": {
          "name": {
            "type": "string"
          },
          "phone": {
            "type": "string",
            "description": "`422 phone_taken` when owned by another client."
          },
          "surname": {
            "type": [
              "string",
              "null"
            ]
          },
          "patronymic": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "additional_phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "birth_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "comment": {
            "type": [
              "string",
              "null"
            ]
          },
          "discount": {
            "type": [
              "number",
              "null"
            ]
          }
        },
        "additionalProperties": false
      },
      "AvailabilitySlot": {
        "type": "object",
        "description": "A computed free appointment slot. Not addressable — slots have no id.",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "availability_slot"
            ]
          },
          "team_member_id": {
            "type": "integer"
          },
          "starts_at": {
            "type": "string",
            "format": "date-time",
            "description": "RFC 3339 with the Location's local UTC offset; pass directly to `createAppointment`."
          },
          "is_bookable": {
            "type": "boolean"
          },
          "duration_seconds": {
            "type": "integer",
            "description": "Slot duration in seconds."
          },
          "resource_instance_ids": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Resource instances occupied by the slot."
          },
          "components": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AvailabilityPackageSegment"
            },
            "description": "For package services, per-component scheduling segments in execution order."
          }
        },
        "required": [
          "object",
          "team_member_id",
          "starts_at",
          "is_bookable"
        ]
      },
      "Resource": {
        "type": "object",
        "description": "A bookable resource of the Location with its instances.",
        "properties": {
          "id": {
            "type": "integer",
            "readOnly": true
          },
          "object": {
            "type": "string",
            "enum": [
              "resource"
            ],
            "readOnly": true
          },
          "title": {
            "type": "string"
          },
          "instances": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResourceInstance"
            }
          }
        },
        "required": [
          "id",
          "object",
          "title"
        ]
      },
      "ResourceInstance": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "object": {
            "type": "string",
            "enum": [
              "resource_instance"
            ]
          },
          "title": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "object",
          "title"
        ]
      },
      "AppointmentStatus": {
        "type": "string",
        "description": "Status of an appointment. `waiting` — scheduled, not yet confirmed; `confirmed` — confirmed by the client; `arrived` — the client attended (terminal \"done\" state); `no_show` — the client did not attend; `cancelled` — cancelled via the cancel command (never set through the status command).",
        "enum": [
          "waiting",
          "confirmed",
          "arrived",
          "no_show",
          "cancelled"
        ]
      },
      "Appointment": {
        "type": "object",
        "description": "A scheduled appointment. `visit_id` remains a response field and can be `null`; payment and visit operations are keyed by `appointment_id`.",
        "properties": {
          "id": {
            "type": "integer",
            "readOnly": true
          },
          "object": {
            "type": "string",
            "enum": [
              "appointment"
            ],
            "readOnly": true
          },
          "location_id": {
            "type": "integer",
            "readOnly": true
          },
          "client_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "`null` only on appointments without a client."
          },
          "visit_id": {
            "type": [
              "integer",
              "null"
            ],
            "readOnly": true,
            "description": "Billing visit of this appointment."
          },
          "team_member_id": {
            "type": "integer"
          },
          "service_ids": {
            "type": "array",
            "items": {
              "type": "integer"
            }
          },
          "resource_instance_ids": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Resource instances occupied by the appointment."
          },
          "status": {
            "$ref": "#/components/schemas/AppointmentStatus"
          },
          "starts_at": {
            "type": "string",
            "format": "date-time",
            "description": "RFC 3339 with the Location's local UTC offset."
          },
          "ends_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "RFC 3339 with the Location's local UTC offset."
          },
          "duration_seconds": {
            "type": "integer"
          },
          "is_paid": {
            "type": "boolean",
            "readOnly": true,
            "description": "Whether the visit of this appointment is fully paid."
          },
          "comment": {
            "type": [
              "string",
              "null"
            ]
          },
          "cancelled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "readOnly": true,
            "description": "UTC; set by the cancel command."
          },
          "cancellation_reason": {
            "type": [
              "string",
              "null"
            ],
            "readOnly": true
          },
          "client": {
            "description": "Present only when requested with `expand[]=client`; PII rules apply.",
            "readOnly": true,
            "$ref": "#/components/schemas/Client"
          },
          "team_member": {
            "description": "Present only when requested with `expand[]=team_member`.",
            "readOnly": true,
            "$ref": "#/components/schemas/TeamMember"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "UTC."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "UTC."
          }
        },
        "required": [
          "id",
          "object",
          "location_id",
          "team_member_id",
          "service_ids",
          "status",
          "starts_at",
          "duration_seconds"
        ]
      },
      "AppointmentInlineClient": {
        "type": "object",
        "description": "Inline client for `createAppointment`: attach-only resolver links an existing client by normalized phone without changing the client record, or creates a new client when none exists. It never restores a deleted client and the response does not reveal whether the client was found or created. No `clients:write` scope is required.",
        "properties": {
          "name": {
            "type": "string"
          },
          "phone": {
            "type": "string",
            "description": "International format."
          },
          "email": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "phone"
        ],
        "additionalProperties": false
      },
      "AppointmentCreate": {
        "type": "object",
        "description": "Create an appointment. A client is optional; pass an existing `client_id`, inline `client` data for attach-only resolve-or-create by normalized phone, or neither for a clientless appointment. Package services pass `components[{package_component_id, team_member_id}]` matching availability segments.",
        "properties": {
          "client_id": {
            "type": "integer",
            "description": "An existing client of the Location."
          },
          "client": {
            "$ref": "#/components/schemas/AppointmentInlineClient"
          },
          "team_member_id": {
            "type": "integer"
          },
          "service_ids": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "integer"
            }
          },
          "resource_instance_ids": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Required by services that occupy a resource (`listResources`)."
          },
          "starts_at": {
            "type": "string",
            "format": "date-time",
            "description": "RFC 3339 with the Location's local UTC offset (take it from availability)."
          },
          "duration_seconds": {
            "type": "integer",
            "description": "Optional override; defaults to the sum of service durations."
          },
          "comment": {
            "type": "string"
          },
          "components": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AppointmentPackageComponentCreate"
            },
            "description": "For package services, selected Team Member per package component."
          }
        },
        "required": [
          "team_member_id",
          "service_ids",
          "starts_at"
        ],
        "additionalProperties": false
      },
      "AppointmentUpdate": {
        "type": "object",
        "description": "Merge-patch body for reschedule/edit. The appointment status is changed through the `status` command; cancellation through the `cancel` command.",
        "properties": {
          "starts_at": {
            "type": "string",
            "format": "date-time",
            "description": "RFC 3339 with the Location's local UTC offset."
          },
          "team_member_id": {
            "type": "integer"
          },
          "service_ids": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "integer"
            }
          },
          "resource_instance_ids": {
            "type": "array",
            "items": {
              "type": "integer"
            }
          },
          "duration_seconds": {
            "type": "integer"
          },
          "comment": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "additionalProperties": false
      },
      "AppointmentCancelRequest": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "description": "Free-form cancellation reason stored on the appointment."
          },
          "notify_client": {
            "type": "boolean",
            "description": "Whether to send the client a cancellation notification."
          }
        },
        "additionalProperties": false
      },
      "AppointmentStatusRequest": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "description": "Target status. `cancelled` cannot be set here — use the cancel command.",
            "enum": [
              "waiting",
              "confirmed",
              "arrived",
              "no_show"
            ]
          }
        },
        "required": [
          "status"
        ],
        "additionalProperties": false
      },
      "PaymentMethod": {
        "type": "object",
        "description": "A payment method configured for the Location - the cashier setup: what the business accepts\nand how it settles. Returned by `listPaymentMethods`, and it is the only source of\n`payment_method_id` for `createPayment`.\n\nThis schema does not describe whether the method can be used for a particular visit. For\nthat, read `visit.payment_methods` or search an instrument by number - both return\n`ApplicablePaymentMethod`.",
        "properties": {
          "id": {
            "type": "integer",
            "readOnly": true
          },
          "object": {
            "type": "string",
            "enum": [
              "payment_method"
            ],
            "readOnly": true
          },
          "slug": {
            "type": "string",
            "description": "Method kind (e.g. `cash`, `card`, `cashless`)."
          },
          "name": {
            "type": "string"
          },
          "default_account_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Account the method settles to unless overridden in the payment."
          },
          "fee_rate": {
            "type": [
              "number",
              "null"
            ],
            "description": "Acquiring fee, percent.",
            "x-altegio-assumption": "Fee percentage remains an open payment-method display question before freeze."
          },
          "card_brands": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CardBrand"
            }
          },
          "installments": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Available installment counts; empty when installments are not supported."
          }
        },
        "required": [
          "id",
          "object",
          "slug",
          "name"
        ],
        "additionalProperties": false
      },
      "ApplicablePaymentMethod": {
        "type": "object",
        "description": "A way to pay **this** visit, with applicability resolved against it: a configured method of\nthe Location, a cash account, the client's deposit, or one of the client's loyalty\ninstruments. Returned inside `visit.payment_methods` and by `searchLoyaltyInstrument`.\n\nWhich identifier to send to `createPayment` follows `type`: `payment_method` uses\n`payment_method_id`, `account` uses `account_id`, and the loyalty types use their own\ninstrument id together with `number` where the instrument is addressed by number.",
        "properties": {
          "id": {
            "type": "integer",
            "readOnly": true,
            "description": "Identifier of the underlying method or instrument, in the space implied by `type`."
          },
          "object": {
            "type": "string",
            "enum": [
              "applicable_payment_method"
            ],
            "readOnly": true
          },
          "type": {
            "type": "string",
            "description": "Which kind of payment this is, and therefore which identifier `createPayment` expects.",
            "enum": [
              "payment_method",
              "account",
              "deposit",
              "loyalty_card",
              "loyalty_program",
              "gift_card",
              "membership",
              "referral_program"
            ]
          },
          "name": {
            "type": "string"
          },
          "payment_method_id": {
            "type": [
              "integer",
              "null"
            ],
            "readOnly": true,
            "description": "Set when `type` is `payment_method`; pass it to `createPayment`."
          },
          "account_id": {
            "type": [
              "integer",
              "null"
            ],
            "readOnly": true,
            "description": "Settlement account, when the type settles to one."
          },
          "number": {
            "type": [
              "string",
              "null"
            ],
            "description": "Instrument number, for gift cards and memberships addressed by number."
          },
          "is_applicable": {
            "type": "boolean",
            "readOnly": true,
            "description": "Whether this visit can be paid with it right now."
          },
          "applicable_amount_minor": {
            "type": [
              "integer",
              "null"
            ],
            "readOnly": true,
            "description": "How much of this visit it can cover, in integer minor units. `0` for a membership, which covers sessions rather than money."
          },
          "is_partial_allowed": {
            "type": "boolean",
            "readOnly": true,
            "description": "Whether it may cover part of the amount, leaving a remainder for another payment."
          }
        },
        "required": [
          "id",
          "object",
          "type",
          "name",
          "is_applicable"
        ],
        "additionalProperties": false
      },
      "CardBrand": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "title": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "title"
        ]
      },
      "Account": {
        "type": "object",
        "description": "A cash/settlement account of the Location.",
        "properties": {
          "id": {
            "type": "integer",
            "readOnly": true
          },
          "object": {
            "type": "string",
            "enum": [
              "account"
            ],
            "readOnly": true
          },
          "title": {
            "type": "string"
          },
          "type_slug": {
            "type": "string",
            "enum": [
              "cash",
              "cashless"
            ]
          },
          "is_acquiring": {
            "type": "boolean",
            "description": "Whether the account belongs to online acquiring."
          }
        },
        "required": [
          "id",
          "object",
          "title",
          "type_slug"
        ]
      },
      "VisitItem": {
        "type": "object",
        "description": "A line item of a visit.",
        "properties": {
          "id": {
            "type": "integer"
          },
          "object": {
            "type": "string",
            "enum": [
              "visit_item"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "service",
              "product"
            ]
          },
          "service_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Set for `service` items."
          },
          "product_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Set for `product` items."
          },
          "title": {
            "type": "string"
          },
          "quantity": {
            "type": "integer"
          },
          "price_minor": {
            "type": "integer",
            "description": "Unit price in integer minor units."
          },
          "amount_minor": {
            "type": "integer",
            "description": "Line total in integer minor units."
          }
        },
        "required": [
          "id",
          "object",
          "type",
          "title",
          "quantity",
          "price_minor",
          "amount_minor"
        ],
        "additionalProperties": false
      },
      "VisitItemCreate": {
        "type": "object",
        "description": "One product line to append to a visit. Service lines come from the appointment and cannot be created here.",
        "properties": {
          "product_id": {
            "type": "integer",
            "description": "Product identifier from `listProducts`."
          },
          "quantity": {
            "type": "integer",
            "minimum": 1,
            "default": 1
          },
          "price_minor": {
            "type": "integer",
            "description": "Unit price override in integer minor units. Omit to use the catalog price; overriding requires the discount permission and is rejected without it."
          }
        },
        "required": [
          "product_id"
        ],
        "additionalProperties": false
      },
      "Refund": {
        "type": "object",
        "description": "A full local reversal of a payment action. This is not a payment-provider refund contract.",
        "properties": {
          "id": {
            "type": "string",
            "readOnly": true
          },
          "object": {
            "type": "string",
            "enum": [
              "refund"
            ],
            "readOnly": true
          },
          "payment_id": {
            "type": "string",
            "readOnly": true
          },
          "amount_minor": {
            "type": "integer",
            "description": "Reversed amount in minor units."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "UTC."
          }
        },
        "required": [
          "id",
          "object",
          "payment_id",
          "amount_minor"
        ],
        "additionalProperties": false
      },
      "Payment": {
        "type": "object",
        "description": "Opaque payment action registered against an appointment's visit. `id` is stable between requests: `t<number>` for money payments and `l<number>` for loyalty/deposit operations.",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[tl][0-9]+$",
            "readOnly": true
          },
          "object": {
            "type": "string",
            "enum": [
              "payment"
            ],
            "readOnly": true
          },
          "visit_id": {
            "type": [
              "integer",
              "null"
            ],
            "readOnly": true
          },
          "type": {
            "type": "string",
            "enum": [
              "payment_method",
              "account",
              "deposit",
              "loyalty_card",
              "loyalty_program",
              "gift_card",
              "membership",
              "referral_program",
              "membership_auto",
              "prepaid_hold"
            ],
            "description": "`membership_auto` and `prepaid_hold` are read-only and never accepted by PaymentCreate."
          },
          "amount_minor": {
            "type": "integer",
            "description": "Payment amount in minor units. For `membership`, this is `0`; covered items are listed in `covers_items`."
          },
          "fee_minor": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Acquiring fee represented inside the payment; it is not exposed as a separate payment."
          },
          "currency": {
            "type": "string",
            "readOnly": true,
            "description": "ISO 4217, server-controlled by the Location."
          },
          "account_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "payment_method_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "completed",
              "refunded"
            ]
          },
          "covers_items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PaymentCoversItem"
            }
          },
          "card_brand_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "installment_count": {
            "type": [
              "integer",
              "null"
            ]
          },
          "refund": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Refund"
              },
              {
                "type": "null"
              }
            ],
            "readOnly": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "UTC."
          }
        },
        "required": [
          "id",
          "object",
          "type",
          "amount_minor",
          "currency",
          "status",
          "created_at"
        ],
        "additionalProperties": false
      },
      "PaymentCreate": {
        "description": "Payment request. `type` selects the variant and therefore which identifier is expected;\nevery identifier comes from `visit.payment_methods` or `searchLoyaltyInstrument`, so a\nclient never has to guess one.\n\nInstruments that determine their own amount (`membership`, `loyalty_program`) take no\n`amount_minor`: a membership spends sessions and a loyalty program computes its own\ndiscount. `membership_auto` and `prepaid_hold` are read-only types and are not accepted.",
        "oneOf": [
          {
            "type": "object",
            "title": "payment_method",
            "description": "Cash, card terminal or cashless through a method configured for the Location.",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "payment_method"
                ]
              },
              "payment_method_id": {
                "type": "integer",
                "description": "From `listPaymentMethods`."
              },
              "amount_minor": {
                "type": "integer"
              },
              "account_id": {
                "type": "integer",
                "description": "Account the payment settles to."
              },
              "card_brand_id": {
                "type": "integer"
              },
              "installment_count": {
                "type": "integer"
              }
            },
            "required": [
              "type",
              "payment_method_id",
              "amount_minor",
              "account_id"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "title": "account",
            "description": "Straight to a cash or cashless account, without a configured method.",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "account"
                ]
              },
              "account_id": {
                "type": "integer"
              },
              "amount_minor": {
                "type": "integer"
              }
            },
            "required": [
              "type",
              "account_id",
              "amount_minor"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "title": "deposit",
            "description": "From the client's deposit balance. A client may hold more than one deposit, so the deposit is named explicitly.",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "deposit"
                ]
              },
              "deposit_id": {
                "type": "integer",
                "description": "From `visit.payment_methods`."
              },
              "amount_minor": {
                "type": "integer"
              }
            },
            "required": [
              "type",
              "deposit_id",
              "amount_minor"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "title": "loyalty_card",
            "description": "From the balance of a loyalty card. Clients commonly hold cards in several chains, so the card is named explicitly.",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "loyalty_card"
                ]
              },
              "loyalty_card_id": {
                "type": "integer",
                "description": "From `visit.payment_methods`."
              },
              "amount_minor": {
                "type": "integer"
              }
            },
            "required": [
              "type",
              "loyalty_card_id",
              "amount_minor"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "title": "loyalty_program",
            "description": "Applies a loyalty program to the visit. The program computes the amount, so none is sent. Both the card and the program are named because one card can carry several programs.",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "loyalty_program"
                ]
              },
              "loyalty_card_id": {
                "type": "integer"
              },
              "loyalty_program_id": {
                "type": "integer"
              }
            },
            "required": [
              "type",
              "loyalty_card_id",
              "loyalty_program_id"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "title": "gift_card",
            "description": "Pays with a gift card. Identify it by `number`, or by `gift_card_id` when the card carries no code.",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "gift_card"
                ]
              },
              "number": {
                "type": "string"
              },
              "gift_card_id": {
                "type": "integer",
                "description": "From `visit.payment_methods` or `searchLoyaltyInstrument`."
              },
              "amount_minor": {
                "type": "integer"
              }
            },
            "required": [
              "type",
              "amount_minor"
            ],
            "anyOf": [
              {
                "required": [
                  "number"
                ],
                "properties": {
                  "number": {
                    "type": "string"
                  }
                }
              },
              {
                "required": [
                  "gift_card_id"
                ],
                "properties": {
                  "gift_card_id": {
                    "type": "integer"
                  }
                }
              }
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "title": "membership",
            "description": "Spends a membership session. No `amount_minor` - sessions are spent, not money. Identify it by `number`, or by `membership_id` when the membership carries no code (an empty code is allowed by membership settings).",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "membership"
                ]
              },
              "number": {
                "type": "string"
              },
              "membership_id": {
                "type": "integer",
                "description": "From `visit.payment_methods` or `searchLoyaltyInstrument`."
              }
            },
            "required": [
              "type"
            ],
            "anyOf": [
              {
                "required": [
                  "number"
                ],
                "properties": {
                  "number": {
                    "type": "string"
                  }
                }
              },
              {
                "required": [
                  "membership_id"
                ],
                "properties": {
                  "membership_id": {
                    "type": "integer"
                  }
                }
              }
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "title": "referral_program",
            "description": "Applies a referral program. A client may be eligible under several referral programs from different chains, so the program is named alongside the referrer.",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "referral_program"
                ]
              },
              "referral_program_id": {
                "type": "integer",
                "description": "From `visit.payment_methods`."
              },
              "referrer_phone": {
                "type": "string"
              },
              "amount_minor": {
                "type": "integer"
              }
            },
            "required": [
              "type",
              "referral_program_id",
              "referrer_phone",
              "amount_minor"
            ],
            "additionalProperties": false
          }
        ],
        "discriminator": {
          "propertyName": "type"
        }
      },
      "Visit": {
        "type": "object",
        "description": "Billing state resolved from an appointment: service and products, applicable payment methods, payments from money and loyalty sources, and `amount_to_pay_minor`.",
        "properties": {
          "id": {
            "type": "integer",
            "readOnly": true
          },
          "object": {
            "type": "string",
            "enum": [
              "visit"
            ],
            "readOnly": true
          },
          "location_id": {
            "type": "integer",
            "readOnly": true
          },
          "appointment_ids": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "readOnly": true,
            "description": "Appointments grouped into this visit.",
            "x-altegio-assumption": "Exposing the appointment ids of a visit is a preview convenience pending the visit slice implementation. Must be resolved before contract freeze."
          },
          "status": {
            "type": "string",
            "enum": [
              "waiting",
              "confirmed",
              "arrived",
              "no_show"
            ],
            "description": "Status of the visit."
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VisitItem"
            }
          },
          "payments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Payment"
            }
          },
          "currency": {
            "type": "string",
            "readOnly": true,
            "description": "ISO 4217, server-controlled by the Location."
          },
          "client": {
            "description": "Present only when requested with `expand[]=client`; PII rules apply.",
            "readOnly": true,
            "$ref": "#/components/schemas/Client"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "UTC."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "UTC."
          },
          "amount_to_pay_minor": {
            "type": "integer",
            "readOnly": true,
            "description": "Remaining unpaid amount in integer minor units. `0` means fully paid or free."
          },
          "payment_methods": {
            "type": "array",
            "description": "Ways to pay this visit, with applicability resolved against it and identifiers sufficient for `createPayment`. Only instruments attached to the client appear here; a gift card or membership held by number is found with `searchLoyaltyInstrument`.",
            "items": {
              "$ref": "#/components/schemas/ApplicablePaymentMethod"
            }
          }
        },
        "required": [
          "id",
          "object",
          "status",
          "items",
          "payment_methods",
          "payments",
          "amount_to_pay_minor",
          "currency"
        ]
      },
      "ServiceList": {
        "description": "Cursor-paginated list of services.",
        "allOf": [
          {
            "$ref": "#/components/schemas/ListEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Service"
                }
              }
            },
            "required": [
              "data"
            ]
          }
        ]
      },
      "ServiceCategoryList": {
        "description": "Cursor-paginated list of service categories.",
        "allOf": [
          {
            "$ref": "#/components/schemas/ListEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ServiceCategory"
                }
              }
            },
            "required": [
              "data"
            ]
          }
        ]
      },
      "TeamMemberList": {
        "description": "Cursor-paginated list of Team Members.",
        "allOf": [
          {
            "$ref": "#/components/schemas/ListEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/TeamMember"
                }
              }
            },
            "required": [
              "data"
            ]
          }
        ]
      },
      "TeamMemberSchedule": {
        "description": "Working days of a Team Member within the requested range.",
        "allOf": [
          {
            "$ref": "#/components/schemas/ListEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ScheduleDay"
                }
              }
            },
            "required": [
              "data"
            ]
          }
        ]
      },
      "PositionList": {
        "description": "Cursor-paginated list of positions.",
        "allOf": [
          {
            "$ref": "#/components/schemas/ListEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Position"
                }
              }
            },
            "required": [
              "data"
            ]
          }
        ]
      },
      "ClientList": {
        "description": "Cursor-paginated list of clients.",
        "allOf": [
          {
            "$ref": "#/components/schemas/ListEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Client"
                }
              }
            },
            "required": [
              "data"
            ]
          }
        ]
      },
      "Availability": {
        "description": "Computed free slots for the requested services and range.",
        "allOf": [
          {
            "$ref": "#/components/schemas/ListEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/AvailabilitySlot"
                }
              }
            },
            "required": [
              "data"
            ]
          }
        ]
      },
      "ResourceList": {
        "description": "Cursor-paginated list of resources.",
        "allOf": [
          {
            "$ref": "#/components/schemas/ListEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Resource"
                }
              }
            },
            "required": [
              "data"
            ]
          }
        ]
      },
      "AppointmentList": {
        "description": "Cursor-paginated list of appointments.",
        "allOf": [
          {
            "$ref": "#/components/schemas/ListEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Appointment"
                }
              }
            },
            "required": [
              "data"
            ]
          }
        ]
      },
      "PaymentMethodList": {
        "description": "Cursor-paginated list of payment methods.",
        "allOf": [
          {
            "$ref": "#/components/schemas/ListEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/PaymentMethod"
                }
              }
            },
            "required": [
              "data"
            ]
          }
        ]
      },
      "ApplicablePaymentMethodList": {
        "description": "Cursor-paginated list of ways to pay a specific visit.",
        "allOf": [
          {
            "$ref": "#/components/schemas/ListEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ApplicablePaymentMethod"
                }
              }
            },
            "required": [
              "data"
            ]
          }
        ]
      },
      "AccountList": {
        "description": "Cursor-paginated list of accounts.",
        "allOf": [
          {
            "$ref": "#/components/schemas/ListEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            },
            "required": [
              "data"
            ]
          }
        ]
      },
      "LocationCreate": {
        "type": "object",
        "description": "Create a Location. `country_code`, `timezone`, and `currency` are derived from `city_id` and not writable.",
        "properties": {
          "name": {
            "type": "string"
          },
          "city_id": {
            "type": "integer"
          },
          "business_type_id": {
            "type": "integer"
          },
          "language": {
            "type": "string",
            "description": "BCP 47 language tag."
          },
          "datetime_format": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "city_id",
          "business_type_id",
          "language",
          "datetime_format"
        ],
        "additionalProperties": false
      },
      "LocationCreated": {
        "description": "The created Location plus its provisioning status.",
        "allOf": [
          {
            "$ref": "#/components/schemas/Location"
          },
          {
            "type": "object",
            "properties": {
              "provisioning_status": {
                "type": "string",
                "readOnly": true,
                "enum": [
                  "ready",
                  "pending"
                ],
                "description": "`ready` = base accounts and payment methods exist; `pending` = defaults are still being provisioned."
              }
            },
            "required": [
              "provisioning_status"
            ]
          }
        ]
      },
      "City": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "object": {
            "type": "string",
            "enum": [
              "city"
            ]
          },
          "country_id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "country_code": {
            "type": "string",
            "readOnly": true
          }
        },
        "required": [
          "id",
          "object",
          "country_id",
          "name",
          "country_code"
        ],
        "additionalProperties": false
      },
      "CityList": {
        "description": "Cursor-paginated list of cities.",
        "allOf": [
          {
            "$ref": "#/components/schemas/ListEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/City"
                }
              }
            },
            "required": [
              "data"
            ]
          }
        ]
      },
      "BusinessType": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "object": {
            "type": "string",
            "enum": [
              "business_type"
            ]
          },
          "title": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "object",
          "title"
        ],
        "additionalProperties": false
      },
      "BusinessTypeList": {
        "description": "Cursor-paginated list of business types.",
        "allOf": [
          {
            "$ref": "#/components/schemas/ListEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/BusinessType"
                }
              }
            },
            "required": [
              "data"
            ]
          }
        ]
      },
      "LocationAccessRole": {
        "type": "object",
        "description": "Public access role. Internal permission slugs are not exposed.",
        "properties": {
          "role_id": {
            "type": "integer"
          },
          "object": {
            "type": "string",
            "enum": [
              "location_access_role"
            ]
          },
          "title": {
            "type": "string"
          }
        },
        "required": [
          "role_id",
          "object",
          "title"
        ],
        "additionalProperties": false
      },
      "LocationAccessRoleList": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ListEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/LocationAccessRole"
                }
              }
            },
            "required": [
              "data"
            ]
          }
        ]
      },
      "TeamMemberAccessInvitation": {
        "type": "object",
        "properties": {
          "sent_at": {
            "type": "string",
            "format": "date-time",
            "description": "UTC."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "UTC."
          }
        },
        "required": [
          "sent_at",
          "expires_at"
        ],
        "additionalProperties": false
      },
      "TeamMemberAccess": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "team_member_access"
            ]
          },
          "team_member_id": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "none",
              "invited",
              "active"
            ]
          },
          "role_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "invitation": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/TeamMemberAccessInvitation"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "object",
          "team_member_id",
          "status",
          "role_id"
        ],
        "additionalProperties": false
      },
      "TeamMemberInvitationCreate": {
        "type": "object",
        "description": "Empty body for resend. Delivery contact cannot be overridden here — set write-only phone/email only on Team Member create (`access_invitation`).",
        "properties": {},
        "additionalProperties": false
      },
      "TeamMemberAccessUpdate": {
        "type": "object",
        "description": "Merge-patch body; only `role_id` can be changed.",
        "properties": {
          "role_id": {
            "type": "integer"
          }
        },
        "required": [
          "role_id"
        ],
        "additionalProperties": false
      },
      "Product": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "object": {
            "type": "string",
            "enum": [
              "product"
            ]
          },
          "title": {
            "type": "string"
          },
          "price_minor": {
            "type": "integer"
          },
          "unit": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "object",
          "title",
          "price_minor",
          "unit"
        ],
        "additionalProperties": false
      },
      "ProductList": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ListEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Product"
                }
              }
            },
            "required": [
              "data"
            ]
          }
        ]
      },
      "PaymentCoversItem": {
        "type": "object",
        "properties": {
          "visit_item_id": {
            "type": "integer"
          },
          "amount_minor": {
            "type": "integer"
          }
        },
        "required": [
          "visit_item_id"
        ],
        "additionalProperties": false
      },
      "AvailabilityPackageSegment": {
        "type": "object",
        "properties": {
          "package_component_id": {
            "type": "integer",
            "description": "Stable component id from `ServicePackageDetails.components[].id`."
          },
          "service_id": {
            "type": "integer"
          },
          "team_member_id": {
            "type": "integer"
          },
          "starts_at": {
            "type": "string",
            "format": "date-time"
          },
          "duration_seconds": {
            "type": "integer"
          },
          "resource_instance_ids": {
            "type": "array",
            "items": {
              "type": "integer"
            }
          }
        },
        "required": [
          "package_component_id",
          "service_id",
          "team_member_id",
          "starts_at",
          "duration_seconds",
          "resource_instance_ids"
        ],
        "additionalProperties": false
      },
      "AppointmentPackageComponentCreate": {
        "type": "object",
        "properties": {
          "package_component_id": {
            "type": "integer"
          },
          "team_member_id": {
            "type": "integer"
          }
        },
        "required": [
          "package_component_id",
          "team_member_id"
        ],
        "additionalProperties": false
      },
      "AppointmentCreateResponse": {
        "type": "object",
        "description": "Create-appointment result. Simple services return one appointment; package services return every created component appointment plus the shared visit id.",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "appointment_create_result"
            ]
          },
          "visit_id": {
            "type": [
              "integer",
              "null"
            ],
            "readOnly": true
          },
          "appointments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Appointment"
            }
          }
        },
        "required": [
          "object",
          "visit_id",
          "appointments"
        ],
        "additionalProperties": false
      }
    }
  }
}