# List appointments

Returns the appointments of the Location as a cursor-paginated list, newest window
first by start time with a stable tie-breaker. Client contact data inside
`expand[]=client` uses redacted contact fields without `clients:read_contact`.
Cancelled appointments are included; filter them out on the `status` field. There is no
status query parameter in the first iteration.
Cursor-paginated list. Default ordering: starts_at desc, id tie-breaker. Cursors use the `cursor` query parameter, expire after one hour, and are bound to the credential, tenant, list, and filters. Expired, malformed, foreign, or re-filtered cursors fail with `422 validation_failed` using field codes `expired_cursor`, `invalid_cursor`, `foreign_cursor`, or `filter_changed`.

Endpoint: GET /locations/{location_id}/appointments
Version: 3.0.0-preview
Security: OAuth2, ApiKey

## Security:

  - `OAuth2` (unknown)
    oauth2 scopes: appointments:read

  - `ApiKey` (unknown)
    http bearer

## Path parameters:

  - `location_id` (integer, required)
    Identifier of the Location. Verified against the credential on every call — a Location the credential cannot access responds with `404`.

## Query parameters:

  - `starts_at_from` (string)
    Only appointments starting at or after this RFC 3339 datetime.

  - `starts_at_to` (string)
    Only appointments starting before this RFC 3339 datetime.

  - `team_member_id` (integer)
    Only appointments of this Team Member.

  - `client_id` (integer)
    Only appointments of this client.

  - `limit` (integer)
    Page size for cursor pagination. Default is 25; maximum is 100. Values outside 1..100 or non-integers fail with `422 validation_failed`; the value is never clamped.

  - `cursor` (string)
    Opaque cursor from `next_cursor`. An empty value is the same as omitting the parameter and returns the first page. Cursors expire one hour after the response that issued them (every page issues a fresh cursor) and are bound to credential, tenant, list, filters, and ordering; for OAuth the credential is the user's authorization, so a cursor survives a token refresh; a row whose ordering column changed between pages may be skipped or returned twice, and a row created while you page may be missing from this pass if it sorts before the current position (start a new pass to pick it up); expired, malformed, foreign, or re-filtered cursors fail with `422 validation_failed` using field codes `expired_cursor`, `invalid_cursor` (also a cursor whose number of key values does not match the list's ordering), `foreign_cursor` (another credential or tenant), or `filter_changed` (another list, ordering, or filters).

  - `expand[]` (array)
    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`.

## Response 200:

  - `200` (unknown)
    A page of appointments.

## Response 200 fields (application/json):

  - `object` (string, required)
    Enum: "list"

  - `has_more` (boolean, required)
    Whether more items exist beyond this page.

  - `next_cursor` (string | null, required)
    Opaque cursor for the next page; `null` on the last page.

  - `data` (array, required)

  - `data.id` (integer, required)

  - `data.object` (string, required)
    Enum: "appointment"

  - `data.location_id` (integer, required)

  - `data.client_id` (integer | null)
    `null` only on appointments without a client.

  - `data.visit_id` (integer | null)
    Billing visit of this appointment.

  - `data.team_member_id` (integer, required)

  - `data.service_ids` (array, required)

  - `data.resource_instance_ids` (array)
    Resource instances occupied by the appointment.

  - `data.status` (string, required)
    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"

  - `data.starts_at` (string, required)
    RFC 3339 with the Location's local UTC offset.

  - `data.ends_at` (string)
    RFC 3339 with the Location's local UTC offset.

  - `data.duration_seconds` (integer, required)

  - `data.is_paid` (boolean)
    Whether the visit of this appointment is fully paid.

  - `data.comment` (string | null)

  - `data.cancelled_at` (string | null)
    UTC; set by the cancel command.

  - `data.cancellation_reason` (string | null)

  - `data.client` (object)
    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.

  - `data.client.id` (integer, required)

  - `data.client.object` (string, required)
    Enum: "client"

  - `data.client.name` (string, required)

  - `data.client.surname` (string | null)

  - `data.client.patronymic` (string | null)

  - `data.client.display_name` (string)

  - `data.client.phone` (string | null, required)
    International phone number. `null` when absent or withheld — see `redacted_fields`.

  - `data.client.additional_phone` (string | null)
    Additional phone number. `null` when absent or withheld — see `redacted_fields`.

  - `data.client.email` (string | null)
    Email address. `null` when absent or withheld — see `redacted_fields`.

  - `data.client.birth_date` (string | null)

  - `data.client.comment` (string | null)
    Private note about the client; requires comment access.

  - `data.client.discount` (number | null)
    Personal discount, percent.

  - `data.client.visits_count` (integer)

  - `data.client.first_visit_at` (string | null)
    Location-local offset.

  - `data.client.last_visit_at` (string | null)
    Location-local offset.

  - `data.client.currency` (string)
    ISO 4217, server-controlled by the Location.

  - `data.client.created_at` (string)
    UTC.

  - `data.client.updated_at` (string)
    UTC.

  - `data.client.sold_amount_minor` (integer)
    Lifetime paid total, integer minor units of the Location currency.

  - `data.client.redacted_fields` (array)
    Names of fields withheld by the PII policy for this object, for example
`["phone", "email"]`. A withheld contact is always returned as `null` — never as a
masked or empty string, so an integration cannot write a mask back. An empty array
means nothing was withheld and a `null` value simply means no value is stored.

  - `data.team_member` (object)
    A Team Member of the Location. Personal contact data of Team Members is not exposed in the preview scope.

  - `data.team_member.id` (integer, required)

  - `data.team_member.object` (string, required)
    Enum: "team_member"

  - `data.team_member.name` (string, required)

  - `data.team_member.specialization` (string, required)
    Public profession label shown to clients (for example in online booking).

  - `data.team_member.position_id` (integer | null)
    Position from `listPositions`.

  - `data.team_member.service_ids` (array)
    Services this Team Member can be scheduled for. Written through `updateTeamMemberServices`.

  - `data.team_member.is_bookable` (boolean)
    Derived — whether the member can currently receive appointments.

  - `data.team_member.is_billable` (boolean)
    Whether this Team Member occupies a paid seat in the Location's licence, which is what
the Location is charged for. A non-billable member cannot hold schedule access, so
`is_bookable` is always `false` for one. Locations still on the older licensing model
report `false` here and ignore the field on write — there the paid seat count follows
schedule access instead.

  - `data.team_member.dismissal_date` (string | null)
    Last working day set by `dismissTeamMember`, `YYYY-MM-DD` in the Location timezone; `null` while employed.

  - `data.team_member.dismissal_reason` (string | null)
    Reason given on dismissal; `null` while employed.

  - `data.team_member.created_at` (string)
    UTC.

  - `data.team_member.updated_at` (string)
    UTC.

  - `data.created_at` (string)
    UTC.

  - `data.updated_at` (string)
    UTC.

## Response 200 headers (application/json):

  - `RateLimit-Remaining` (integer)
    Requests left in the most constrained policy window that applies to this request.

  - `RateLimit-Reset` (integer)
    On `429`, seconds until the most constrained policy frees up; it equals `Retry-After` there.
On a successful response it reports the **length** of that policy's window rather than the
time left in the current one, so treat it as the window size, not as a countdown — the
counter to pace against is `RateLimit-Remaining`.

  - `RateLimit-Policy` (string)
    Most constrained policy applied to the request, as `<limit>;w=<window in seconds>`.
    Example: 10;w=60

  - `X-Request-Id` (string)
    Request identifier for support and log correlation.

## Response 400:

  - `400` (unknown)
    Client error without a more specific registered code (`bad_request` — status default for
bare 400 and unknown 4xx), or protocol/syntax failure (`malformed_request` — broken JSON /
unparseable body only; must be thrown explicitly, never used as a synonym for every 400).
Semantic field validation is `422 validation_failed`, not 400.

## Response 400 fields (application/problem+json):

  - `type` (string, required)
    Stable URL identifying the problem type; links to documentation.

  - `title` (string, required)
    Human-readable summary of the problem type.

  - `status` (integer, required)
    HTTP status code of this response.

  - `detail` (string)
    Human-readable explanation of this occurrence.

  - `instance` (string, required)
    Request identifier for support and log correlation.

  - `code` (string, required)
    Stable machine-readable reason code.

  - `errors` (array)
    Field-level issues (present on validation problems).

  - `errors.code` (string, required)
    Stable machine-readable reason for this field.

  - `errors.pointer` (string, required)
    JSON Pointer for `body`; parameter name for `query`, `path`, or `header`.

  - `errors.message` (string, required)
    Human-readable explanation.

  - `errors.source` (string, required)
    Where the invalid value came from.
    Enum: "body", "query", "path", "header"

## Response 400 headers (application/problem+json):

  - `X-Request-Id` (string)
    Request identifier for support and log correlation.

## Response 401:

  - `401` (unknown)
    The request has no valid Bearer credential (`invalid_token`).

## Response 401 fields (application/problem+json):

  - `type` (string, required)
    Stable URL identifying the problem type; links to documentation.

  - `title` (string, required)
    Human-readable summary of the problem type.

  - `status` (integer, required)
    HTTP status code of this response.

  - `detail` (string)
    Human-readable explanation of this occurrence.

  - `instance` (string, required)
    Request identifier for support and log correlation.

  - `code` (string, required)
    Stable machine-readable reason code.

  - `errors` (array)
    Field-level issues (present on validation problems).

  - `errors.code` (string, required)
    Stable machine-readable reason for this field.

  - `errors.pointer` (string, required)
    JSON Pointer for `body`; parameter name for `query`, `path`, or `header`.

  - `errors.message` (string, required)
    Human-readable explanation.

  - `errors.source` (string, required)
    Where the invalid value came from.
    Enum: "body", "query", "path", "header"

## Response 401 headers (application/problem+json):

  - `WWW-Authenticate` (string)
    Bearer challenge with the URL of the protected resource metadata (RFC 9728 §5.1),
where the client finds the authorization server.
    Example: Bearer resource_metadata="https://api.alteg.io/.well-known/oauth-protected-resource/api/v3"

  - `X-Request-Id` (string)
    Request identifier for support and log correlation.

## Response 403:

  - `403` (unknown)
    The token lacks the required scope (`missing_scope`), the scope is present but the token subject lacks the business permission (`permission_denied`), or the Location has no active license (`subscription_inactive`).

## Response 403 fields (application/problem+json):

  - `type` (string, required)
    Stable URL identifying the problem type; links to documentation.

  - `title` (string, required)
    Human-readable summary of the problem type.

  - `status` (integer, required)
    HTTP status code of this response.

  - `detail` (string)
    Human-readable explanation of this occurrence.

  - `instance` (string, required)
    Request identifier for support and log correlation.

  - `code` (string, required)
    Stable machine-readable reason code.

  - `errors` (array)
    Field-level issues (present on validation problems).

  - `errors.code` (string, required)
    Stable machine-readable reason for this field.

  - `errors.pointer` (string, required)
    JSON Pointer for `body`; parameter name for `query`, `path`, or `header`.

  - `errors.message` (string, required)
    Human-readable explanation.

  - `errors.source` (string, required)
    Where the invalid value came from.
    Enum: "body", "query", "path", "header"

## Response 403 headers (application/problem+json):

  - `WWW-Authenticate` (string)
    Only on `missing_scope`: Bearer challenge with `error="insufficient_scope"`, the scope the
operation requires and the URL of the protected resource metadata (RFC 6750 §3.1, RFC 9728 §5.1).
    Example: Bearer error="insufficient_scope", scope="team_members:read", resource_metadata="https://api.alteg.io/.well-known/oauth-protected-resource/api/v3"

  - `X-Request-Id` (string)
    Request identifier for support and log correlation.

## Response 404:

  - `404` (unknown)
    The resource does not exist or is hidden by tenant policy. Prefer a resource-specific
code (`appointment_not_found`, `location_not_found`, …). Routing/gate/unknown-path 404s
use the generic `resource_not_found` (same shape, so "no pin" and "missing" are not distinguished).

## Response 404 fields (application/problem+json):

  - `type` (string, required)
    Stable URL identifying the problem type; links to documentation.

  - `title` (string, required)
    Human-readable summary of the problem type.

  - `status` (integer, required)
    HTTP status code of this response.

  - `detail` (string)
    Human-readable explanation of this occurrence.

  - `instance` (string, required)
    Request identifier for support and log correlation.

  - `code` (string, required)
    Stable machine-readable reason code.

  - `errors` (array)
    Field-level issues (present on validation problems).

  - `errors.code` (string, required)
    Stable machine-readable reason for this field.

  - `errors.pointer` (string, required)
    JSON Pointer for `body`; parameter name for `query`, `path`, or `header`.

  - `errors.message` (string, required)
    Human-readable explanation.

  - `errors.source` (string, required)
    Where the invalid value came from.
    Enum: "body", "query", "path", "header"

## Response 404 headers (application/problem+json):

  - `X-Request-Id` (string)
    Request identifier for support and log correlation.

## Response 406:

  - `406` (unknown)
    Requested `Accept` media type cannot be satisfied (`not_acceptable`).

## Response 406 fields (application/problem+json):

  - `type` (string, required)
    Stable URL identifying the problem type; links to documentation.

  - `title` (string, required)
    Human-readable summary of the problem type.

  - `status` (integer, required)
    HTTP status code of this response.

  - `detail` (string)
    Human-readable explanation of this occurrence.

  - `instance` (string, required)
    Request identifier for support and log correlation.

  - `code` (string, required)
    Stable machine-readable reason code.

  - `errors` (array)
    Field-level issues (present on validation problems).

  - `errors.code` (string, required)
    Stable machine-readable reason for this field.

  - `errors.pointer` (string, required)
    JSON Pointer for `body`; parameter name for `query`, `path`, or `header`.

  - `errors.message` (string, required)
    Human-readable explanation.

  - `errors.source` (string, required)
    Where the invalid value came from.
    Enum: "body", "query", "path", "header"

## Response 406 headers (application/problem+json):

  - `X-Request-Id` (string)
    Request identifier for support and log correlation.

## Response 409:

  - `409` (unknown)
    Domain conflict (`slot_taken`, `duplicate_location`, `last_location_owner`), idempotency conflict, or generic `conflict` when no more specific registered code applies.

## Response 409 fields (application/problem+json):

  - `type` (string, required)
    Stable URL identifying the problem type; links to documentation.

  - `title` (string, required)
    Human-readable summary of the problem type.

  - `status` (integer, required)
    HTTP status code of this response.

  - `detail` (string)
    Human-readable explanation of this occurrence.

  - `instance` (string, required)
    Request identifier for support and log correlation.

  - `code` (string, required)
    Stable machine-readable reason code.

  - `errors` (array)
    Field-level issues (present on validation problems).

  - `errors.code` (string, required)
    Stable machine-readable reason for this field.

  - `errors.pointer` (string, required)
    JSON Pointer for `body`; parameter name for `query`, `path`, or `header`.

  - `errors.message` (string, required)
    Human-readable explanation.

  - `errors.source` (string, required)
    Where the invalid value came from.
    Enum: "body", "query", "path", "header"

## Response 409 headers (application/problem+json):

  - `X-Request-Id` (string)
    Request identifier for support and log correlation.

  - `Retry-After` (integer)
    Seconds to wait before retrying.

## Response 422:

  - `422` (unknown)
    Semantic validation failed (`validation_failed`); field-level details are in `errors[]`. Merge-patch violations use `field_not_nullable` / `field_immutable`.

## Response 422 fields (application/problem+json):

  - `type` (string, required)
    Stable URL identifying the problem type; links to documentation.

  - `title` (string, required)
    Human-readable summary of the problem type.

  - `status` (integer, required)
    HTTP status code of this response.

  - `detail` (string)
    Human-readable explanation of this occurrence.

  - `instance` (string, required)
    Request identifier for support and log correlation.

  - `code` (string, required)
    Stable machine-readable reason code.

  - `errors` (array)
    Field-level issues (present on validation problems).

  - `errors.code` (string, required)
    Stable machine-readable reason for this field.

  - `errors.pointer` (string, required)
    JSON Pointer for `body`; parameter name for `query`, `path`, or `header`.

  - `errors.message` (string, required)
    Human-readable explanation.

  - `errors.source` (string, required)
    Where the invalid value came from.
    Enum: "body", "query", "path", "header"

## Response 422 headers (application/problem+json):

  - `X-Request-Id` (string)
    Request identifier for support and log correlation.

## Response 429:

  - `429` (unknown)
    Rate limit exceeded (`rate_limit_exceeded`). Retry after `Retry-After`.

## Response 429 fields (application/problem+json):

  - `type` (string, required)
    Stable URL identifying the problem type; links to documentation.

  - `title` (string, required)
    Human-readable summary of the problem type.

  - `status` (integer, required)
    HTTP status code of this response.

  - `detail` (string)
    Human-readable explanation of this occurrence.

  - `instance` (string, required)
    Request identifier for support and log correlation.

  - `code` (string, required)
    Stable machine-readable reason code.

  - `errors` (array)
    Field-level issues (present on validation problems).

  - `errors.code` (string, required)
    Stable machine-readable reason for this field.

  - `errors.pointer` (string, required)
    JSON Pointer for `body`; parameter name for `query`, `path`, or `header`.

  - `errors.message` (string, required)
    Human-readable explanation.

  - `errors.source` (string, required)
    Where the invalid value came from.
    Enum: "body", "query", "path", "header"

## Response 429 headers (application/problem+json):

  - `Retry-After` (integer)
    Seconds to wait before retrying.

  - `RateLimit-Remaining` (integer)
    Requests left in the most constrained policy window that applies to this request.

  - `RateLimit-Reset` (integer)
    On `429`, seconds until the most constrained policy frees up; it equals `Retry-After` there.
On a successful response it reports the **length** of that policy's window rather than the
time left in the current one, so treat it as the window size, not as a countdown — the
counter to pace against is `RateLimit-Remaining`.

  - `RateLimit-Policy` (string)
    Most constrained policy applied to the request, as `<limit>;w=<window in seconds>`.
    Example: 10;w=60

  - `X-Request-Id` (string)
    Request identifier for support and log correlation.

## Response 500:

  - `500` (unknown)
    Unexpected server error (`internal_error`).

## Response 500 fields (application/problem+json):

  - `type` (string, required)
    Stable URL identifying the problem type; links to documentation.

  - `title` (string, required)
    Human-readable summary of the problem type.

  - `status` (integer, required)
    HTTP status code of this response.

  - `detail` (string)
    Human-readable explanation of this occurrence.

  - `instance` (string, required)
    Request identifier for support and log correlation.

  - `code` (string, required)
    Stable machine-readable reason code.

  - `errors` (array)
    Field-level issues (present on validation problems).

  - `errors.code` (string, required)
    Stable machine-readable reason for this field.

  - `errors.pointer` (string, required)
    JSON Pointer for `body`; parameter name for `query`, `path`, or `header`.

  - `errors.message` (string, required)
    Human-readable explanation.

  - `errors.source` (string, required)
    Where the invalid value came from.
    Enum: "body", "query", "path", "header"

## Response 500 headers (application/problem+json):

  - `X-Request-Id` (string)
    Request identifier for support and log correlation.

## Response 503:

  - `503` (unknown)
    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.

## Response 503 fields (application/problem+json):

  - `type` (string, required)
    Stable URL identifying the problem type; links to documentation.

  - `title` (string, required)
    Human-readable summary of the problem type.

  - `status` (integer, required)
    HTTP status code of this response.

  - `detail` (string)
    Human-readable explanation of this occurrence.

  - `instance` (string, required)
    Request identifier for support and log correlation.

  - `code` (string, required)
    Stable machine-readable reason code.

  - `errors` (array)
    Field-level issues (present on validation problems).

  - `errors.code` (string, required)
    Stable machine-readable reason for this field.

  - `errors.pointer` (string, required)
    JSON Pointer for `body`; parameter name for `query`, `path`, or `header`.

  - `errors.message` (string, required)
    Human-readable explanation.

  - `errors.source` (string, required)
    Where the invalid value came from.
    Enum: "body", "query", "path", "header"

## Response 503 headers (application/problem+json):

  - `X-Request-Id` (string)
    Request identifier for support and log correlation.

## Response 400 examples:

  - `Generic client reject (status default)` (unknown)

  - `Broken JSON / protocol only` (unknown)

## Response 403 examples:

  - `Token scope is insufficient` (unknown)

  - `Subject lacks the business permission` (unknown)

  - `Location license is not active` (unknown)

## Response 404 examples:

  - `Generic 404 (routing / gate / hidden)` (unknown)

  - `Resource-specific 404` (unknown)

