{
  "openapi": "3.0.1",
  "info": {
    "title": "Fellix Public API",
    "version": "v1",
    "description": "The Fellix REST API for your own systems. Everything here is scoped to the organization\nthat issued the credential you send.\n\n**Base URL: `https://api.fellix.ai`** — every path below hangs off it, so `/v1/ping` is\n`https://api.fellix.ai/v1/ping`.\n\n## Authentication\n\nTwo ways in, both on the `Authorization` header:\n\n- **Static key** — `Authorization: Bearer flx_live_…`. Created in the dashboard under\n  Settings → API. Shown once, at creation; we store only a hash of it and cannot show it\n  again. Lost means rotate.\n- **OAuth2 client credentials** — `POST /v1/oauth/token` with `grant_type=client_credentials`,\n  `client_id` and `client_secret`, in exchange for a one-hour access token. No refresh token\n  is issued (RFC 6749 §4.4.3); ask again with the same secret.\n\nRevoking a credential takes effect **immediately**, including for access tokens already\nissued from it — an access token carries an identifier, not a copy of your permissions.\n\n## Scopes\n\nEvery credential carries a set of `<resource>:<action>` scopes. A request for something the\ncredential was not granted answers `403`, and the body names the scope it wanted.\n`customers:write` implies `customers:read`. `GET /v1/account` tells you what you hold.\n\n## Rate limits\n\nPer organization, per endpoint, per minute: every credential your organization holds draws on\nthe same allowance, so minting a second key does not double it. Every response to a credential\nwe accepted carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (the\nUnix timestamp at which the current window ends); a `429` also carries `Retry-After`, in\nseconds. A `401` carries none of them — the bucket is named from the credential's\norganization, so a request we could not attribute has no bucket to report.\n\nThe window **slides**: the minute before the current one still counts, weighted by how much of\nit is still in view, so an allowance cannot be spent twice either side of a boundary. Heavier\nendpoints carry a proportionally lower ceiling; the one that applies is always the\n`X-RateLimit-Limit` that endpoint returns.\n\n**Two things are never charged for.** A `5xx`, because the failure is ours, and a `409` from an\n`Idempotency-Key` conflict, because nothing was performed — retry either without spending\nallowance.\n\nA single credential can be held *below* the organization's ceiling, which is what to ask for\nwhen one integration is noisier than the rest. `GET /v1/account` reports the ceiling in force\nfor the credential you are using.\n\n## Pagination\n\nList endpoints are cursor-paginated. There is **no page number**.\n\n```\nGET /v1/customers?limit=50\n→ { \"data\": [...], \"meta\": { ..., \"pagination\": { \"next_cursor\": \"Ij...\", \"has_more\": true } } }\n\nGET /v1/customers?limit=50&after=Ij...\n→ { \"data\": [...], \"meta\": { ..., \"pagination\": { \"next_cursor\": null, \"has_more\": false } } }\n```\n\nThe cursor is opaque and signed — do not parse or construct one, just send back what the\nprevious response gave you. Ordering is newest first (`created_at DESC`). A cursor we did\nnot issue answers `400`.\n\n## Relations\n\nNothing is embedded unless you ask. `?include=customer,tags` embeds those relations; each\nendpoint documents which names it accepts, and an unknown one answers `400` rather than\nbeing quietly dropped. The same names configure what your webhook payloads carry, so a\nwebhook and a `GET` of the same resource can be made to agree exactly.\n\n**A relation of a relation is a dotted path**, up to 3 levels deep:\n\n```\nGET /v1/messages/42?include=conversation.customer.accounts\n→ { \"data\": { …, \"conversation\": { …, \"customer\": { …, \"accounts\": [ … ] } } } }\n```\n\nA path carries its parents with it, so `?include=customer.accounts` embeds `customer` as\nwell and you do not have to name both. The reverse is not true: naming a parent embeds only\nthe parent, and `?include=customer` carries no `accounts`.\n\nTwo things a path cannot do, both `400`:\n\n- **Go deeper than 3 levels.** The trees we preload terminate, and a\n  contract promising depth seven promises a response nobody meant to send.\n- **Come back to a resource it already passed through.** From a message,\n  `conversation.last_message` is the message you are already holding — or a sibling of it —\n  and serving it would cost a query per row instead of one per page.\n\nThe accepted list on each endpoint is exhaustive: if a path is not in it, it is a `400`, not\nan empty key. **Depth is not what costs you** — every embedded relation is one extra query\nper page, however deeply it is nested, so one call with `include=conversation.customer` is\nalways cheaper than a page of messages followed by a call per conversation.\n\n## Filtering\n\n`?filters[<field>_<predicate>]=<value>`, combined with AND:\n\n```\nGET /v1/customers?filters[created_at_gteq]=2026-08-01T00:00:00Z&filters[name_cont]=acme\n```\n\nWhich predicates a field takes depends on its type:\n\n| Field type | Predicates | Notes |\n|---|---|---|\n| text | `eq` `cont` `start` | `cont` and `start` match a substring and a prefix, both case-insensitive. |\n| number, id | `eq` `in` `gt` `gteq` `lt` `lteq` | Whole numbers. `in` takes a comma-separated list. |\n| decimal | `eq` `gt` `gteq` `lt` `lteq` | Fractional values. No `in`. |\n| boolean | `eq` | `true`, `false`, `1` or `0`. |\n| timestamp | `gt` `gteq` `lt` `lteq` | ISO8601 only, and no `eq` — see below. |\n| enum | `eq` `in` | One of a fixed set, listed with the field. `in` takes a comma-separated list. |\n\nEach endpoint's own filterable fields are listed with it, one line per accepted key —\ngenerated from the same allowlist the runtime checks, so the list cannot go stale.\n\nWorth knowing before you build a query:\n\n- **Two predicates on one field make a range.** `filters[created_at_gteq]=…` with\n  `filters[created_at_lteq]=…` is the window query; there is no `between`.\n- **A timestamp has no `eq`.** Equality on a moment is almost never what anyone means, and\n  the value must be ISO8601 — we would rather refuse `last tuesday` than silently pick a\n  window you did not ask for.\n- **Some fields filter themselves until you say otherwise.** `filters[is_archived_eq]`\n  defaults to `false` wherever it exists, so archived rows are absent unless you ask for\n  them; naming the field at all replaces the default rather than adding to it.\n- **`in` is a comma-separated list** (`filters[status_in]=open,closed`), which means a value\n  containing a comma cannot be expressed with it. Use several requests.\n- **No `OR`, no negation, and no sorting** in v1. Clauses always AND, and the order is\n  always newest first — if you need either, filter narrowly and combine on your side.\n- **Filters read the resource's own columns**, never an embedded relation's. `?include=` and\n  `?filters[]` are independent: embedding a customer does not let you filter on their name.\n\nAn unknown field, an unknown predicate or a value we cannot read answers `400` and names\nevery offending key — nothing is ever silently ignored.\n\n## Keeping a copy in sync\n\nThere is no \"changes since\" endpoint and you do not need one. Every list endpoint takes\n`filters[updated_at_gteq]`, and the order is newest first:\n\n```\nGET /v1/customers?filters[updated_at_gteq]=2026-09-25T00:00:00Z&limit=100\n```\n\nStore the timestamp you started the run at, not the newest `updated_at` you saw, and overlap\nthe next window by a minute — a row written while you were paging through belongs to the next\nrun, and an overlap costs you a duplicate you can ignore instead of a row you never see.\n\nRows are never hard-deleted from under you, but a client can be merged into another: follow\n`merged_into_id` and stop writing to the old one.\n\n## Writing\n\nCreate with `POST`, change with `PUT`. Bodies are namespaced by resource\n(`{\"customer\": {...}}`), and a field this API does not return is a field it does not accept —\nan unrecognised one answers `400` and names it rather than being ignored.\n\n**Retry safely with `Idempotency-Key`.** A timeout leaves you unable to tell \"never arrived\"\nfrom \"arrived, answer lost\"; send a key you generate and a retry returns the first answer with\n`Idempotency-Replayed: true` instead of writing again. Reusing a key with a different body is\na `409`, because that is a bug worth being told about rather than papered over.\n\nTwo rules worth knowing before you send a message:\n\n- **A thread a colleague has claimed refuses an API send** (`422`). Someone is typing in it,\n  and two voices from one window is worse than a refusal you can see.\n- **On WhatsApp, 24 hours after the customer's last message only a template is delivered.**\n  That is what `POST /v1/conversations/{id}/messages/template` is for; a plain send after the\n  window closes is accepted by nobody.\n\nMessages sent this way carry no sender — the same shape the assistant's own replies use — and\ncount as system messages, not as one of your agents' replies.\n\n## Reporting\n\nThree aggregates over conversations: `/v1/analytics/pipeline` (where things stand),\n`/v1/analytics/trends` (the same won/lost as a series) and `/v1/analytics/tags` (volume and\noutcome per tag). All take `start_date` / `end_date` and default to the last 30 days, and\nevery answer carries the window it used in `meta.period` — a figure without its window is not\na figure.\n\nThey are **narrow on purpose**. Our own dashboard computes more than this, in shapes that\nchange whenever a panel changes; what is published here is the stable core of each. They are\nalso the most expensive thing on this API, so they carry a lower rate limit than a list.\n\n`leads` and `voice_calls` are ordinary list endpoints rather than aggregates, each behind its\nown scope: a lead carries the campaign and ad ids you join your spend data against, and a\ncall carries its transcript — which is what a real person said, and a different thing to be\ntrusted with than a count.\n\n## Webhooks\n\nThe push half of this API. An organization subscribes its own HTTPS endpoints in the\ndashboard and receives HMAC-signed JSON — see `WebhookEnvelope` below for the wrapper.\n\n**`data.attributes` is the same shape as a GET of that resource.** Not approximately: the\ntwo are serialized from one definition, so `conversation.created` carries exactly what\n`GET /v1/conversations/{id}` returns for the same relations. That is why you can act on a\npayload directly and only call back when you want something the event did not carry.\n\n**Embedded relations are per endpoint, and nothing is embedded by default.** Each endpoint\nchooses which relations its payloads carry, per resource, from the same list `?include=`\naccepts — so an endpoint configured with `conversation: [customer, tags]` receives exactly\nwhat `?include=customer,tags` returns. Configure it in the dashboard; an endpoint that\nchooses nothing gets ids and attributes only, which is the cheapest thing to receive and\nusually enough to decide whether to call back.\n\nSome events describe resources this surface has no GET for yet — `voice_call.*`,\n`whatsapp_call.*`, `lead.created`, `team.*`. Their shapes are `PublicVoiceCall`,\n`PublicWhatsappCall`, `PublicWhatsappCallForward`, `PublicLead`, `PublicTeam` and\n`PublicTeamMember`, documented here for that reason.\n\nDedupe on the envelope `id` (stable across endpoints and across redeliveries) and order on\n`data.attributes.updated_at` — delivery order is not guaranteed.\n\n## Errors\n\nEverything except the OAuth token endpoint (which follows RFC 6749) answers in one shape:\n\n```json\n{ \"errors\": [ { \"title\": \"...\", \"detail\": \"...\", \"code\": \"not_found\", \"status\": 404 } ] }\n```\n\n`code` is the stable, machine-readable part: `auth-blank`, `auth-invalid`, `forbidden`,\n`not_found`, `invalid_cursor`, `invalid_parameter`, `too_many_requests`.\n\nText follows `Accept-Language` where we have a translation; `en-US` otherwise.\n"
  },
  "paths": {
    "/v1/analytics/pipeline": {
      "get": {
        "summary": "pipeline report",
        "operationId": "publicAnalyticsPipeline",
        "tags": [
          "Reporting"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Where the pipeline stands, and how long it took to get there. Narrower than our own dashboard computes, on purpose: its shapes change whenever a panel does, and this one is frozen. Requires the `analytics:read` scope.",
        "parameters": [
          {
            "$ref": "#/components/parameters/period_start"
          },
          {
            "$ref": "#/components/parameters/period_end"
          },
          {
            "$ref": "#/components/parameters/period_granularity"
          }
        ],
        "responses": {
          "200": {
            "description": "the report",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PublicPipelineAnalytics"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/MetaForPeriod"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "a date that is not ISO8601",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "401": {
            "description": "missing, unknown, revoked or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "403": {
            "description": "the credential lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "429": {
            "description": "the organization's per-endpoint ceiling was reached",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/analytics/trends": {
      "get": {
        "summary": "trends report",
        "operationId": "publicAnalyticsTrends",
        "tags": [
          "Reporting"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "The same won/lost as a series, bucketed by `granularity`. The response echoes the granularity it used, because an unrecognised request falls back rather than erroring. Requires the `analytics:read` scope.",
        "parameters": [
          {
            "$ref": "#/components/parameters/period_start"
          },
          {
            "$ref": "#/components/parameters/period_end"
          },
          {
            "$ref": "#/components/parameters/period_granularity"
          }
        ],
        "responses": {
          "200": {
            "description": "the report",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PublicTrendAnalytics"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/MetaForPeriod"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "a date that is not ISO8601",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "401": {
            "description": "missing, unknown, revoked or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "403": {
            "description": "the credential lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "429": {
            "description": "the organization's per-endpoint ceiling was reached",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/analytics/tags": {
      "get": {
        "summary": "tags report",
        "operationId": "publicAnalyticsTags",
        "tags": [
          "Reporting"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Distinct conversations per tag in the window, with their outcome, ordered by volume. Usage-volume-per-category is deliberately not here: a conversation with two tags of one category counts twice, which reads like a conversation count and is not one. Requires the `analytics:read` scope.",
        "parameters": [
          {
            "$ref": "#/components/parameters/period_start"
          },
          {
            "$ref": "#/components/parameters/period_end"
          },
          {
            "$ref": "#/components/parameters/period_granularity"
          }
        ],
        "responses": {
          "200": {
            "description": "the report",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PublicTagAnalytics"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/MetaForPeriod"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "a date that is not ISO8601",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "401": {
            "description": "missing, unknown, revoked or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "403": {
            "description": "the credential lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "429": {
            "description": "the organization's per-endpoint ceiling was reached",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/ping": {
      "get": {
        "summary": "Check a credential",
        "operationId": "publicPing",
        "tags": [
          "Meta"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Answers 200 for any live credential. Needs no scope — it is the call to make first, to confirm the key works and see the rate-limit headers.",
        "responses": {
          "200": {
            "description": "the credential is live",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PublicPing"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "missing, unknown, revoked or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "429": {
            "description": "the organization's per-endpoint ceiling was reached",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/account": {
      "get": {
        "summary": "Describe the calling credential",
        "operationId": "publicAccount",
        "tags": [
          "Meta"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Who this credential belongs to and what it may do. Read this when something else answers 403.",
        "parameters": [
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Relations to embed. Accepted: organization. Nothing is embedded unless named here.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "successful",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PublicAccount"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "an include that this endpoint does not offer",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "401": {
            "description": "missing, unknown, revoked or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "429": {
            "description": "the organization's per-endpoint ceiling was reached",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/oauth/token": {
      "post": {
        "summary": "Exchange client credentials for an access token",
        "operationId": "publicOauthToken",
        "tags": [
          "Authentication"
        ],
        "security": [],
        "description": "RFC 6749 client_credentials. Returns a one-hour bearer token and deliberately no refresh token (§4.4.3) — ask again with the same secret. Revoking the credential invalidates tokens already issued from it, immediately. This endpoint answers in the RFC 6749 shape, not the API error envelope.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "a token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthToken"
                }
              }
            }
          },
          "401": {
            "description": "the client_id or client_secret is wrong",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "400": {
            "description": "an unsupported grant_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "grant_type",
                  "client_id",
                  "client_secret"
                ],
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "enum": [
                      "client_credentials"
                    ]
                  },
                  "client_id": {
                    "type": "string"
                  },
                  "client_secret": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "required": true
        }
      }
    },
    "/v1/customers": {
      "get": {
        "summary": "list customers",
        "operationId": "publicListCustomers",
        "tags": [
          "Clients"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "The people your organization talks to. Poll `filters[updated_at_gteq]` to sync only what moved. Requires the `customers:read` scope.",
        "parameters": [
          {
            "$ref": "#/components/parameters/cursor_limit"
          },
          {
            "$ref": "#/components/parameters/cursor_after"
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Relations to embed, comma-separated. Accepted: accounts. A dotted path embeds a relation of a relation and brings its parents with it, so `customer.accounts` also embeds `customer`. Nothing is embedded unless named here, and an unknown name is a 400.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "filters",
            "in": "query",
            "style": "deepObject",
            "explode": true,
            "required": false,
            "schema": {
              "type": "object",
              "properties": {
                "name_eq": {
                  "type": "string",
                  "description": "name equals"
                },
                "name_cont": {
                  "type": "string",
                  "description": "name contains (case-insensitive)"
                },
                "name_start": {
                  "type": "string",
                  "description": "name starts with (case-insensitive)"
                },
                "surname_eq": {
                  "type": "string",
                  "description": "surname equals"
                },
                "surname_cont": {
                  "type": "string",
                  "description": "surname contains (case-insensitive)"
                },
                "surname_start": {
                  "type": "string",
                  "description": "surname starts with (case-insensitive)"
                },
                "email_eq": {
                  "type": "string",
                  "description": "email equals"
                },
                "email_cont": {
                  "type": "string",
                  "description": "email contains (case-insensitive)"
                },
                "email_start": {
                  "type": "string",
                  "description": "email starts with (case-insensitive)"
                },
                "phone_eq": {
                  "type": "string",
                  "description": "phone equals"
                },
                "phone_cont": {
                  "type": "string",
                  "description": "phone contains (case-insensitive)"
                },
                "phone_start": {
                  "type": "string",
                  "description": "phone starts with (case-insensitive)"
                },
                "is_archived_eq": {
                  "type": "boolean",
                  "description": "is_archived equals — default: false"
                },
                "created_at_gteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "created_at greater than or equal to — ISO8601"
                },
                "created_at_lteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "created_at less than or equal to — ISO8601"
                },
                "updated_at_gteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "updated_at greater than or equal to — ISO8601"
                },
                "updated_at_lteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "updated_at less than or equal to — ISO8601"
                }
              },
              "additionalProperties": false
            },
            "description": "Filters combine with AND. An unknown field, an unknown predicate or a value we cannot read answers 400 and names every offending key."
          }
        ],
        "responses": {
          "200": {
            "description": "a page of results",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PublicCustomer"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/MetaForCursorPagination"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "a filter or a relation we cannot honour — the body names every offending key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "401": {
            "description": "missing, unknown, revoked or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "403": {
            "description": "the credential lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "429": {
            "description": "the organization's per-endpoint ceiling was reached",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "create a client",
        "operationId": "publicCreateCustomer",
        "tags": [
          "Clients"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Requires the `customers:write` scope. Email and phone are unique within the organization, so a duplicate answers 422 rather than creating a second record.",
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotency_key"
          }
        ],
        "responses": {
          "201": {
            "description": "created",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PublicCustomer"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "a field this API does not accept",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "422": {
            "description": "validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "401": {
            "description": "missing, unknown, revoked or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "403": {
            "description": "the credential lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "409": {
            "description": "the Idempotency-Key was already used for a different request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "429": {
            "description": "the organization's per-endpoint ceiling was reached",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicCustomerInput"
              }
            }
          }
        }
      }
    },
    "/v1/customers/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        }
      ],
      "get": {
        "summary": "fetch one client",
        "operationId": "publicGetCustomer",
        "tags": [
          "Clients"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Requires the `customers:read` scope. Another organization's id is a 404.",
        "parameters": [
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Relations to embed, comma-separated. Accepted: accounts. A dotted path embeds a relation of a relation and brings its parents with it, so `customer.accounts` also embeds `customer`. Nothing is embedded unless named here, and an unknown name is a 400.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "successful",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PublicCustomer"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "no such record for this organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "401": {
            "description": "missing, unknown, revoked or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "403": {
            "description": "the credential lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          }
        }
      },
      "put": {
        "summary": "update a client",
        "operationId": "publicUpdateCustomer",
        "tags": [
          "Clients"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Requires the `customers:write` scope. Recorded in the activity feed as a change made by an integration, not by a member.",
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotency_key"
          }
        ],
        "responses": {
          "200": {
            "description": "updated",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PublicCustomer"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "no such client for this organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "401": {
            "description": "missing, unknown, revoked or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "403": {
            "description": "the credential lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "409": {
            "description": "the Idempotency-Key was already used for a different request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "429": {
            "description": "the organization's per-endpoint ceiling was reached",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicCustomerInput"
              }
            }
          }
        }
      }
    },
    "/v1/conversations": {
      "get": {
        "summary": "list conversations",
        "operationId": "publicListConversations",
        "tags": [
          "Conversations"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "One thread with one client on one channel. `include=channel` still names the channel after it is removed. Requires the `conversations:read` scope.",
        "parameters": [
          {
            "$ref": "#/components/parameters/cursor_limit"
          },
          {
            "$ref": "#/components/parameters/cursor_after"
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Relations to embed, comma-separated. Accepted: customer, customer.accounts, channel, tags, last_message. A dotted path embeds a relation of a relation and brings its parents with it, so `customer.accounts` also embeds `customer`. Nothing is embedded unless named here, and an unknown name is a 400.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "filters",
            "in": "query",
            "style": "deepObject",
            "explode": true,
            "required": false,
            "schema": {
              "type": "object",
              "properties": {
                "customer_id_eq": {
                  "type": "integer",
                  "description": "customer_id equals"
                },
                "customer_id_in": {
                  "type": "string",
                  "description": "customer_id one of (comma-separated)"
                },
                "channel_id_eq": {
                  "type": "integer",
                  "description": "channel_id equals"
                },
                "channel_id_in": {
                  "type": "string",
                  "description": "channel_id one of (comma-separated)"
                },
                "status_eq": {
                  "type": "string",
                  "enum": [
                    "active",
                    "completed",
                    "escalated",
                    "abandoned",
                    "blocked"
                  ],
                  "description": "status equals — values: active|completed|escalated|abandoned|blocked"
                },
                "status_in": {
                  "type": "string",
                  "description": "status one of (comma-separated) — values: active|completed|escalated|abandoned|blocked"
                },
                "lead_status_eq": {
                  "type": "string",
                  "enum": [
                    "open",
                    "won",
                    "lost"
                  ],
                  "description": "lead_status equals — values: open|won|lost"
                },
                "lead_status_in": {
                  "type": "string",
                  "description": "lead_status one of (comma-separated) — values: open|won|lost"
                },
                "qualification_eq": {
                  "type": "string",
                  "enum": [
                    "hot",
                    "warm",
                    "cold"
                  ],
                  "description": "qualification equals — values: hot|warm|cold"
                },
                "qualification_in": {
                  "type": "string",
                  "description": "qualification one of (comma-separated) — values: hot|warm|cold"
                },
                "language_eq": {
                  "type": "string",
                  "description": "language equals"
                },
                "language_cont": {
                  "type": "string",
                  "description": "language contains (case-insensitive)"
                },
                "language_start": {
                  "type": "string",
                  "description": "language starts with (case-insensitive)"
                },
                "is_archived_eq": {
                  "type": "boolean",
                  "description": "is_archived equals — default: false"
                },
                "created_at_gteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "created_at greater than or equal to — ISO8601"
                },
                "created_at_lteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "created_at less than or equal to — ISO8601"
                },
                "updated_at_gteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "updated_at greater than or equal to — ISO8601"
                },
                "updated_at_lteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "updated_at less than or equal to — ISO8601"
                }
              },
              "additionalProperties": false
            },
            "description": "Filters combine with AND. An unknown field, an unknown predicate or a value we cannot read answers 400 and names every offending key."
          }
        ],
        "responses": {
          "200": {
            "description": "a page of results",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PublicConversation"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/MetaForCursorPagination"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "a filter or a relation we cannot honour — the body names every offending key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "401": {
            "description": "missing, unknown, revoked or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "403": {
            "description": "the credential lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "429": {
            "description": "the organization's per-endpoint ceiling was reached",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/conversations/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        }
      ],
      "get": {
        "summary": "fetch one conversation",
        "operationId": "publicGetConversation",
        "tags": [
          "Conversations"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Requires the `conversations:read` scope. Another organization's id is a 404.",
        "parameters": [
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Relations to embed, comma-separated. Accepted: customer, customer.accounts, channel, tags, last_message. A dotted path embeds a relation of a relation and brings its parents with it, so `customer.accounts` also embeds `customer`. Nothing is embedded unless named here, and an unknown name is a 400.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "successful",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PublicConversation"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "no such record for this organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "401": {
            "description": "missing, unknown, revoked or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "403": {
            "description": "the credential lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/messages": {
      "get": {
        "summary": "list messages",
        "operationId": "publicListMessages",
        "tags": [
          "Messages"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Narrow to one thread with `filters[conversation_id_eq]`. Attachments are not part of v1. Requires the `messages:read` scope.",
        "parameters": [
          {
            "$ref": "#/components/parameters/cursor_limit"
          },
          {
            "$ref": "#/components/parameters/cursor_after"
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Relations to embed, comma-separated. Accepted: conversation, conversation.customer, conversation.customer.accounts, conversation.channel, conversation.tags. A dotted path embeds a relation of a relation and brings its parents with it, so `customer.accounts` also embeds `customer`. Nothing is embedded unless named here, and an unknown name is a 400.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "filters",
            "in": "query",
            "style": "deepObject",
            "explode": true,
            "required": false,
            "schema": {
              "type": "object",
              "properties": {
                "conversation_id_eq": {
                  "type": "integer",
                  "description": "conversation_id equals"
                },
                "conversation_id_in": {
                  "type": "string",
                  "description": "conversation_id one of (comma-separated)"
                },
                "message_type_eq": {
                  "type": "string",
                  "enum": [
                    "text",
                    "image",
                    "video",
                    "audio",
                    "document",
                    "location",
                    "contact",
                    "poll",
                    "event",
                    "reaction",
                    "sticker",
                    "reply",
                    "template",
                    "interactive",
                    "system"
                  ],
                  "description": "message_type equals — values: text|image|video|audio|document|location|contact|poll|event|reaction|sticker|reply|template|interactive|system"
                },
                "message_type_in": {
                  "type": "string",
                  "description": "message_type one of (comma-separated) — values: text|image|video|audio|document|location|contact|poll|event|reaction|sticker|reply|template|interactive|system"
                },
                "from_customer_eq": {
                  "type": "boolean",
                  "description": "from_customer equals"
                },
                "external_id_eq": {
                  "type": "string",
                  "description": "external_id equals"
                },
                "sent_at_gteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "sent_at greater than or equal to — ISO8601"
                },
                "sent_at_lteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "sent_at less than or equal to — ISO8601"
                },
                "created_at_gteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "created_at greater than or equal to — ISO8601"
                },
                "created_at_lteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "created_at less than or equal to — ISO8601"
                },
                "updated_at_gteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "updated_at greater than or equal to — ISO8601"
                },
                "updated_at_lteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "updated_at less than or equal to — ISO8601"
                }
              },
              "additionalProperties": false
            },
            "description": "Filters combine with AND. An unknown field, an unknown predicate or a value we cannot read answers 400 and names every offending key."
          }
        ],
        "responses": {
          "200": {
            "description": "a page of results",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PublicMessage"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/MetaForCursorPagination"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "a filter or a relation we cannot honour — the body names every offending key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "401": {
            "description": "missing, unknown, revoked or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "403": {
            "description": "the credential lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "429": {
            "description": "the organization's per-endpoint ceiling was reached",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/messages/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        }
      ],
      "get": {
        "summary": "fetch one message",
        "operationId": "publicGetMessage",
        "tags": [
          "Messages"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Requires the `messages:read` scope. Another organization's id is a 404.",
        "parameters": [
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Relations to embed, comma-separated. Accepted: conversation, conversation.customer, conversation.customer.accounts, conversation.channel, conversation.tags. A dotted path embeds a relation of a relation and brings its parents with it, so `customer.accounts` also embeds `customer`. Nothing is embedded unless named here, and an unknown name is a 400.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "successful",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PublicMessage"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "no such record for this organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "401": {
            "description": "missing, unknown, revoked or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "403": {
            "description": "the credential lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/tags": {
      "get": {
        "summary": "list tags",
        "operationId": "publicListTags",
        "tags": [
          "Tags"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "The organization's own taxonomy. `system: true` marks the ones Fellix maintains. Requires the `tags:read` scope.",
        "parameters": [
          {
            "$ref": "#/components/parameters/cursor_limit"
          },
          {
            "$ref": "#/components/parameters/cursor_after"
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "This resource embeds no relations.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "filters",
            "in": "query",
            "style": "deepObject",
            "explode": true,
            "required": false,
            "schema": {
              "type": "object",
              "properties": {
                "name_eq": {
                  "type": "string",
                  "description": "name equals"
                },
                "name_cont": {
                  "type": "string",
                  "description": "name contains (case-insensitive)"
                },
                "name_start": {
                  "type": "string",
                  "description": "name starts with (case-insensitive)"
                },
                "category_key_eq": {
                  "type": "string",
                  "description": "category_key equals"
                },
                "category_key_cont": {
                  "type": "string",
                  "description": "category_key contains (case-insensitive)"
                },
                "category_key_start": {
                  "type": "string",
                  "description": "category_key starts with (case-insensitive)"
                },
                "system_eq": {
                  "type": "boolean",
                  "description": "system equals"
                },
                "created_at_gteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "created_at greater than or equal to — ISO8601"
                },
                "created_at_lteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "created_at less than or equal to — ISO8601"
                },
                "updated_at_gteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "updated_at greater than or equal to — ISO8601"
                },
                "updated_at_lteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "updated_at less than or equal to — ISO8601"
                }
              },
              "additionalProperties": false
            },
            "description": "Filters combine with AND. An unknown field, an unknown predicate or a value we cannot read answers 400 and names every offending key."
          }
        ],
        "responses": {
          "200": {
            "description": "a page of results",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PublicTag"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/MetaForCursorPagination"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "a filter or a relation we cannot honour — the body names every offending key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "401": {
            "description": "missing, unknown, revoked or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "403": {
            "description": "the credential lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "429": {
            "description": "the organization's per-endpoint ceiling was reached",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/tags/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        }
      ],
      "get": {
        "summary": "fetch one tag",
        "operationId": "publicGetTag",
        "tags": [
          "Tags"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Requires the `tags:read` scope. Another organization's id is a 404.",
        "parameters": [
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "This resource embeds no relations.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "successful",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PublicTag"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "no such record for this organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "401": {
            "description": "missing, unknown, revoked or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "403": {
            "description": "the credential lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/appointments": {
      "get": {
        "summary": "list appointments",
        "operationId": "publicListAppointments",
        "tags": [
          "Appointments"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Bookings. `filters[starts_at_gteq]` + `filters[starts_at_lteq]` is the calendar window query. Requires the `appointments:read` scope.",
        "parameters": [
          {
            "$ref": "#/components/parameters/cursor_limit"
          },
          {
            "$ref": "#/components/parameters/cursor_after"
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Relations to embed, comma-separated. Accepted: customer, customer.accounts. A dotted path embeds a relation of a relation and brings its parents with it, so `customer.accounts` also embeds `customer`. Nothing is embedded unless named here, and an unknown name is a 400.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "filters",
            "in": "query",
            "style": "deepObject",
            "explode": true,
            "required": false,
            "schema": {
              "type": "object",
              "properties": {
                "customer_id_eq": {
                  "type": "integer",
                  "description": "customer_id equals"
                },
                "customer_id_in": {
                  "type": "string",
                  "description": "customer_id one of (comma-separated)"
                },
                "conversation_id_eq": {
                  "type": "integer",
                  "description": "conversation_id equals"
                },
                "conversation_id_in": {
                  "type": "string",
                  "description": "conversation_id one of (comma-separated)"
                },
                "status_eq": {
                  "type": "string",
                  "enum": [
                    "requested",
                    "confirmed",
                    "cancelled",
                    "completed",
                    "no_show"
                  ],
                  "description": "status equals — values: requested|confirmed|cancelled|completed|no_show"
                },
                "status_in": {
                  "type": "string",
                  "description": "status one of (comma-separated) — values: requested|confirmed|cancelled|completed|no_show"
                },
                "source_eq": {
                  "type": "string",
                  "enum": [
                    "operator",
                    "assistant",
                    "api"
                  ],
                  "description": "source equals — values: operator|assistant|api"
                },
                "source_in": {
                  "type": "string",
                  "description": "source one of (comma-separated) — values: operator|assistant|api"
                },
                "starts_at_gteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "starts_at greater than or equal to — ISO8601"
                },
                "starts_at_lteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "starts_at less than or equal to — ISO8601"
                },
                "ends_at_gteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "ends_at greater than or equal to — ISO8601"
                },
                "ends_at_lteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "ends_at less than or equal to — ISO8601"
                },
                "created_at_gteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "created_at greater than or equal to — ISO8601"
                },
                "created_at_lteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "created_at less than or equal to — ISO8601"
                },
                "updated_at_gteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "updated_at greater than or equal to — ISO8601"
                },
                "updated_at_lteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "updated_at less than or equal to — ISO8601"
                }
              },
              "additionalProperties": false
            },
            "description": "Filters combine with AND. An unknown field, an unknown predicate or a value we cannot read answers 400 and names every offending key."
          }
        ],
        "responses": {
          "200": {
            "description": "a page of results",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PublicAppointment"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/MetaForCursorPagination"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "a filter or a relation we cannot honour — the body names every offending key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "401": {
            "description": "missing, unknown, revoked or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "403": {
            "description": "the credential lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "429": {
            "description": "the organization's per-endpoint ceiling was reached",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "create a booking",
        "operationId": "publicCreateAppointment",
        "tags": [
          "Appointments"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Requires the `appointments:write` scope. Arrives as `source: api` and `status: confirmed`. Capacity and opening-hours refusals come back as 422 with the reason.",
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotency_key"
          }
        ],
        "responses": {
          "201": {
            "description": "created",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PublicAppointment"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "422": {
            "description": "validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "401": {
            "description": "missing, unknown, revoked or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "403": {
            "description": "the credential lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "409": {
            "description": "the Idempotency-Key was already used for a different request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "429": {
            "description": "the organization's per-endpoint ceiling was reached",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicAppointmentInput"
              }
            }
          }
        }
      }
    },
    "/v1/appointments/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        }
      ],
      "get": {
        "summary": "fetch one appointment",
        "operationId": "publicGetAppointment",
        "tags": [
          "Appointments"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Requires the `appointments:read` scope. Another organization's id is a 404.",
        "parameters": [
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Relations to embed, comma-separated. Accepted: customer, customer.accounts. A dotted path embeds a relation of a relation and brings its parents with it, so `customer.accounts` also embeds `customer`. Nothing is embedded unless named here, and an unknown name is a 400.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "successful",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PublicAppointment"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "no such record for this organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "401": {
            "description": "missing, unknown, revoked or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "403": {
            "description": "the credential lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          }
        }
      },
      "put": {
        "summary": "update or cancel a booking",
        "operationId": "publicUpdateAppointment",
        "tags": [
          "Appointments"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Requires the `appointments:write` scope. Cancel with `status: \"cancelled\"` — there is no DELETE, because a cancelled booking is a fact the calendar keeps. Moving `starts_at` without an end keeps the same duration.",
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotency_key"
          }
        ],
        "responses": {
          "200": {
            "description": "updated",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PublicAppointment"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "no such booking for this organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "401": {
            "description": "missing, unknown, revoked or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "403": {
            "description": "the credential lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "409": {
            "description": "the Idempotency-Key was already used for a different request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "429": {
            "description": "the organization's per-endpoint ceiling was reached",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicAppointmentInput"
              }
            }
          }
        }
      }
    },
    "/v1/leads": {
      "get": {
        "summary": "list leads",
        "operationId": "publicListLeads",
        "tags": [
          "Leads"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Ad and form submissions. Read-only — a lead arrives from the ad platform, not from you. `campaign_id` and `ad_id` are what you join your own spend data against. Requires the `leads:read` scope.",
        "parameters": [
          {
            "$ref": "#/components/parameters/cursor_limit"
          },
          {
            "$ref": "#/components/parameters/cursor_after"
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Relations to embed, comma-separated. Accepted: customer, customer.accounts, conversation, conversation.customer, conversation.customer.accounts, conversation.channel, conversation.tags, conversation.last_message. A dotted path embeds a relation of a relation and brings its parents with it, so `customer.accounts` also embeds `customer`. Nothing is embedded unless named here, and an unknown name is a 400.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "filters",
            "in": "query",
            "style": "deepObject",
            "explode": true,
            "required": false,
            "schema": {
              "type": "object",
              "properties": {
                "source_eq": {
                  "type": "string",
                  "enum": [
                    "meta_lead_ads",
                    "meta_ctwa",
                    "meta_ctm",
                    "meta_organic",
                    "native_form",
                    "manual"
                  ],
                  "description": "source equals — values: meta_lead_ads|meta_ctwa|meta_ctm|meta_organic|native_form|manual"
                },
                "source_in": {
                  "type": "string",
                  "description": "source one of (comma-separated) — values: meta_lead_ads|meta_ctwa|meta_ctm|meta_organic|native_form|manual"
                },
                "status_eq": {
                  "type": "string",
                  "enum": [
                    "new",
                    "processed",
                    "failed"
                  ],
                  "description": "status equals — values: new|processed|failed"
                },
                "status_in": {
                  "type": "string",
                  "description": "status one of (comma-separated) — values: new|processed|failed"
                },
                "platform_eq": {
                  "type": "string",
                  "description": "platform equals"
                },
                "platform_cont": {
                  "type": "string",
                  "description": "platform contains (case-insensitive)"
                },
                "platform_start": {
                  "type": "string",
                  "description": "platform starts with (case-insensitive)"
                },
                "campaign_id_eq": {
                  "type": "string",
                  "description": "campaign_id equals"
                },
                "ad_id_eq": {
                  "type": "string",
                  "description": "ad_id equals"
                },
                "form_id_eq": {
                  "type": "string",
                  "description": "form_id equals"
                },
                "customer_id_eq": {
                  "type": "integer",
                  "description": "customer_id equals"
                },
                "customer_id_in": {
                  "type": "string",
                  "description": "customer_id one of (comma-separated)"
                },
                "conversation_id_eq": {
                  "type": "integer",
                  "description": "conversation_id equals"
                },
                "conversation_id_in": {
                  "type": "string",
                  "description": "conversation_id one of (comma-separated)"
                },
                "submitted_at_gteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "submitted_at greater than or equal to — ISO8601"
                },
                "submitted_at_lteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "submitted_at less than or equal to — ISO8601"
                },
                "created_at_gteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "created_at greater than or equal to — ISO8601"
                },
                "created_at_lteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "created_at less than or equal to — ISO8601"
                },
                "updated_at_gteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "updated_at greater than or equal to — ISO8601"
                },
                "updated_at_lteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "updated_at less than or equal to — ISO8601"
                }
              },
              "additionalProperties": false
            },
            "description": "Filters combine with AND. An unknown field, an unknown predicate or a value we cannot read answers 400 and names every offending key."
          }
        ],
        "responses": {
          "200": {
            "description": "a page of results",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PublicLead"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/MetaForCursorPagination"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "a filter or a relation we cannot honour — the body names every offending key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "401": {
            "description": "missing, unknown, revoked or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "403": {
            "description": "the credential lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "429": {
            "description": "the organization's per-endpoint ceiling was reached",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/leads/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        }
      ],
      "get": {
        "summary": "fetch one lead",
        "operationId": "publicGetLead",
        "tags": [
          "Leads"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Requires the `leads:read` scope. Another organization's id is a 404.",
        "parameters": [
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Relations to embed, comma-separated. Accepted: customer, customer.accounts, conversation, conversation.customer, conversation.customer.accounts, conversation.channel, conversation.tags, conversation.last_message. A dotted path embeds a relation of a relation and brings its parents with it, so `customer.accounts` also embeds `customer`. Nothing is embedded unless named here, and an unknown name is a 400.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "successful",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PublicLead"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "no such record for this organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "401": {
            "description": "missing, unknown, revoked or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "403": {
            "description": "the credential lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/voice_calls": {
      "get": {
        "summary": "list voice_calls",
        "operationId": "publicListVoiceCalls",
        "tags": [
          "Voice calls"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Call records with their transcript. Read-only, and its own scope rather than `analytics:read` — a transcript is what a real person said, not a count. The recording itself is not part of v1. Requires the `voice_calls:read` scope.",
        "parameters": [
          {
            "$ref": "#/components/parameters/cursor_limit"
          },
          {
            "$ref": "#/components/parameters/cursor_after"
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Relations to embed, comma-separated. Accepted: customer, customer.accounts, conversation, conversation.customer, conversation.customer.accounts, conversation.channel, conversation.tags, conversation.last_message. A dotted path embeds a relation of a relation and brings its parents with it, so `customer.accounts` also embeds `customer`. Nothing is embedded unless named here, and an unknown name is a 400.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "filters",
            "in": "query",
            "style": "deepObject",
            "explode": true,
            "required": false,
            "schema": {
              "type": "object",
              "properties": {
                "status_eq": {
                  "type": "string",
                  "enum": [
                    "pending",
                    "ringing",
                    "in_progress",
                    "completed",
                    "failed",
                    "no_answer",
                    "busy",
                    "canceled"
                  ],
                  "description": "status equals — values: pending|ringing|in_progress|completed|failed|no_answer|busy|canceled"
                },
                "status_in": {
                  "type": "string",
                  "description": "status one of (comma-separated) — values: pending|ringing|in_progress|completed|failed|no_answer|busy|canceled"
                },
                "direction_eq": {
                  "type": "string",
                  "enum": [
                    "inbound",
                    "outbound"
                  ],
                  "description": "direction equals — values: inbound|outbound"
                },
                "direction_in": {
                  "type": "string",
                  "description": "direction one of (comma-separated) — values: inbound|outbound"
                },
                "external_call_id_eq": {
                  "type": "string",
                  "description": "external_call_id equals"
                },
                "from_number_eq": {
                  "type": "string",
                  "description": "from_number equals"
                },
                "from_number_cont": {
                  "type": "string",
                  "description": "from_number contains (case-insensitive)"
                },
                "from_number_start": {
                  "type": "string",
                  "description": "from_number starts with (case-insensitive)"
                },
                "to_number_eq": {
                  "type": "string",
                  "description": "to_number equals"
                },
                "to_number_cont": {
                  "type": "string",
                  "description": "to_number contains (case-insensitive)"
                },
                "to_number_start": {
                  "type": "string",
                  "description": "to_number starts with (case-insensitive)"
                },
                "customer_id_eq": {
                  "type": "integer",
                  "description": "customer_id equals"
                },
                "customer_id_in": {
                  "type": "string",
                  "description": "customer_id one of (comma-separated)"
                },
                "conversation_id_eq": {
                  "type": "integer",
                  "description": "conversation_id equals"
                },
                "conversation_id_in": {
                  "type": "string",
                  "description": "conversation_id one of (comma-separated)"
                },
                "duration_seconds_gteq": {
                  "type": "integer",
                  "description": "duration_seconds greater than or equal to"
                },
                "duration_seconds_lteq": {
                  "type": "integer",
                  "description": "duration_seconds less than or equal to"
                },
                "started_at_gteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "started_at greater than or equal to — ISO8601"
                },
                "started_at_lteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "started_at less than or equal to — ISO8601"
                },
                "created_at_gteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "created_at greater than or equal to — ISO8601"
                },
                "created_at_lteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "created_at less than or equal to — ISO8601"
                },
                "updated_at_gteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "updated_at greater than or equal to — ISO8601"
                },
                "updated_at_lteq": {
                  "type": "string",
                  "format": "date-time",
                  "description": "updated_at less than or equal to — ISO8601"
                }
              },
              "additionalProperties": false
            },
            "description": "Filters combine with AND. An unknown field, an unknown predicate or a value we cannot read answers 400 and names every offending key."
          }
        ],
        "responses": {
          "200": {
            "description": "a page of results",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PublicVoiceCall"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/MetaForCursorPagination"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "a filter or a relation we cannot honour — the body names every offending key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "401": {
            "description": "missing, unknown, revoked or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "403": {
            "description": "the credential lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "429": {
            "description": "the organization's per-endpoint ceiling was reached",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/voice_calls/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        }
      ],
      "get": {
        "summary": "fetch one voice_call",
        "operationId": "publicGetVoiceCall",
        "tags": [
          "Voice calls"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Requires the `voice_calls:read` scope. Another organization's id is a 404.",
        "parameters": [
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Relations to embed, comma-separated. Accepted: customer, customer.accounts, conversation, conversation.customer, conversation.customer.accounts, conversation.channel, conversation.tags, conversation.last_message. A dotted path embeds a relation of a relation and brings its parents with it, so `customer.accounts` also embeds `customer`. Nothing is embedded unless named here, and an unknown name is a 400.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "successful",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PublicVoiceCall"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "no such record for this organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "401": {
            "description": "missing, unknown, revoked or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "403": {
            "description": "the credential lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/conversations/{conversation_id}/messages": {
      "parameters": [
        {
          "name": "conversation_id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        }
      ],
      "post": {
        "summary": "send a message",
        "operationId": "publicSendMessage",
        "tags": [
          "Messages"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Requires the `messages:write` scope. Sent with no sender, the same shape the assistant's own replies use. Refused with 422 when a colleague has claimed the thread, when it is archived, or when the channel cannot send. On WhatsApp, after 24 hours of customer silence use the template endpoint instead.",
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotency_key"
          }
        ],
        "responses": {
          "201": {
            "description": "sent",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PublicMessage"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "422": {
            "description": "the thread or the channel refused it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "404": {
            "description": "no such conversation for this organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "401": {
            "description": "missing, unknown, revoked or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "403": {
            "description": "the credential lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "409": {
            "description": "the Idempotency-Key was already used for a different request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "429": {
            "description": "the organization's per-endpoint ceiling was reached",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicMessageInput"
              }
            }
          }
        }
      }
    },
    "/v1/conversations/{conversation_id}/messages/template": {
      "parameters": [
        {
          "name": "conversation_id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        }
      ],
      "post": {
        "summary": "send a WhatsApp template",
        "operationId": "publicSendTemplate",
        "tags": [
          "Messages"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Requires the `messages:write` scope. WhatsApp only, and the only thing Meta delivers once 24 hours have passed since the customer last wrote — which is why an appointment reminder has to go this way. 422 names the reason when the channel, its credential or the thread is not in a state to send.",
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotency_key"
          }
        ],
        "responses": {
          "201": {
            "description": "sent",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PublicMessage"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "422": {
            "description": "this channel cannot send a template",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "401": {
            "description": "missing, unknown, revoked or expired credential",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "403": {
            "description": "the credential lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "409": {
            "description": "the Idempotency-Key was already used for a different request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          },
          "429": {
            "description": "the organization's per-endpoint ceiling was reached",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per minute on this endpoint."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests left in the current window."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Unix timestamp at which the current window ends."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeneralError"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicTemplateInput"
              }
            }
          }
        }
      }
    }
  },
  "servers": [
    {
      "url": "http://localhost:3100/api/public",
      "description": "Local development"
    },
    {
      "url": "https://api.fellix.ai",
      "description": "Production"
    }
  ],
  "components": {
    "schemas": {
      "Meta": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "title": {
            "type": "string"
          },
          "detail": {
            "type": "string"
          }
        }
      },
      "ErrorModel": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "description": "Error code, \"<controller>#<action>#<key>\""
          },
          "status": {
            "type": "integer",
            "description": "HTTP status repeated in the body"
          },
          "title": {
            "type": "string",
            "description": "Error title"
          },
          "detail": {
            "type": "string",
            "description": "Error detail"
          }
        }
      },
      "GeneralError": {
        "type": "object",
        "properties": {
          "errors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ErrorModel"
            }
          }
        }
      },
      "MetaForCursorPagination": {
        "type": "object",
        "required": [
          "code",
          "status",
          "title",
          "detail",
          "pagination"
        ],
        "properties": {
          "code": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "title": {
            "type": "string"
          },
          "detail": {
            "type": "string"
          },
          "pagination": {
            "type": "object",
            "required": [
              "next_cursor",
              "has_more"
            ],
            "properties": {
              "next_cursor": {
                "type": "string",
                "nullable": true,
                "description": "Opaque. Absent on the last page."
              },
              "has_more": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "PublicOrganization": {
        "type": "object",
        "required": [
          "id",
          "name",
          "time_zone",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "time_zone": {
            "type": "string",
            "description": "IANA name. Timestamps are UTC regardless."
          },
          "default_locale": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date_time"
          }
        }
      },
      "PublicAccount": {
        "type": "object",
        "required": [
          "id",
          "name",
          "credential_type",
          "scopes",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "credential_type": {
            "type": "string",
            "enum": [
              "static",
              "oauth"
            ]
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "customers:read",
                "customers:write",
                "conversations:read",
                "conversations:write",
                "messages:read",
                "messages:write",
                "tags:read",
                "tags:write",
                "appointments:read",
                "appointments:write",
                "leads:read",
                "voice_calls:read",
                "analytics:read"
              ]
            },
            "description": "What this credential may do. A `:write` scope implies its `:read`."
          },
          "prefix": {
            "type": "string",
            "nullable": true,
            "description": "First 12 characters of a static key. Not a secret."
          },
          "client_id": {
            "type": "string",
            "nullable": true,
            "description": "OAuth credentials only."
          },
          "expires_at": {
            "type": "string",
            "format": "date_time",
            "nullable": true
          },
          "last_used_at": {
            "type": "string",
            "format": "date_time",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date_time"
          },
          "rate_limits": {
            "$ref": "#/components/schemas/PublicRateLimits"
          },
          "organization": {
            "$ref": "#/components/schemas/PublicOrganization"
          }
        }
      },
      "PublicRateLimits": {
        "type": "object",
        "required": [
          "window_seconds"
        ],
        "description": "The ceiling in force for THIS credential. `requests_per_minute` is null when no ceiling applies, in which case no response carries `X-RateLimit-*` either. A heavier endpoint scales down from this figure, so the number a given endpoint enforces is the `X-RateLimit-Limit` it returns.",
        "properties": {
          "requests_per_minute": {
            "type": "integer",
            "nullable": true
          },
          "window_seconds": {
            "type": "integer",
            "description": "Length of the sliding window."
          },
          "credential_capped": {
            "type": "boolean",
            "description": "True when this credential was held below its organization's ceiling."
          }
        }
      },
      "PublicPing": {
        "type": "object",
        "required": [
          "status",
          "organization_id",
          "time"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ]
          },
          "organization_id": {
            "type": "integer"
          },
          "time": {
            "type": "string",
            "format": "date_time",
            "description": "Our clock, UTC. Compare it against yours."
          }
        }
      },
      "PublicCustomerAccount": {
        "type": "object",
        "required": [
          "id",
          "account_type",
          "detail",
          "is_primary",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "account_type": {
            "type": "string",
            "description": "email, phone, instagram, …"
          },
          "detail": {
            "type": "string",
            "description": "The address, number or handle itself"
          },
          "label": {
            "type": "string",
            "nullable": true
          },
          "is_primary": {
            "type": "boolean",
            "description": "The one mirrored into the client's flat email/phone"
          },
          "created_at": {
            "type": "string",
            "format": "date_time"
          },
          "updated_at": {
            "type": "string",
            "format": "date_time"
          }
        }
      },
      "PublicCustomer": {
        "type": "object",
        "required": [
          "id",
          "is_archived",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "surname": {
            "type": "string",
            "nullable": true
          },
          "email": {
            "type": "string",
            "nullable": true,
            "description": "The primary email. Every address is in `accounts`."
          },
          "phone": {
            "type": "string",
            "nullable": true,
            "description": "The primary phone. Every number is in `accounts`."
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "is_archived": {
            "type": "boolean",
            "description": "Archived clients are hidden unless you ask for them"
          },
          "whatsapp_opt_out_at": {
            "type": "string",
            "format": "date_time",
            "nullable": true,
            "description": "When set, this person asked not to be messaged on WhatsApp. Honour it."
          },
          "default_locale": {
            "type": "string",
            "nullable": true
          },
          "time_zone": {
            "type": "string",
            "nullable": true
          },
          "merged_into_id": {
            "type": "integer",
            "nullable": true,
            "description": "Set when this record was merged into another. Follow it; do not keep writing here."
          },
          "created_at": {
            "type": "string",
            "format": "date_time"
          },
          "updated_at": {
            "type": "string",
            "format": "date_time"
          },
          "accounts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicCustomerAccount"
            },
            "description": "Only present with `?include=accounts`"
          }
        }
      },
      "PublicChannel": {
        "type": "object",
        "required": [
          "id",
          "platform",
          "is_active",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "platform": {
            "type": "string",
            "enum": [
              "instagram",
              "whatsapp",
              "voice",
              "facebook",
              "webchat"
            ]
          },
          "is_active": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date_time"
          }
        }
      },
      "PublicTag": {
        "type": "object",
        "required": [
          "id",
          "name",
          "system",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "color": {
            "type": "string",
            "nullable": true
          },
          "category_key": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "system": {
            "type": "boolean",
            "description": "Maintained by Fellix. Do not assume you can recreate it."
          },
          "created_at": {
            "type": "string",
            "format": "date_time"
          },
          "updated_at": {
            "type": "string",
            "format": "date_time"
          }
        }
      },
      "PublicMessage": {
        "type": "object",
        "required": [
          "id",
          "conversation_id",
          "message_type",
          "from_customer",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "conversation_id": {
            "type": "integer"
          },
          "message_type": {
            "type": "string",
            "enum": [
              "text",
              "image",
              "video",
              "audio",
              "document",
              "location",
              "contact",
              "poll",
              "event",
              "reaction",
              "sticker",
              "reply",
              "template",
              "interactive",
              "system"
            ]
          },
          "from_customer": {
            "type": "boolean",
            "description": "true for inbound, false for anything we sent"
          },
          "content": {
            "type": "string",
            "nullable": true
          },
          "language": {
            "type": "string",
            "nullable": true
          },
          "external_id": {
            "type": "string",
            "nullable": true,
            "description": "The provider's own id, for reconciliation"
          },
          "reply_to_id": {
            "type": "integer",
            "nullable": true
          },
          "delivery_status": {
            "type": "string",
            "nullable": true,
            "enum": [
              "sent",
              "delivered",
              "read",
              "failed",
              "pending"
            ],
            "description": "Null for an inbound message — delivery is only a question about what we sent. Attachments are not part of v1."
          },
          "sent_at": {
            "type": "string",
            "format": "date_time",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date_time"
          },
          "updated_at": {
            "type": "string",
            "format": "date_time"
          },
          "conversation": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PublicConversation"
              }
            ],
            "nullable": true,
            "description": "Only present with `?include=conversation`"
          }
        }
      },
      "PublicConversation": {
        "type": "object",
        "required": [
          "id",
          "status",
          "is_archived",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "customer_id": {
            "type": "integer",
            "nullable": true
          },
          "channel_id": {
            "type": "integer",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "completed",
              "escalated",
              "abandoned",
              "blocked"
            ]
          },
          "is_archived": {
            "type": "boolean"
          },
          "language": {
            "type": "string",
            "nullable": true
          },
          "lead_status": {
            "type": "string",
            "nullable": true,
            "enum": [
              "open",
              "won",
              "lost"
            ]
          },
          "qualification": {
            "type": "string",
            "nullable": true,
            "enum": [
              "hot",
              "warm",
              "cold"
            ]
          },
          "lost_reason": {
            "type": "string",
            "nullable": true
          },
          "rating": {
            "type": "integer",
            "nullable": true
          },
          "started_at": {
            "type": "string",
            "format": "date_time",
            "nullable": true
          },
          "ended_at": {
            "type": "string",
            "format": "date_time",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date_time"
          },
          "updated_at": {
            "type": "string",
            "format": "date_time"
          },
          "customer": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PublicCustomer"
              }
            ],
            "nullable": true,
            "description": "Only present with `?include=customer`"
          },
          "channel": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PublicChannel"
              }
            ],
            "nullable": true,
            "description": "Only present with `?include=channel`. Still named after the channel is removed."
          },
          "tags": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicTag"
            },
            "description": "Only present with `?include=tags`"
          },
          "last_message": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PublicMessage"
              }
            ],
            "nullable": true,
            "description": "Only present with `?include=last_message`"
          }
        }
      },
      "PublicAppointment": {
        "type": "object",
        "required": [
          "id",
          "starts_at",
          "ends_at",
          "status",
          "source",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "customer_id": {
            "type": "integer",
            "nullable": true
          },
          "conversation_id": {
            "type": "integer",
            "nullable": true
          },
          "channel_id": {
            "type": "integer",
            "nullable": true
          },
          "starts_at": {
            "type": "string",
            "format": "date_time"
          },
          "ends_at": {
            "type": "string",
            "format": "date_time"
          },
          "status": {
            "type": "string",
            "enum": [
              "requested",
              "confirmed",
              "cancelled",
              "completed",
              "no_show"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "operator",
              "assistant",
              "api"
            ],
            "description": "Who booked it"
          },
          "notes": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date_time"
          },
          "updated_at": {
            "type": "string",
            "format": "date_time"
          },
          "customer": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PublicCustomer"
              }
            ],
            "nullable": true,
            "description": "Only present with `?include=customer`"
          }
        }
      },
      "PublicUser": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "surname": {
            "type": "string",
            "nullable": true
          },
          "email": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "PublicLead": {
        "type": "object",
        "required": [
          "id",
          "source",
          "status",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "source": {
            "type": "string",
            "enum": [
              "meta_lead_ads",
              "meta_ctwa",
              "meta_ctm",
              "meta_organic",
              "native_form",
              "manual"
            ]
          },
          "platform": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "new",
              "processed",
              "failed"
            ]
          },
          "external_lead_id": {
            "type": "string",
            "nullable": true,
            "description": "The provider's own id for the submission"
          },
          "form_id": {
            "type": "string",
            "nullable": true
          },
          "form_name": {
            "type": "string",
            "nullable": true
          },
          "ad_id": {
            "type": "string",
            "nullable": true
          },
          "ad_name": {
            "type": "string",
            "nullable": true
          },
          "adset_id": {
            "type": "string",
            "nullable": true
          },
          "adset_name": {
            "type": "string",
            "nullable": true
          },
          "campaign_id": {
            "type": "string",
            "nullable": true
          },
          "campaign_name": {
            "type": "string",
            "nullable": true
          },
          "submitted_at": {
            "type": "string",
            "format": "date_time",
            "nullable": true
          },
          "customer_id": {
            "type": "integer",
            "nullable": true
          },
          "conversation_id": {
            "type": "integer",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date_time"
          },
          "updated_at": {
            "type": "string",
            "format": "date_time"
          },
          "customer": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PublicCustomer"
              }
            ],
            "nullable": true
          },
          "conversation": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PublicConversation"
              }
            ],
            "nullable": true
          }
        }
      },
      "PublicVoiceCall": {
        "type": "object",
        "required": [
          "id",
          "status",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "external_call_id": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "ringing",
              "in_progress",
              "completed",
              "failed",
              "no_answer",
              "busy",
              "canceled"
            ]
          },
          "direction": {
            "type": "string",
            "nullable": true,
            "enum": [
              "inbound",
              "outbound"
            ]
          },
          "from_number": {
            "type": "string",
            "nullable": true
          },
          "to_number": {
            "type": "string",
            "nullable": true
          },
          "started_at": {
            "type": "string",
            "format": "date_time",
            "nullable": true
          },
          "ended_at": {
            "type": "string",
            "format": "date_time",
            "nullable": true
          },
          "duration_seconds": {
            "type": "integer",
            "nullable": true
          },
          "transcript": {
            "type": "string",
            "nullable": true,
            "description": "Null until the call ends. The recording itself is not part of v1."
          },
          "customer_id": {
            "type": "integer",
            "nullable": true
          },
          "conversation_id": {
            "type": "integer",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date_time"
          },
          "updated_at": {
            "type": "string",
            "format": "date_time"
          },
          "customer": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PublicCustomer"
              }
            ],
            "nullable": true
          },
          "conversation": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PublicConversation"
              }
            ],
            "nullable": true
          }
        }
      },
      "PublicWhatsappCall": {
        "type": "object",
        "required": [
          "id",
          "status",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "wacid": {
            "type": "string",
            "nullable": true,
            "description": "Meta's own call id"
          },
          "status": {
            "type": "string",
            "enum": [
              "ringing",
              "completed",
              "missed",
              "failed"
            ]
          },
          "direction": {
            "type": "string",
            "nullable": true,
            "enum": [
              "inbound",
              "outbound"
            ]
          },
          "from_phone": {
            "type": "string",
            "nullable": true
          },
          "to_phone": {
            "type": "string",
            "nullable": true
          },
          "connected_at": {
            "type": "string",
            "format": "date_time",
            "nullable": true
          },
          "started_at": {
            "type": "string",
            "format": "date_time",
            "nullable": true
          },
          "ended_at": {
            "type": "string",
            "format": "date_time",
            "nullable": true
          },
          "duration_seconds": {
            "type": "integer",
            "nullable": true
          },
          "channel_id": {
            "type": "integer",
            "nullable": true
          },
          "customer_id": {
            "type": "integer",
            "nullable": true
          },
          "conversation_id": {
            "type": "integer",
            "nullable": true
          },
          "message_id": {
            "type": "integer",
            "nullable": true,
            "description": "The timeline row this call is rendered as"
          },
          "created_at": {
            "type": "string",
            "format": "date_time"
          },
          "updated_at": {
            "type": "string",
            "format": "date_time"
          },
          "customer": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PublicCustomer"
              }
            ],
            "nullable": true
          },
          "conversation": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PublicConversation"
              }
            ],
            "nullable": true
          },
          "channel": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PublicChannel"
              }
            ],
            "nullable": true
          },
          "message": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PublicMessage"
              }
            ],
            "nullable": true
          }
        }
      },
      "PublicWhatsappCallForward": {
        "type": "object",
        "required": [
          "id",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "target_kind": {
            "type": "string",
            "nullable": true,
            "description": "What the call was forwarded to"
          },
          "dial_call_status": {
            "type": "string",
            "nullable": true,
            "description": "The outcome of the attempt"
          },
          "from_phone": {
            "type": "string",
            "nullable": true
          },
          "caller_id": {
            "type": "string",
            "nullable": true
          },
          "destination_number": {
            "type": "string",
            "nullable": true
          },
          "duration_seconds": {
            "type": "integer",
            "nullable": true
          },
          "dialed_at": {
            "type": "string",
            "format": "date_time",
            "nullable": true
          },
          "completed_at": {
            "type": "string",
            "format": "date_time",
            "nullable": true
          },
          "channel_id": {
            "type": "integer",
            "nullable": true
          },
          "whatsapp_call_id": {
            "type": "integer",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date_time"
          },
          "updated_at": {
            "type": "string",
            "format": "date_time"
          },
          "channel": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PublicChannel"
              }
            ],
            "nullable": true
          },
          "whatsapp_call": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PublicWhatsappCall"
              }
            ],
            "nullable": true,
            "description": "Null while the link to the originating call has not resolved, which is a normal state"
          }
        }
      },
      "PublicTeam": {
        "type": "object",
        "required": [
          "id",
          "name",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "total_members": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date_time"
          },
          "updated_at": {
            "type": "string",
            "format": "date_time"
          },
          "channels": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicChannel"
            }
          }
        }
      },
      "PublicTeamMember": {
        "type": "object",
        "required": [
          "id",
          "team_id",
          "user_id",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "team_id": {
            "type": "integer"
          },
          "user_id": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date_time"
          },
          "updated_at": {
            "type": "string",
            "format": "date_time"
          },
          "team": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PublicTeam"
              }
            ],
            "nullable": true
          },
          "user": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PublicUser"
              }
            ],
            "nullable": true
          }
        }
      },
      "WebhookEnvelope": {
        "type": "object",
        "required": [
          "id",
          "type",
          "api_version",
          "created_at",
          "organization_id",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable across endpoints AND redeliveries — dedupe on it"
          },
          "type": {
            "type": "string",
            "description": "The event name, e.g. conversation.created"
          },
          "api_version": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date_time",
            "description": "When the event happened"
          },
          "organization_id": {
            "type": "integer"
          },
          "data": {
            "type": "object",
            "required": [
              "type",
              "id",
              "attributes"
            ],
            "properties": {
              "type": {
                "type": "string",
                "description": "The resource, e.g. conversation"
              },
              "id": {
                "type": "integer"
              },
              "attributes": {
                "type": "object",
                "description": "The resource, in exactly the shape a GET of it returns. Reflects the record at payload-build time, not at delivery time."
              },
              "metrics": {
                "type": "object",
                "nullable": true,
                "description": "Present on metric-sourced events"
              }
            }
          }
        }
      },
      "PublicCustomerInput": {
        "type": "object",
        "required": [
          "customer"
        ],
        "properties": {
          "customer": {
            "type": "object",
            "required": [
              "name"
            ],
            "properties": {
              "name": {
                "type": "string"
              },
              "surname": {
                "type": "string",
                "nullable": true
              },
              "email": {
                "type": "string",
                "nullable": true,
                "description": "Unique within the organization"
              },
              "phone": {
                "type": "string",
                "nullable": true,
                "description": "Unique within the organization"
              },
              "description": {
                "type": "string",
                "nullable": true
              },
              "is_archived": {
                "type": "boolean"
              },
              "whatsapp_opt_out_at": {
                "type": "string",
                "format": "date_time",
                "nullable": true,
                "description": "Set it to record a refusal you took yourself; clear it only on an explicit request from the person."
              },
              "default_locale": {
                "type": "string",
                "nullable": true
              },
              "time_zone": {
                "type": "string",
                "nullable": true,
                "description": "IANA name"
              }
            }
          }
        }
      },
      "PublicAppointmentInput": {
        "type": "object",
        "required": [
          "appointment"
        ],
        "properties": {
          "appointment": {
            "type": "object",
            "required": [
              "customer_id",
              "starts_at"
            ],
            "properties": {
              "customer_id": {
                "type": "integer"
              },
              "conversation_id": {
                "type": "integer",
                "nullable": true
              },
              "channel_id": {
                "type": "integer",
                "nullable": true
              },
              "starts_at": {
                "type": "string",
                "format": "date_time"
              },
              "ends_at": {
                "type": "string",
                "format": "date_time",
                "nullable": true,
                "description": "Omit and it becomes one slot of the organization's configured length. Moving `starts_at` without an end keeps the same duration."
              },
              "status": {
                "type": "string",
                "enum": [
                  "requested",
                  "confirmed",
                  "cancelled",
                  "completed",
                  "no_show"
                ],
                "description": "Cancel by setting `cancelled`. There is no DELETE — a cancelled booking is kept."
              },
              "notes": {
                "type": "string",
                "nullable": true
              }
            }
          }
        }
      },
      "PublicMessageInput": {
        "type": "object",
        "required": [
          "message"
        ],
        "properties": {
          "message": {
            "type": "object",
            "properties": {
              "content": {
                "type": "string",
                "description": "Length limits are the platform's: 4096 on WhatsApp, 2000 on Instagram."
              },
              "file": {
                "type": "string",
                "format": "binary",
                "description": "Send as multipart/form-data. Website chat channels only — the other platforms' send paths carry text, so a file accepted there would be stored and never delivered."
              }
            }
          }
        }
      },
      "PublicTemplateInput": {
        "type": "object",
        "required": [
          "template"
        ],
        "properties": {
          "template": {
            "type": "object",
            "required": [
              "name",
              "language"
            ],
            "properties": {
              "name": {
                "type": "string",
                "description": "The approved template name as WhatsApp knows it"
              },
              "language": {
                "type": "string",
                "description": "e.g. en_US"
              },
              "components": {
                "type": "array",
                "items": {
                  "type": "object"
                },
                "description": "Body/header parameters, in Meta's own component shape"
              }
            }
          }
        }
      },
      "MetaForPeriod": {
        "type": "object",
        "required": [
          "code",
          "status",
          "title",
          "detail",
          "period"
        ],
        "properties": {
          "code": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "title": {
            "type": "string"
          },
          "detail": {
            "type": "string"
          },
          "period": {
            "type": "object",
            "required": [
              "start",
              "end",
              "granularity"
            ],
            "description": "The window this answer is for. Always present, because a figure without its window is not a figure.",
            "properties": {
              "start": {
                "type": "string",
                "format": "date_time"
              },
              "end": {
                "type": "string",
                "format": "date_time"
              },
              "granularity": {
                "type": "string",
                "enum": [
                  "day",
                  "week",
                  "month"
                ]
              }
            }
          }
        }
      },
      "PublicPipelineAnalytics": {
        "type": "object",
        "required": [
          "open",
          "won",
          "lost",
          "win_rate"
        ],
        "properties": {
          "open": {
            "type": "integer",
            "description": "Conversations sitting at `lead_status: open`"
          },
          "won": {
            "type": "integer"
          },
          "lost": {
            "type": "integer"
          },
          "win_rate": {
            "type": "number",
            "description": "Percentage of settled conversations that were won"
          },
          "avg_days_to_won": {
            "type": "number",
            "nullable": true
          },
          "avg_days_to_lost": {
            "type": "number",
            "nullable": true
          },
          "by_lost_reason": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Keyed by your own lost-reason keys, not by anything we define"
          },
          "by_qualification": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Keyed by qualification (hot/warm/cold)"
          }
        }
      },
      "PublicTrendAnalytics": {
        "type": "object",
        "required": [
          "granularity",
          "points"
        ],
        "properties": {
          "granularity": {
            "type": "string",
            "enum": [
              "day",
              "week",
              "month"
            ],
            "description": "The one actually used — echoed because an unrecognised request falls back"
          },
          "points": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "period",
                "won",
                "lost"
              ],
              "properties": {
                "period": {
                  "type": "string",
                  "format": "date_time",
                  "description": "Start of the bucket"
                },
                "won": {
                  "type": "integer"
                },
                "lost": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "PublicTagAnalytics": {
        "type": "object",
        "required": [
          "by_tag"
        ],
        "properties": {
          "by_tag": {
            "type": "array",
            "description": "Distinct conversations per tag in the window, ordered by volume.",
            "items": {
              "type": "object",
              "required": [
                "tag",
                "total",
                "open",
                "won",
                "lost",
                "win_rate"
              ],
              "properties": {
                "tag": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "name": {
                      "type": "string"
                    },
                    "color": {
                      "type": "string",
                      "nullable": true
                    },
                    "category_key": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                },
                "total": {
                  "type": "integer"
                },
                "open": {
                  "type": "integer"
                },
                "won": {
                  "type": "integer"
                },
                "lost": {
                  "type": "integer"
                },
                "win_rate": {
                  "type": "number"
                }
              }
            }
          }
        }
      },
      "OAuthToken": {
        "type": "object",
        "required": [
          "access_token",
          "token_type",
          "expires_in"
        ],
        "properties": {
          "access_token": {
            "type": "string"
          },
          "token_type": {
            "type": "string",
            "enum": [
              "Bearer"
            ]
          },
          "expires_in": {
            "type": "integer",
            "description": "Seconds."
          },
          "scope": {
            "type": "string",
            "description": "Space-separated, as RFC 6749 requires."
          }
        }
      },
      "OAuthError": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "invalid_client",
              "unsupported_grant_type"
            ]
          },
          "error_description": {
            "type": "string"
          }
        }
      }
    },
    "parameters": {
      "cursor_limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100
        },
        "description": "Page size. Default 50, maximum 100 (larger values are clamped, not rejected)."
      },
      "period_start": {
        "name": "start_date",
        "in": "query",
        "required": false,
        "schema": {
          "type": "string",
          "format": "date"
        },
        "description": "ISO8601 date (YYYY-MM-DD). Defaults to 30 days ago. Anything else is a 400 — a date we guessed at is a window you never asked for."
      },
      "period_end": {
        "name": "end_date",
        "in": "query",
        "required": false,
        "schema": {
          "type": "string",
          "format": "date"
        },
        "description": "ISO8601 date (YYYY-MM-DD). Defaults to today."
      },
      "period_granularity": {
        "name": "granularity",
        "in": "query",
        "required": false,
        "schema": {
          "type": "string",
          "enum": [
            "day",
            "week",
            "month"
          ]
        },
        "description": "Bucket size for the series. An unrecognised value falls back to `day`, and the response says which one it used."
      },
      "idempotency_key": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "maxLength": 255
        },
        "description": "Optional, and the only safe way to retry a write. Send a value you generate (a UUID). The first answer is stored for 24 hours and returned verbatim on a retry, with `Idempotency-Replayed: true`. The same key with a different body is refused (409). A 5xx releases the key, so retrying after one really does retry."
      },
      "cursor_after": {
        "name": "after",
        "in": "query",
        "required": false,
        "schema": {
          "type": "string"
        },
        "description": "The `meta.pagination.next_cursor` from the previous response, sent back unchanged."
      }
    },
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "A static key (`flx_live_…`) or an access token from `POST /v1/oauth/token`."
      }
    }
  }
}