{"openapi":"3.1.0","info":{"title":"Labeeq API","version":"1.0.0","summary":"Read and write the conversations, contacts and deals in a Labeeq workspace.","description":"The Labeeq API lets you build on top of your own workspace: read contacts and\nconversations, send a real message on any connected channel, and open deals in\nyour pipeline. It is a JSON REST API over HTTPS, included in every plan.\n\nRather click around first? Import the [Postman collection](https://developers.labeeq.com/postman_collection.json) — every\nendpoint below, with example bodies and the auth header already pointed at a\n`token` variable. It is generated from this document, so the two cannot disagree.\n\n## Capability availability\n\n| Capability | Web | Developer API | Mobile API | Connectors |\n| --- | --- | --- | --- | --- |\n| جهات الاتصال | جهات الاتصال | Available | Available | عبر واجهة المطور |\n| رسائل العملاء | صندوق الوارد | Available | Available | عبر واجهة المطور |\n| الطلبات | الطلبات | Available | Available | عبر واجهة المطور |\n| اكتشاف معرّفات مساحة العمل | إعدادات مساحة العمل | Unavailable | Available | غير متاح عبر الواجهة العامة |\n| تعديل الحقول المخصصة | جهات الاتصال | Unavailable | Unavailable | غير متاح عبر الواجهة العامة |\n| بدء سير عمل | صندوق الوارد | Unavailable | Unavailable | غير متاح عبر الواجهة العامة |\n| إرسال حملة | Campaigns | Unavailable | Unavailable | غير متاح عبر الواجهة العامة |\n| إدارة اشتراكات إشعارات الويب | Integrations | Unavailable | Unavailable | غير متاح عبر الواجهة العامة |\n\nAvailability is subject to workspace permissions and plan limits. Mobile API entries\ndescribe the backend contract, not a guarantee of a corresponding screen in every app version.\nOutbound webhook events can be configured in the web application even when webhook\nsubscription management is unavailable over the Developer API. Connectors cannot bypass\ndisabled endpoints. Google Sheets and Meta Conversions availability is shown in the\nintegration hub; a Coming soon connector is not a supported customer workflow.\n\n## Authentication\n\nEvery request carries a personal access token as a bearer token:\n\n```\nAuthorization: Bearer YOUR_TOKEN\n```\n\nCreate tokens in **Settings → Integrations → Developer API**. The plain token is\nshown once, at creation — only its hash is stored, so a lost token is replaced,\nnever recovered. A token may carry an **expiry date**; the workspace's owners are\nemailed a week before it, and an expired token answers `401` with\n`code: token_expired`. **Rotate** it from the same page: the replacement keeps the\nname, abilities, allowlist and lifetime, and the old key can be left working for\na short grace period while you swap it in.\n\nA token belongs in the header and nowhere else. Requests carrying a `token` or\n`api_token` **query parameter are refused with `400`**, because a token in a URL\nis copied into access logs, proxy caches and browser history. Requests over plain\nHTTP are refused with `403`; every call must be TLS.\n\nCall `GET /me` first: it answers with the workspace the token belongs to and the\nabilities it carries, which is the quickest way to confirm your setup.\n\nA token can also be **pinned to the addresses it may be used from** — an exact\naddress or a CIDR range, set when you create it. Used from anywhere else it\nanswers `403`, which makes a leaked token far less useful to whoever found it.\nEvery write your tokens make is recorded with its token, address and result, and\nthe trail is visible in the same settings page.\n\n## Abilities\n\nA token carries a fixed set of abilities, chosen when you create it. **An ability\ncan only narrow what its owner may already do** — the call must pass *both* the\ntoken's abilities and the owning teammate's own role permissions. Revoking a\nteammate's access therefore revokes their tokens' reach at the same time.\n\n| Ability | Grants |\n| --- | --- |\n| `contacts:read` | قراءة جهات الاتصال |\n| `contacts:write` | إنشاء وتعديل جهات الاتصال |\n| `contacts:delete` | حذف جهات الاتصال |\n| `conversations:read` | قراءة المحادثات |\n| `conversations:write` | فتح وإغلاق وإسناد وتوجيه المحادثات |\n| `messages:send` | إرسال الرسائل |\n| `deals:read` | قراءة الصفقات |\n| `deals:write` | إنشاء الصفقات وتعديلها |\n| `orders:read` | قراءة الطلبات |\n| `orders:write` | إنشاء الطلبات وتعديلها ونقلها |\n| `products:read` | قراءة كتالوج المنتجات |\n| `tasks:read` | قراءة المهام |\n| `tasks:write` | إنشاء المهام وتعديلها |\n\n## Workspaces\n\nA token is bound to the workspace of the teammate who created it. Every response is\nscoped to that workspace: a record belonging to another one answers `404`, never\nanother company's data.\n\n## Pagination\n\nCollections are cursor-paginated. Follow `meta.next_cursor` and pass it back as\n`?cursor=`; `per_page` defaults to 50 and caps at 100. Cursors are opaque — treat\nthem as strings and do not construct them yourself.\n\n## Rate limits\n\nTwo limits apply, and **both announce themselves on every response** so you can slow\ndown before you are cut off rather than discovering the wall by hitting it.\n\n| Header | Means |\n| --- | --- |\n| `X-RateLimit-Limit` / `X-RateLimit-Remaining` | The **burst** limit — 30, 60 or 100 requests per 10 seconds, by plan. |\n| `X-Quota-Limit` / `X-Quota-Remaining` | The **month's** allowance of total calls, by plan. |\n| `X-Quota-Reset` | When that allowance resets — the start of next month. |\n\nExceeding either answers `429` with a `Retry-After` in seconds. The difference matters:\na burst clears in seconds, while the monthly allowance is spent for the **rest of the\nmonth**, so `Retry-After` there points at next month rather than a moment away. Plans\nwithout a monthly cap send no quota headers rather than inventing an infinity.\n\nBack off and retry rather than hammering — the burst counter is per token, so a\nrunaway script starves your other integrations too.\n\n## Retrying a write safely\n\nA timeout tells you nothing about whether the work happened. Send an\n`Idempotency-Key` header on any `POST` — any unique string of your own, an order\nid or a UUID — and retrying with the same key returns the **first** response\ninstead of sending a second message or opening a second deal.\n\n```\nIdempotency-Key: order-4821-shipped\n```\n\nA replayed response carries `Idempotent-Replay: true`. Keys are remembered for 24\nhours and are scoped to your workspace, so they cannot collide with another\ncustomer's. Reusing a key with a *different* body answers `422` — that is a bug\non your side, and returning the earlier result would hide it. A request that\nfailed is not recorded, so you can fix the body and retry with the same key.\nSending no key keeps the old behaviour: every call does the work.\n\nNot every `POST` is a write: searching contacts asks a question in a body and\nhonours no key. Operations that accept one document the `Idempotency-Key` header;\nthe rest do not.\n\n## Events and no-code automation\n\nThis API is for reading and writing your workspace directly. If you want events\npushed to you the moment something happens, or automation without writing code,\nuse the **Zapier integration** (Settings → Integrations → Zapier) — it carries the\nsame message, conversation and contact events and needs no polling.\n\nEndpoints of your own subscribe in **Settings → Integrations → Webhooks**. Every\nevent they can receive is listed under **Webhooks** in this reference, with the\nexact payload it carries: the same resource shapes the API returns.\n\nThe Zapier and n8n pages issue their keys as ordinary **scoped tokens**: preset to\nread and write contacts, conversations, messages, deals and tasks, and listed with\nyour other tokens, where they can be pinned, rotated and revoked. Keys those pages\nmade before scoped tokens carry every ability; they keep working, and the pages\noffer to switch them. If the platform ever sets a retirement date for them, a call\nwith such a key after that date answers `403` with `code` `legacy_key_retired`.\n\n## Errors\n\nEvery error is JSON of the same shape — a `message`, plus an `errors` map when a\nbody failed validation — so one handler covers them all. Internal details are\nnever returned: an unexpected failure is a plain `500`, with nothing about the\nframework or the database in it.\n\n| Code | What it means | What to do |\n| --- | --- | --- |\n| `400` | The request itself was malformed — a token in the query string, for instance. | Fix the request; retrying will not help. |\n| `401` | No token, or one that has been revoked. When the token exists but is past its date, `code` is `token_expired`. | Rotate the token in Settings → Integrations — the replacement keeps its abilities and allowlist, so only the key changes. |\n| `402` | A plan allowance is spent — sending a campaign beyond the month's broadcast limit. | Upgrade, or wait for the month to turn. |\n| `403` | The token lacks the ability, **or** its teammate lacks the permission — both must allow the call. Also answered when the call was not over HTTPS, and with `code` `legacy_key_retired` for a retired full-access Zapier or n8n key. | Check the ability list above, then the teammate's role. |\n| `404` | No such record **in this workspace**. Another company's record answers this too, never their data. | Check the id, and that it belongs to you. |\n| `409` | A request with this `Idempotency-Key` is still in flight. | Wait a moment and retry with the same key. |\n| `422` | The body failed validation, or a value names something that does not exist — a label, a stage, a template. | Read `errors`; it names the field and what was allowed. |\n| `429` | Rate limited — burst or monthly. | Honour `Retry-After`. |\n\n**A `422` is usually a refusal, not a bug.** This API declines to guess: a search\nrule it cannot read, a label the workspace does not have, a template that exists in\ntwo languages, a merge missing one of its two sides — each says so and names the\nfield, rather than doing something plausible and leaving you to discover it later.","contact":{"name":"Labeeq support","url":"https://labeeq.com/contact"}},"servers":[{"url":"https://developers.labeeq.com/api/v1","description":"Labeeq API"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Account","description":"Who the token belongs to."},{"name":"Contacts","description":"The people who talk to you — one record per person, across every channel."},{"name":"Conversations","description":"Threads with a contact on a channel."},{"name":"Messages","description":"Reading a thread, and sending into it."},{"name":"Deals","description":"Revenue opportunities moving along their pipelines."},{"name":"Orders","description":"What a customer bought — lines, delivery, payment, and where it is on the way."},{"name":"Products","description":"The catalogue an order's lines refer to, read-only."},{"name":"Tasks","description":"Work the team owes somebody, with an optional contact attached."},{"name":"Webhooks","description":"Events Labeeq sends to your own endpoint as a signed POST, the moment they happen."}],"paths":{"/me":{"get":{"tags":["Account"],"operationId":"getMe","summary":"Who am I","description":"The token's teammate, their workspace, and the abilities the token carries. Costs nothing and touches no data — the call to make first.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Me"}}},"example":{"data":{"user":{"id":7,"name":"Mona Zaki","email":"mona@hadayek.com"},"company":{"id":3,"name":"Hadayek Clinics"},"account":{"id":2,"name":"Hadayek Group"},"abilities":["contacts:read","messages:send"]}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/contacts":{"get":{"tags":["Contacts"],"operationId":"listContacts","summary":"List contacts","description":"Contacts in the workspace, newest first. `search` matches name and email; it also matches phone numbers when the token's teammate may see them. Filters combine: every one you send must match.\n\n**Required ability:** `contacts:read`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},"meta":{"$ref":"#/components/schemas/CursorMeta"}}},"example":{"data":[{"id":42,"name":"Mona Zaki","phone":"+201001234567","email":"mona@example.com","lifecycle_stage_id":3,"blocked":false,"created_at":"2026-07-26T09:41:00+00:00","updated_at":"2026-07-26T09:41:00+00:00"}],"meta":{"next_cursor":null,"per_page":50}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/Search"},{"$ref":"#/components/parameters/Email"},{"$ref":"#/components/parameters/Phone"},{"$ref":"#/components/parameters/Label"},{"$ref":"#/components/parameters/LifecycleStageId"},{"$ref":"#/components/parameters/CreatedAfter"},{"$ref":"#/components/parameters/CreatedBefore"},{"$ref":"#/components/parameters/UpdatedAfter"},{"$ref":"#/components/parameters/Include"},{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"}]},"post":{"tags":["Contacts"],"operationId":"createContact","summary":"Create a contact","description":"Adds a person to the workspace. A contact needs a name; give a phone or an email so you can reach them.\n\n**Required ability:** `contacts:write`","responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Contact"}}},"example":{"data":{"id":42,"name":"Mona Zaki","phone":"+201001234567","email":"mona@example.com","lifecycle_stage_id":3,"blocked":false,"created_at":"2026-07-26T09:41:00+00:00","updated_at":"2026-07-26T09:41:00+00:00"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/ValidationFailed"},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":255,"examples":["Mona Zaki"]},"email":{"type":["string","null"],"format":"email","examples":["mona@example.com"]},"phone":{"type":["string","null"],"maxLength":32,"examples":["+201001234567"],"description":"Answers `403` without the view-phone permission — a teammate shown a masked number may not replace it."},"lifecycle_stage_id":{"type":["integer","null"],"description":"A stage from Settings → Lifecycle."}}}}}}}},"/contacts/{id}":{"get":{"tags":["Contacts"],"operationId":"getContact","summary":"Get a contact","description":"One contact, addressed by id or by something you already hold — see the `id` parameter. A contact in another workspace answers `404`.\n\n**Required ability:** `contacts:read`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Contact"}}},"example":{"data":{"id":42,"name":"Mona Zaki","phone":"+201001234567","email":"mona@example.com","lifecycle_stage_id":3,"blocked":false,"created_at":"2026-07-26T09:41:00+00:00","updated_at":"2026-07-26T09:41:00+00:00"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/ContactId"},{"$ref":"#/components/parameters/Include"}]},"patch":{"tags":["Contacts"],"operationId":"updateContact","summary":"Update a contact","description":"Changes the fields you send and leaves the rest alone — sending only `email` will not empty the name. Moving `lifecycle_stage_id` runs the same rules the panel does, so open deals settle with the stage.\n\n**Required ability:** `contacts:write`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Contact"}}},"example":{"data":{"id":42,"name":"Mona Zaki","phone":"+201001234567","email":"mona@example.com","lifecycle_stage_id":3,"blocked":false,"created_at":"2026-07-26T09:41:00+00:00","updated_at":"2026-07-26T09:41:00+00:00"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationFailed"},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/ContactId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":[],"properties":{"name":{"type":"string","maxLength":255,"examples":["Mona Zaki"]},"email":{"type":["string","null"],"format":"email","examples":["mona@example.com"]},"phone":{"type":["string","null"],"maxLength":32,"examples":["+201001234567"],"description":"Answers `403` without the view-phone permission — a teammate shown a masked number may not replace it."},"lifecycle_stage_id":{"type":["integer","null"],"description":"A stage from Settings → Lifecycle."}}}}}}},"put":{"tags":["Contacts"],"operationId":"upsertContact","summary":"Create or update a contact","description":"Makes the contact at this address look like the body, creating it when there is none — the call a sync loop makes when it holds an email or a phone and does not know whether you have the person already. Answers `201` when it created and `200` when it updated.\n\nAddressing by `id:` never creates: an id is ours to issue, so one we never gave out answers `404`.\n\n**Required ability:** `contacts:write`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Contact"}}},"example":{"data":{"id":42,"name":"Mona Zaki","phone":"+201001234567","email":"mona@example.com","lifecycle_stage_id":3,"blocked":false,"created_at":"2026-07-26T09:41:00+00:00","updated_at":"2026-07-26T09:41:00+00:00"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationFailed"},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/ContactId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":[],"properties":{"name":{"type":"string","maxLength":255,"examples":["Mona Zaki"]},"email":{"type":["string","null"],"format":"email","examples":["mona@example.com"]},"phone":{"type":["string","null"],"maxLength":32,"examples":["+201001234567"],"description":"Answers `403` without the view-phone permission — a teammate shown a masked number may not replace it."},"lifecycle_stage_id":{"type":["integer","null"],"description":"A stage from Settings → Lifecycle."}}}}}}},"delete":{"tags":["Contacts"],"operationId":"deleteContact","summary":"Delete a contact","description":"Removes the contact from the workspace. Their conversations stay in the record. Deleting has its own ability, so a sync token can create and update without ever being able to empty the address book.\n\n**Required ability:** `contacts:delete`","responses":{"204":{"description":"No content"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/ContactId"}]}},"/contacts/search":{"post":{"tags":["Contacts"],"operationId":"searchContacts","summary":"Search contacts","description":"Everything the Contacts page can filter on, as a body — a `POST` because the question\ndoes not fit in a query string, not because it changes anything.\n\nRules are grouped: rules **inside** a group must all match, and a contact matching\n**any** group is returned. Sending no rules matches everyone, which is the unfiltered\nlist. The answer is a normal cursor page, so `include` and `cursor` work as they do on\n`GET /contacts`.\n\nThese are the same rules the segment builder writes and the same engine that decides a\ncampaign's audience, so a search here and a saved view in the panel cannot disagree\nabout who matches. Send `segment_id` to run a **saved segment** by id instead.\n\n## Rules\n\nA rule is `{\"attribute\": …, \"operator\": …, \"value\": …}`. These operators are\n``set`, `not_set`, `never`` and take no value; every other operator needs one.\n\n| Attribute | Operators |\n| --- | --- |\n| `lifecycle_stage` | `is` `is_not` |\n| `label` | `has` `has_not` |\n| `language` | `is` |\n| `channel` | `has` |\n| `last_seen` | `within_days` `older_than_days` `never` |\n| `created` | `within_days` `older_than_days` |\n| `name` | `contains` `set` `not_set` |\n| `email` | `contains` `set` `not_set` |\n| `phone` | `contains` `set` `not_set` |\n\nA rule the vocabulary does not recognise answers `422` naming the rule and what was\nallowed there — an unknown operator returning an empty page would be indistinguishable\nfrom nobody matching. Filtering by `phone` needs the view-phone permission, for the\nsame reason looking a contact up by phone does.\n\n**Required ability:** `contacts:read`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},"meta":{"$ref":"#/components/schemas/CursorMeta"}}},"example":{"data":[{"id":42,"name":"Mona Zaki","phone":"+201001234567","email":"mona@example.com","lifecycle_stage_id":3,"blocked":false,"created_at":"2026-07-26T09:41:00+00:00","updated_at":"2026-07-26T09:41:00+00:00"}],"meta":{"next_cursor":null,"per_page":50}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationFailed"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/Include"},{"$ref":"#/components/parameters/Cursor"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":[],"properties":{"search":{"type":["string","null"],"description":"Free text over name and email (and phone, with the view-phone permission).","examples":["mona"]},"match":{"type":"array","description":"Groups of rules. Rules inside a group are ANDed; the groups are ORed.","items":{"type":"array","items":{"$ref":"#/components/schemas/SegmentRule"}},"examples":[[[{"attribute":"label","operator":"has","value":5},{"attribute":"created","operator":"within_days","value":7}]]]},"segment_id":{"type":["integer","null"],"description":"Run a saved segment instead of sending rules. Takes precedence over `match`."},"per_page":{"type":"integer","minimum":1,"maximum":100,"default":50}}}}}}}},"/contacts/merge":{"post":{"tags":["Contacts"],"operationId":"mergeContacts","summary":"Merge two contacts","description":"Folds one contact into another — usually the same person who reached in on two channels. The loser's conversations, deals, ratings, channel handles, labels and custom fields all move across; the survivor's own values win a clash and fill any gaps from the loser; the loser is then removed. Answers with the survivor.\n\n**Which one survives is yours to say.** Both are named and there is no default: an API that guessed — newest wins, most conversations wins — would quietly destroy the wrong record at scale.\n\nGated on the delete ability, because a contact goes away.\n\n**Required ability:** `contacts:delete`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Contact"}}},"example":{"data":{"id":42,"name":"Mona Zaki","phone":"+201001234567","email":"mona@example.com","lifecycle_stage_id":3,"blocked":false,"created_at":"2026-07-26T09:41:00+00:00","updated_at":"2026-07-26T09:41:00+00:00"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/ValidationFailed"},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["survivor","loser"],"properties":{"survivor":{"type":["string","integer"],"description":"The contact to keep, as an id or any contact identifier.","examples":["id:42"]},"loser":{"type":["string","integer"],"description":"The contact to fold in and remove.","examples":["phone:+201001234567"]}}}}}}}},"/contacts/{id}/lifecycle":{"put":{"tags":["Contacts"],"operationId":"setContactLifecycle","summary":"Move a contact's lifecycle stage","description":"Moves the contact along the lifecycle, by stage id or exact name. The lifecycle **is** the pipeline here, so this is not a field being set: reaching the winning stage settles the contact's open deals, and the move is written to their timeline.\n\nAnswers with the stage they were on before — `null` when they were already on the one you asked for, so \"nothing moved\" is stated rather than left to be inferred.\n\nThere is no way to remove a lifecycle: a contact always sits somewhere on the journey, starting at the default stage the moment they are created.\n\n**Required ability:** `contacts:write`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ContactLifecycle"}}},"example":{"data":{"contact_id":42,"previous":{"id":1,"name":"New","role":"default","outcome":null},"lifecycle":{"id":4,"name":"Customer","role":null,"outcome":"won"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationFailed"},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/ContactId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["stage"],"properties":{"stage":{"type":["string","integer"],"description":"A stage id, or its exact name from Settings → Lifecycle. An unknown one answers `422`.","examples":["Customer"]}}}}}}}},"/contacts/{id}/labels":{"get":{"tags":["Contacts"],"operationId":"listContactLabels","summary":"List a contact's labels","description":"The labels on one contact, by name — tags, in the vocabulary some other tools use.\n\n**Required ability:** `contacts:read`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/LabelRef"}}}},"example":{"data":[{"id":5,"name":"VIP"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/ContactId"}]},"post":{"tags":["Contacts"],"operationId":"addContactLabels","summary":"Add labels to a contact","description":"Attaches labels the workspace already has, by id or by exact name, and answers with the contact's labels afterwards. Adding one that is already there changes nothing.\n\n**Labels are never invented.** A name the workspace does not have fails the whole call with `422` rather than creating it — a typo in an integration would otherwise grow the label list until nobody can find anything. Create labels in Settings.\n\n**Required ability:** `contacts:write`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/LabelRef"}}}},"example":{"data":[{"id":5,"name":"VIP"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationFailed"},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/ContactId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["labels"],"properties":{"labels":{"type":"array","minItems":1,"description":"Label ids or exact names.","items":{"type":["string","integer"]},"examples":[["VIP",12]]}}}}}}}},"/contacts/{id}/labels/{label}":{"delete":{"tags":["Contacts"],"operationId":"removeContactLabel","summary":"Remove a label from a contact","description":"Takes one label off a contact and answers with the labels that remain. The label itself is untouched — it stays in the workspace for everyone else. Removing one the contact does not carry is not an error.\n\n**Required ability:** `contacts:write`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/LabelRef"}}}},"example":{"data":[]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/ContactId"},{"name":"label","in":"path","required":true,"description":"The label's id, or its exact name.","schema":{"type":"string"},"examples":{"default":{"value":"VIP"}}}]}},"/conversations":{"get":{"tags":["Conversations"],"operationId":"listConversations","summary":"List conversations","description":"Threads in the workspace, newest first, each with the channel it runs on.\n\n**Required ability:** `conversations:read`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Conversation"}},"meta":{"$ref":"#/components/schemas/CursorMeta"}}},"example":{"data":[{"id":1024,"contact_id":42,"channel":{"id":2,"name":"Clinic WhatsApp","type":"whatsapp"},"status":"open","subject":null,"assignee":{"type":"user","id":7},"last_message_at":"2026-07-26T09:41:00+00:00","closed_at":null,"created_at":"2026-07-26T09:12:00+00:00"}],"meta":{"next_cursor":null,"per_page":50}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"name":"status","in":"query","required":false,"description":"Only threads in this status.","schema":{"type":"string","enum":["open","pending","snoozed","closed"]}},{"name":"contact_id","in":"query","required":false,"schema":{"type":"integer"}},{"name":"channel_id","in":"query","required":false,"schema":{"type":"integer"}},{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"}]},"post":{"tags":["Conversations"],"operationId":"openConversation","summary":"Open a conversation","description":"Starts a thread with a contact on one of your channels.\n\nA contact has at most one live thread per channel, so an open one answers `200` with the thread you already have rather than opening a second — a customer with two conversations on the same number is a mess for the agent, not a feature. A genuinely new thread answers `201`.\n\n**Required ability:** `conversations:write`","responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Conversation"}}},"example":{"data":{"id":1024,"contact_id":42,"channel":{"id":2,"name":"Clinic WhatsApp","type":"whatsapp"},"status":"open","subject":null,"assignee":{"type":"user","id":7},"last_message_at":"2026-07-26T09:41:00+00:00","closed_at":null,"created_at":"2026-07-26T09:12:00+00:00"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/ValidationFailed"},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["contact","channel_id"],"properties":{"contact":{"type":["string","integer"],"description":"An id or any contact identifier — `id:42`, `email:…`, `phone:…`.","examples":["phone:+201001234567"]},"channel_id":{"type":"integer","description":"The channel to talk on. Read the ids from any conversation payload."}}}}}}}},"/conversations/{id}":{"get":{"tags":["Conversations"],"operationId":"getConversation","summary":"Get a conversation","description":"One thread with its channel and current assignee.\n\n**Required ability:** `conversations:read`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Conversation"}}},"example":{"data":{"id":1024,"contact_id":42,"channel":{"id":2,"name":"Clinic WhatsApp","type":"whatsapp"},"status":"open","subject":null,"assignee":{"type":"user","id":7},"last_message_at":"2026-07-26T09:41:00+00:00","closed_at":null,"created_at":"2026-07-26T09:12:00+00:00"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/Id"}]},"patch":{"tags":["Conversations"],"operationId":"updateConversation","summary":"Change a conversation","description":"Status, owner, team and priority. Only the keys you send are acted on.\n\nClosing does everything closing does in the panel — cancels the thread's flow enrollments, drops a pending AI follow-up, fires the resolved trigger, may send a CSAT survey and summarises the thread. This is why there is no \"just set the status\" shortcut.\n\n**The assignee may be an AI agent.** `{\"type\": \"ai\", \"id\": 3}` hands the thread to one of your agents, which then answers as a teammate would — and if the customer is already waiting, it replies at once rather than waiting for the next inbound message. `null` unassigns whoever owns it. A team is separate from an assignee: `team_id` routes the thread to a queue, and both can be set at once.\n\n**Required ability:** `conversations:write`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Conversation"}}},"example":{"data":{"id":1024,"contact_id":42,"channel":{"id":2,"name":"Clinic WhatsApp","type":"whatsapp"},"status":"open","subject":null,"assignee":{"type":"user","id":7},"last_message_at":"2026-07-26T09:41:00+00:00","closed_at":null,"created_at":"2026-07-26T09:12:00+00:00"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationFailed"},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":[],"properties":{"status":{"type":"string","enum":["open","pending","snoozed","closed"],"examples":["closed"]},"category":{"type":["string","null"],"description":"Why the thread ended, with `status: closed`. Must be one of `GET /closing-notes` — free text would make the category impossible to count, which is the only reason it is a category rather than part of the summary.","examples":["Resolved"]},"summary":{"type":["string","null"],"description":"The detail a category cannot carry. Free text, up to 2000 characters.","examples":["Refunded order 4821 and confirmed by email."]},"snoozed_until":{"type":["string","null"],"description":"Required with `status: snoozed`, and must be in the future — a snooze already past would be woken by the next sweep, which reads as the call having done nothing.","examples":["2026-08-05T09:00:00+00:00"]},"assignee":{"type":["object","null"],"description":"Who owns the thread, or `null` to unassign.","required":["type","id"],"properties":{"type":{"type":"string","enum":["user","ai"]},"id":{"type":"integer"}}},"team_id":{"type":["integer","null"],"description":"Route to a team, or `null` to clear the routing."},"priority":{"type":"string","enum":["low","normal","high"],"examples":["high"]}}}}}}}},"/closing-notes":{"get":{"tags":["Conversations"],"operationId":"listClosingNotes","summary":"List closing notes","description":"The reasons this workspace closes conversations with, in the order it arranged them. Read this before closing a thread with a `category`.\n\nAn empty list is a normal state — a workspace that has not configured any simply closes threads without a reason.\n\n**Required ability:** `conversations:read`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ClosingNote"}}}},"example":{"data":[{"category":"Resolved","description":"The customer got what they needed"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/conversations/{id}/messages":{"get":{"tags":["Messages"],"operationId":"listMessages","summary":"List messages","description":"A thread's messages, newest first.\n\n**Required ability:** `conversations:read`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Message"}},"meta":{"$ref":"#/components/schemas/CursorMeta"}}},"example":{"data":[{"id":9001,"conversation_id":1024,"type":"message","direction":"inbound","sender_type":"contact","user_id":null,"body":"Do you open on Friday?","content_type":"text","status":null,"created_at":"2026-07-26T09:41:00+00:00"}],"meta":{"next_cursor":null,"per_page":50}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"}]},"post":{"tags":["Messages"],"operationId":"sendMessage","summary":"Send a message","description":"Sends an outbound message into the thread, on whichever channel it runs on,\nattributed to the token's teammate. This is a real send: it takes the same\npath as the inbox composer, so delivery, automation and reporting all behave\nidentically.\n\n## Kinds\n\n`type: \"text\"` — a plain message, up to 4096 characters.\n\n`type: \"attachment\"` — a file, given as a **public URL we fetch** rather than\nan upload, since you already have the file and multipart bodies are awkward\nfrom most automation platforms. Up to 25 MB; a URL we cannot reach, or one\npointing inside a private network, is refused.\n\n`type: \"template\"` — an **approved WhatsApp template** by name, with its\n`parameters` in order. Only approved templates on the thread's own channel\ncan be sent. If the template exists in more than one language you must say\nwhich, since two languages of one template are two different messages.\n\n`type: \"buttons\"` — the `text` as a prompt with up to ten tappable quick\nreplies. A channel that renders buttons natively (Telegram, the web widget)\nshows them as buttons; every other channel shows the text. Tapping one sends\nits **exact label back as a normal inbound message** — which is also why two\nbuttons may not carry the same text. This is the same shape the workflow\nengine's Buttons step sends.\n\n## The 24-hour window\n\nWhatsApp applies Meta's rules: outside the 24-hour customer service window a\nfree-form message is refused by Meta and arrives back as a failed message. A\n**template is the only thing Meta accepts there**, which is why this endpoint\ncan send one.\n\nSending `{\"body\": \"…\"}` with no `type` still works and means a text message.\n\n**Required ability:** `messages:send`","responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Message"}}},"example":{"data":{"id":9001,"conversation_id":1024,"type":"message","direction":"outbound","sender_type":"contact","user_id":null,"body":"Your order is on its way!","content_type":"text","status":null,"created_at":"2026-07-26T09:41:00+00:00"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationFailed"},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text","attachment","template","buttons"],"examples":["text"]},"text":{"type":"string","maxLength":4096,"description":"Required for `text`.","examples":["Your order is on its way!"]},"reply_to":{"type":["string","null"],"maxLength":255,"description":"Required on a public comment thread (`audience` = `public`): the id of the inbound comment you are answering, from the message list. Only `text` can be published on a public thread.","examples":["PAGE-1_777"]},"url":{"type":"string","maxLength":2048,"description":"Required for `attachment`. A public http(s) address we can fetch.","examples":["https://files.example.com/invoice-4821.pdf"]},"caption":{"type":["string","null"],"maxLength":1024,"description":"Optional text alongside an attachment."},"file_name":{"type":["string","null"],"maxLength":255,"description":"What to call the file. Taken from the URL when omitted."},"buttons":{"type":"array","minItems":1,"maxItems":10,"description":"Required for `buttons`. The tappable labels, in order, each unique.","items":{"type":"string","maxLength":100},"examples":[["Sales","Support"]]},"template":{"type":"string","maxLength":255,"description":"Required for `template`. The approved template's name.","examples":["order_shipped"]},"language":{"type":["string","null"],"description":"Required only when the template exists in more than one language.","examples":["ar"]},"parameters":{"type":"array","description":"The template's placeholders, in order.","items":{"type":"string"},"examples":[["4821","tomorrow"]]}}}}}}}},"/conversations/{id}/notes":{"post":{"tags":["Messages"],"operationId":"addConversationNote","summary":"Add an internal note","description":"A note the team can see and the contact never does — what an integration writes when it has something to report (\"refund processed\", \"order shipped\") without messaging the customer.\n\nMentioned teammates are notified, exactly as an @mention typed in the inbox. Every id must be an active teammate here: silently dropping one would leave you believing somebody was told who never was.\n\nAnswers to the conversation ability rather than `messages:send`, because nothing is sent to the customer.\n\n**Required ability:** `conversations:write`","responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Message"}}},"example":{"data":{"id":9001,"conversation_id":1024,"type":"note","direction":null,"sender_type":"contact","user_id":null,"body":"Refunded order 4821.","content_type":"text","status":null,"created_at":"2026-07-26T09:41:00+00:00"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationFailed"},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["body"],"properties":{"body":{"type":"string","maxLength":4096,"examples":["Refunded order 4821."]},"mentions":{"type":"array","description":"Teammate ids to notify.","items":{"type":"integer"}}}}}}}}},"/messages/{id}":{"get":{"tags":["Messages"],"operationId":"getMessage","summary":"Get a message","description":"One message by id — how you resolve the `message.id` a webhook handed you without paging the whole thread.\n\n**Required ability:** `conversations:read`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Message"}}},"example":{"data":{"id":9001,"conversation_id":1024,"type":"message","direction":"inbound","sender_type":"contact","user_id":null,"body":"Do you open on Friday?","content_type":"text","status":null,"created_at":"2026-07-26T09:41:00+00:00"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/Id"}]}},"/deals":{"get":{"tags":["Deals"],"operationId":"listDeals","summary":"List deals","description":"Deals on their pipelines, newest first.\n\n**Required ability:** `deals:read`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Deal"}},"meta":{"$ref":"#/components/schemas/CursorMeta"}}},"example":{"data":[{"id":88,"name":"Clinic fit-out","pipeline":{"id":1,"name":"Sales pipeline"},"stage":{"id":3,"name":"Qualified"},"status":"open","amount":"25000.00","close_date":null,"owner_id":7,"organization_id":null,"source":"website","closed_at":null,"closed_lost_reason":null,"contact_ids":[42],"created_at":"2026-07-26T09:41:00+00:00"}],"meta":{"next_cursor":null,"per_page":50}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"name":"status","in":"query","required":false,"description":"`open` for deals still in play, or a settled status.","schema":{"type":"string","enum":["open","won","lost"]}},{"name":"contact_id","in":"query","required":false,"schema":{"type":"integer"}},{"name":"pipeline_id","in":"query","required":false,"schema":{"type":"integer"}},{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"}]},"post":{"tags":["Deals"],"operationId":"createDeal","summary":"Create a deal","description":"Opens a deal, landing on the given stage or the default pipeline's first stage. A `contact_id` must belong to this workspace, or the call answers `422`.\n\n**Required ability:** `deals:write`","responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Deal"}}},"example":{"data":{"id":88,"name":"Clinic fit-out","pipeline":{"id":1,"name":"Sales pipeline"},"stage":{"id":3,"name":"Qualified"},"status":"open","amount":"25000.00","close_date":null,"owner_id":7,"organization_id":null,"source":"website","closed_at":null,"closed_lost_reason":null,"contact_ids":[42],"created_at":"2026-07-26T09:41:00+00:00"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/ValidationFailed"},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":255,"examples":["Clinic fit-out"]},"contact_id":{"type":["integer","null"],"examples":[42]},"amount":{"type":["number","null"],"minimum":0,"examples":[25000]},"source":{"type":["string","null"],"description":"Where the deal came from. Defaults to `other`.","examples":["website"]},"pipeline_id":{"type":["integer","null"],"description":"Defaults to the workspace's default pipeline."},"stage_id":{"type":["integer","null"],"description":"Defaults to the pipeline's first open stage."},"owner_id":{"type":["integer","null"]},"close_date":{"type":["string","null"],"format":"date"}}}}}}}},"/deals/{id}":{"get":{"tags":["Deals"],"operationId":"getDeal","summary":"Get a deal","description":"One deal by id.\n\n**Required ability:** `deals:read`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Deal"}}},"example":{"data":{"id":88,"name":"Clinic fit-out","pipeline":{"id":1,"name":"Sales pipeline"},"stage":{"id":3,"name":"Qualified"},"status":"open","amount":"25000.00","close_date":null,"owner_id":7,"organization_id":null,"source":"website","closed_at":null,"closed_lost_reason":null,"contact_ids":[42],"created_at":"2026-07-26T09:41:00+00:00"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/Id"}]},"patch":{"tags":["Deals"],"operationId":"updateDeal","summary":"Change a deal","description":"Changes a deal's own facts — only the fields you send are touched.\n\n**The stage is the deal's verdict.** Send `stage_id` to move it along its\npipeline: the Won stage wins it, the Lost stage loses it (send\n`closed_lost_reason` with it to say why), and moving back to an open stage\nreopens it. There is no separate close call.\n\n**Required ability:** `deals:write`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Deal"}}},"example":{"data":{"id":88,"name":"Clinic fit-out","pipeline":{"id":1,"name":"Sales pipeline"},"stage":{"id":3,"name":"Qualified"},"status":"open","amount":"25000.00","close_date":null,"owner_id":7,"organization_id":null,"source":"website","closed_at":null,"closed_lost_reason":null,"contact_ids":[42],"created_at":"2026-07-26T09:41:00+00:00"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationFailed"},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":[],"properties":{"name":{"type":"string","maxLength":255},"amount":{"type":["number","null"],"minimum":0},"source":{"type":"string","examples":["referral"]},"owner_id":{"type":["integer","null"],"description":"A teammate id, or null to unassign."},"close_date":{"type":["string","null"],"format":"date"},"stage_id":{"type":"integer","description":"A stage of the deal's own pipeline."},"closed_lost_reason":{"type":["string","null"],"maxLength":255}}}}}}}},"/orders":{"get":{"tags":["Orders"],"operationId":"listOrders","summary":"List orders","description":"Orders newest first. `status` takes one status or `open` (anything not delivered, cancelled or returned); `since` is an ISO time compared to `updated_at`, for polling.\n\n**Required ability:** `orders:read`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Order"}},"meta":{"$ref":"#/components/schemas/CursorMeta"}}},"example":{"data":[{"id":310,"number":1001,"contact_id":42,"conversation_id":918,"deal_id":null,"owner_id":7,"status":"confirmed","source":"ai","payment":{"method":"cod","status":"unpaid","reference":null,"paid_at":null},"delivery":{"address":"شارع ٩، المعادي","city":"القاهرة","area":"المعادي","fee":"50.00"},"items":[{"id":1,"product_id":5,"product_variant_id":12,"name":"سويتر تريكو — أحمر / L","sku":"SW-R-L","quantity":2,"unit_price":"450.00","line_total":"900.00"}],"subtotal":"900.00","discount":"0.00","total":"950.00","notes":"بعد الساعة ٥","external_id":null,"confirmed_at":"2026-08-29T10:05:00+00:00","delivered_at":null,"cancelled_at":null,"cancel_reason":null,"created_at":"2026-08-29T10:00:00+00:00","updated_at":"2026-08-29T10:05:00+00:00"}],"meta":{"next_cursor":null,"per_page":50}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["open","new","confirmed","preparing","out_for_delivery","delivered","cancelled","returned"]}},{"name":"payment_status","in":"query","required":false,"schema":{"type":"string","enum":["unpaid","paid","refunded"]}},{"name":"contact_id","in":"query","required":false,"schema":{"type":"integer"}},{"name":"since","in":"query","required":false,"schema":{"type":"string","format":"date-time"}},{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"}]},"post":{"tags":["Orders"],"operationId":"createOrder","summary":"Place an order","description":"Places an order in `new`. Each line names a `product_id` or `product_variant_id` from the catalogue (name and price are snapshotted), or is free text with its own `unit_price`. The delivery fee is whatever `delivery_fee` says (0 when omitted). `external_id` makes the call safe to repeat: a second order with the same one answers `422`.\n\n**Required ability:** `orders:write`","responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Order"}}},"example":{"data":{"id":310,"number":1001,"contact_id":42,"conversation_id":918,"deal_id":null,"owner_id":7,"status":"confirmed","source":"ai","payment":{"method":"cod","status":"unpaid","reference":null,"paid_at":null},"delivery":{"address":"شارع ٩، المعادي","city":"القاهرة","area":"المعادي","fee":"50.00"},"items":[{"id":1,"product_id":5,"product_variant_id":12,"name":"سويتر تريكو — أحمر / L","sku":"SW-R-L","quantity":2,"unit_price":"450.00","line_total":"900.00"}],"subtotal":"900.00","discount":"0.00","total":"950.00","notes":"بعد الساعة ٥","external_id":null,"confirmed_at":"2026-08-29T10:05:00+00:00","delivered_at":null,"cancelled_at":null,"cancel_reason":null,"created_at":"2026-08-29T10:00:00+00:00","updated_at":"2026-08-29T10:05:00+00:00"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/ValidationFailed"},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["contact_id","items"],"properties":{"contact_id":{"type":"integer","examples":[42]},"items":{"type":"array","minItems":1,"items":{"type":"object","required":["quantity"],"properties":{"product_id":{"type":["integer","null"]},"product_variant_id":{"type":["integer","null"]},"name":{"type":["string","null"],"description":"For a free-text line."},"quantity":{"type":"integer","minimum":1},"unit_price":{"type":["number","null"],"description":"Overrides the catalogue price; required for a free-text line."}}},"examples":[[{"product_variant_id":12,"quantity":2}]]},"delivery_address":{"type":["string","null"],"examples":["شارع ٩، المعادي"]},"delivery_city":{"type":["string","null"]},"delivery_area":{"type":["string","null"],"examples":["المعادي"]},"delivery_fee":{"type":["number","null"]},"discount":{"type":["number","null"]},"payment_method":{"type":["string","null"],"description":"The key of one of the workspace's active payment methods (Settings → Payment methods). Every workspace starts with cod, bank_transfer and wallet.","examples":["cod"]},"notes":{"type":["string","null"]},"owner_id":{"type":["integer","null"]},"conversation_id":{"type":["integer","null"],"description":"The thread the order came from — where its customer messages go."},"external_id":{"type":["string","null"],"maxLength":191}}}}}}}},"/orders/{id}":{"get":{"tags":["Orders"],"operationId":"getOrder","summary":"Get an order","description":"One order by id, with its lines.\n\n**Required ability:** `orders:read`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Order"}}},"example":{"data":{"id":310,"number":1001,"contact_id":42,"conversation_id":918,"deal_id":null,"owner_id":7,"status":"confirmed","source":"ai","payment":{"method":"cod","status":"unpaid","reference":null,"paid_at":null},"delivery":{"address":"شارع ٩، المعادي","city":"القاهرة","area":"المعادي","fee":"50.00"},"items":[{"id":1,"product_id":5,"product_variant_id":12,"name":"سويتر تريكو — أحمر / L","sku":"SW-R-L","quantity":2,"unit_price":"450.00","line_total":"900.00"}],"subtotal":"900.00","discount":"0.00","total":"950.00","notes":"بعد الساعة ٥","external_id":null,"confirmed_at":"2026-08-29T10:05:00+00:00","delivered_at":null,"cancelled_at":null,"cancel_reason":null,"created_at":"2026-08-29T10:00:00+00:00","updated_at":"2026-08-29T10:05:00+00:00"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/Id"}]},"patch":{"tags":["Orders"],"operationId":"updateOrder","summary":"Change an order","description":"Changes delivery, payment, notes, discount or owner — only the fields you send. The status is not a field: see the status call.\n\n**Required ability:** `orders:write`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Order"}}},"example":{"data":{"id":310,"number":1001,"contact_id":42,"conversation_id":918,"deal_id":null,"owner_id":7,"status":"confirmed","source":"ai","payment":{"method":"cod","status":"unpaid","reference":null,"paid_at":null},"delivery":{"address":"شارع ٩، المعادي","city":"القاهرة","area":"المعادي","fee":"50.00"},"items":[{"id":1,"product_id":5,"product_variant_id":12,"name":"سويتر تريكو — أحمر / L","sku":"SW-R-L","quantity":2,"unit_price":"450.00","line_total":"900.00"}],"subtotal":"900.00","discount":"0.00","total":"950.00","notes":"بعد الساعة ٥","external_id":null,"confirmed_at":"2026-08-29T10:05:00+00:00","delivered_at":null,"cancelled_at":null,"cancel_reason":null,"created_at":"2026-08-29T10:00:00+00:00","updated_at":"2026-08-29T10:05:00+00:00"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationFailed"},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":[],"properties":{"delivery_address":{"type":["string","null"]},"delivery_city":{"type":["string","null"]},"delivery_area":{"type":["string","null"]},"delivery_fee":{"type":["number","null"]},"payment_method":{"type":"string","description":"The key of one of the workspace's active payment methods.","examples":["bank_transfer"]},"payment_status":{"type":"string","enum":["unpaid","paid","refunded"]},"payment_reference":{"type":["string","null"]},"notes":{"type":["string","null"]},"internal_notes":{"type":["string","null"]},"discount":{"type":["number","null"]},"owner_id":{"type":["integer","null"]}}}}}}}},"/orders/{id}/status":{"patch":{"tags":["Orders"],"operationId":"moveOrder","summary":"Move an order","description":"Moves the order to another status — only the steps its current status allows: `new → confirmed → preparing → out_for_delivery → delivered`, `cancelled` from any open status, `returned` after delivery. Confirming reserves stock for every tracked line and answers `422` on a shortage unless `force` is true; cancelling releases it, delivering sells it, a return restocks it.\n\n**Required ability:** `orders:write`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Order"}}},"example":{"data":{"id":310,"number":1001,"contact_id":42,"conversation_id":918,"deal_id":null,"owner_id":7,"status":"confirmed","source":"ai","payment":{"method":"cod","status":"unpaid","reference":null,"paid_at":null},"delivery":{"address":"شارع ٩، المعادي","city":"القاهرة","area":"المعادي","fee":"50.00"},"items":[{"id":1,"product_id":5,"product_variant_id":12,"name":"سويتر تريكو — أحمر / L","sku":"SW-R-L","quantity":2,"unit_price":"450.00","line_total":"900.00"}],"subtotal":"900.00","discount":"0.00","total":"950.00","notes":"بعد الساعة ٥","external_id":null,"confirmed_at":"2026-08-29T10:05:00+00:00","delivered_at":null,"cancelled_at":null,"cancel_reason":null,"created_at":"2026-08-29T10:00:00+00:00","updated_at":"2026-08-29T10:05:00+00:00"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationFailed"},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["new","confirmed","preparing","out_for_delivery","delivered","cancelled","returned"],"examples":["confirmed"]},"reason":{"type":["string","null"],"description":"Why, when cancelling."},"force":{"type":"boolean","description":"Confirm even when stock is short."}}}}}}}},"/products":{"get":{"tags":["Products"],"operationId":"listProducts","summary":"List products","description":"The catalogue with variants, prices and what is available to sell. `available` is `null` for products that do not track stock.\n\n**Required ability:** `products:read`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Product"}},"meta":{"$ref":"#/components/schemas/CursorMeta"}}},"example":{"data":[{"id":5,"name":"سويتر تريكو","description":null,"sku":"SW","price":"450.00","is_active":true,"track_stock":true,"available":7,"image_url":null,"variants":[{"id":12,"name":"أحمر / L","options":{"colour":"أحمر","size":"L"},"sku":"SW-R-L","price":"450.00","available":3,"is_active":true}],"created_at":"2026-08-29T09:00:00+00:00"}],"meta":{"next_cursor":null,"per_page":50}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"name":"active","in":"query","required":false,"schema":{"type":"boolean"}},{"name":"search","in":"query","required":false,"schema":{"type":"string"}},{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"}]}},"/products/{id}":{"get":{"tags":["Products"],"operationId":"getProduct","summary":"Get a product","description":"One product by id, with its variants.\n\n**Required ability:** `products:read`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Product"}}},"example":{"data":{"id":5,"name":"سويتر تريكو","description":null,"sku":"SW","price":"450.00","is_active":true,"track_stock":true,"available":7,"image_url":null,"variants":[{"id":12,"name":"أحمر / L","options":{"colour":"أحمر","size":"L"},"sku":"SW-R-L","price":"450.00","available":3,"is_active":true}],"created_at":"2026-08-29T09:00:00+00:00"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/Id"}]}},"/tasks":{"get":{"tags":["Tasks"],"operationId":"listTasks","summary":"List tasks","description":"Work the team owes somebody, soonest first. Filter by `status`, `assignee_id`, `contact_id` or `due_before`.\n\n**Required ability:** `tasks:read`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Task"}},"meta":{"$ref":"#/components/schemas/CursorMeta"}}},"example":{"data":[{"id":21,"title":"Call Mona back","description":null,"type":"call","status":"open","due_at":"2026-08-06T10:00:00+00:00","completed_at":null,"assignee_id":7,"created_by":7,"contact_id":42}],"meta":{"next_cursor":null,"per_page":50}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["open","done","cancelled"]}},{"name":"assignee_id","in":"query","required":false,"schema":{"type":"integer"}},{"name":"contact_id","in":"query","required":false,"schema":{"type":"integer"}},{"name":"due_before","in":"query","required":false,"schema":{"type":"string"},"description":"Tasks due at or before this moment."},{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"}]},"post":{"tags":["Tasks"],"operationId":"createTask","summary":"Create a task","description":"Adds a task. Give a `contact_id` and it hangs off that person's record and shows on the calendar, which is how \"call Mona back on Thursday\" ends up in front of the right teammate. Unassigned tasks go to the token's own teammate rather than nobody.\n\n**Required ability:** `tasks:write`","responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Task"}}},"example":{"data":{"id":21,"title":"Call Mona back","description":null,"type":"call","status":"open","due_at":"2026-08-06T10:00:00+00:00","completed_at":null,"assignee_id":7,"created_by":7,"contact_id":42}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/ValidationFailed"},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","due_at"],"properties":{"title":{"type":"string","maxLength":255,"examples":["Call Mona back"]},"description":{"type":["string","null"],"maxLength":2000},"type":{"type":"string","enum":["call","follow_up","meeting","reminder","custom"],"default":"custom"},"due_at":{"type":"string","examples":["2026-08-06T10:00:00+00:00"]},"assignee_id":{"type":["integer","null"]},"contact_id":{"type":["integer","null"]}}}}}}}},"/tasks/{id}":{"patch":{"tags":["Tasks"],"operationId":"updateTask","summary":"Change a task","description":"Only the fields you send are touched. Moving `status` runs the same transition the panel does, so completing a task stamps when it was completed rather than leaving a done task that never finished.\n\n**Required ability:** `tasks:write`","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Task"}}},"example":{"data":{"id":21,"title":"Call Mona back","description":null,"type":"call","status":"done","due_at":"2026-08-06T10:00:00+00:00","completed_at":"2026-08-06T11:04:00+00:00","assignee_id":7,"created_by":7,"contact_id":42}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationFailed"},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":[],"properties":{"title":{"type":"string","maxLength":255},"description":{"type":["string","null"],"maxLength":2000},"status":{"type":"string","enum":["open","done","cancelled"]},"due_at":{"type":"string"},"assignee_id":{"type":["integer","null"]}}}}}}}}},"webhooks":{"message.received":{"post":{"operationId":"webhook.message.received","summary":"Message received","description":"A customer's message arrived on a connected channel.","tags":["Webhooks"],"security":[],"parameters":[{"name":"X-Labeeq-Event","in":"header","required":true,"schema":{"type":"string","const":"message.received"}},{"name":"X-Labeeq-Delivery","in":"header","required":true,"description":"The delivery id. A retry repeats it, so use it to ignore duplicates.","schema":{"type":"string"}},{"name":"X-Labeeq-Signature","in":"header","required":true,"description":"`sha256=` followed by the HMAC-SHA256 of the raw request body, keyed with the endpoint's signing secret.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","company_id","occurred_at","data"],"properties":{"event":{"type":"string","const":"message.received"},"company_id":{"type":"integer","description":"The workspace the event happened in."},"occurred_at":{"type":"string","format":"date-time"},"data":{"type":"object","required":["message","conversation_id","contact_id"],"properties":{"message":{"$ref":"#/components/schemas/Message"},"conversation_id":{"type":"integer"},"contact_id":{"type":"integer"}}}}}}}},"responses":{"2XX":{"description":"Received. Any other answer, or no answer within 10 seconds, is retried after 1, 5 and 30 minutes; ten failed deliveries in a row disable the endpoint and email the account owners."}}}},"message.sent":{"post":{"operationId":"webhook.message.sent","summary":"Message sent","description":"A message went out to the customer, from a teammate, an AI agent or an automation.","tags":["Webhooks"],"security":[],"parameters":[{"name":"X-Labeeq-Event","in":"header","required":true,"schema":{"type":"string","const":"message.sent"}},{"name":"X-Labeeq-Delivery","in":"header","required":true,"description":"The delivery id. A retry repeats it, so use it to ignore duplicates.","schema":{"type":"string"}},{"name":"X-Labeeq-Signature","in":"header","required":true,"description":"`sha256=` followed by the HMAC-SHA256 of the raw request body, keyed with the endpoint's signing secret.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","company_id","occurred_at","data"],"properties":{"event":{"type":"string","const":"message.sent"},"company_id":{"type":"integer","description":"The workspace the event happened in."},"occurred_at":{"type":"string","format":"date-time"},"data":{"type":"object","required":["message","conversation_id","contact_id"],"properties":{"message":{"$ref":"#/components/schemas/Message"},"conversation_id":{"type":"integer"},"contact_id":{"type":"integer"}}}}}}}},"responses":{"2XX":{"description":"Received. Any other answer, or no answer within 10 seconds, is retried after 1, 5 and 30 minutes; ten failed deliveries in a row disable the endpoint and email the account owners."}}}},"conversation.created":{"post":{"operationId":"webhook.conversation.created","summary":"Conversation started","description":"A new conversation started with a contact on a channel.","tags":["Webhooks"],"security":[],"parameters":[{"name":"X-Labeeq-Event","in":"header","required":true,"schema":{"type":"string","const":"conversation.created"}},{"name":"X-Labeeq-Delivery","in":"header","required":true,"description":"The delivery id. A retry repeats it, so use it to ignore duplicates.","schema":{"type":"string"}},{"name":"X-Labeeq-Signature","in":"header","required":true,"description":"`sha256=` followed by the HMAC-SHA256 of the raw request body, keyed with the endpoint's signing secret.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","company_id","occurred_at","data"],"properties":{"event":{"type":"string","const":"conversation.created"},"company_id":{"type":"integer","description":"The workspace the event happened in."},"occurred_at":{"type":"string","format":"date-time"},"data":{"type":"object","required":["conversation"],"properties":{"conversation":{"$ref":"#/components/schemas/Conversation"}}}}}}}},"responses":{"2XX":{"description":"Received. Any other answer, or no answer within 10 seconds, is retried after 1, 5 and 30 minutes; ten failed deliveries in a row disable the endpoint and email the account owners."}}}},"conversation.opened":{"post":{"operationId":"webhook.conversation.opened","summary":"Conversation reopened","description":"A closed conversation was reopened.","tags":["Webhooks"],"security":[],"parameters":[{"name":"X-Labeeq-Event","in":"header","required":true,"schema":{"type":"string","const":"conversation.opened"}},{"name":"X-Labeeq-Delivery","in":"header","required":true,"description":"The delivery id. A retry repeats it, so use it to ignore duplicates.","schema":{"type":"string"}},{"name":"X-Labeeq-Signature","in":"header","required":true,"description":"`sha256=` followed by the HMAC-SHA256 of the raw request body, keyed with the endpoint's signing secret.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","company_id","occurred_at","data"],"properties":{"event":{"type":"string","const":"conversation.opened"},"company_id":{"type":"integer","description":"The workspace the event happened in."},"occurred_at":{"type":"string","format":"date-time"},"data":{"type":"object","required":["conversation"],"properties":{"conversation":{"$ref":"#/components/schemas/Conversation"}}}}}}}},"responses":{"2XX":{"description":"Received. Any other answer, or no answer within 10 seconds, is retried after 1, 5 and 30 minutes; ten failed deliveries in a row disable the endpoint and email the account owners."}}}},"conversation.closed":{"post":{"operationId":"webhook.conversation.closed","summary":"Conversation closed","description":"A conversation was closed.","tags":["Webhooks"],"security":[],"parameters":[{"name":"X-Labeeq-Event","in":"header","required":true,"schema":{"type":"string","const":"conversation.closed"}},{"name":"X-Labeeq-Delivery","in":"header","required":true,"description":"The delivery id. A retry repeats it, so use it to ignore duplicates.","schema":{"type":"string"}},{"name":"X-Labeeq-Signature","in":"header","required":true,"description":"`sha256=` followed by the HMAC-SHA256 of the raw request body, keyed with the endpoint's signing secret.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","company_id","occurred_at","data"],"properties":{"event":{"type":"string","const":"conversation.closed"},"company_id":{"type":"integer","description":"The workspace the event happened in."},"occurred_at":{"type":"string","format":"date-time"},"data":{"type":"object","required":["conversation"],"properties":{"conversation":{"$ref":"#/components/schemas/Conversation"}}}}}}}},"responses":{"2XX":{"description":"Received. Any other answer, or no answer within 10 seconds, is retried after 1, 5 and 30 minutes; ten failed deliveries in a row disable the endpoint and email the account owners."}}}},"conversation.assigned":{"post":{"operationId":"webhook.conversation.assigned","summary":"Conversation assigned","description":"A conversation was assigned or handed to a new assignee. Unassigning sends nothing.","tags":["Webhooks"],"security":[],"parameters":[{"name":"X-Labeeq-Event","in":"header","required":true,"schema":{"type":"string","const":"conversation.assigned"}},{"name":"X-Labeeq-Delivery","in":"header","required":true,"description":"The delivery id. A retry repeats it, so use it to ignore duplicates.","schema":{"type":"string"}},{"name":"X-Labeeq-Signature","in":"header","required":true,"description":"`sha256=` followed by the HMAC-SHA256 of the raw request body, keyed with the endpoint's signing secret.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","company_id","occurred_at","data"],"properties":{"event":{"type":"string","const":"conversation.assigned"},"company_id":{"type":"integer","description":"The workspace the event happened in."},"occurred_at":{"type":"string","format":"date-time"},"data":{"type":"object","required":["conversation"],"properties":{"conversation":{"$ref":"#/components/schemas/Conversation"}}}}}}}},"responses":{"2XX":{"description":"Received. Any other answer, or no answer within 10 seconds, is retried after 1, 5 and 30 minutes; ten failed deliveries in a row disable the endpoint and email the account owners."}}}},"conversation.priority_changed":{"post":{"operationId":"webhook.conversation.priority_changed","summary":"Conversation priority changed","description":"A conversation's priority changed.","tags":["Webhooks"],"security":[],"parameters":[{"name":"X-Labeeq-Event","in":"header","required":true,"schema":{"type":"string","const":"conversation.priority_changed"}},{"name":"X-Labeeq-Delivery","in":"header","required":true,"description":"The delivery id. A retry repeats it, so use it to ignore duplicates.","schema":{"type":"string"}},{"name":"X-Labeeq-Signature","in":"header","required":true,"description":"`sha256=` followed by the HMAC-SHA256 of the raw request body, keyed with the endpoint's signing secret.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","company_id","occurred_at","data"],"properties":{"event":{"type":"string","const":"conversation.priority_changed"},"company_id":{"type":"integer","description":"The workspace the event happened in."},"occurred_at":{"type":"string","format":"date-time"},"data":{"type":"object","required":["conversation"],"properties":{"conversation":{"$ref":"#/components/schemas/Conversation"}}}}}}}},"responses":{"2XX":{"description":"Received. Any other answer, or no answer within 10 seconds, is retried after 1, 5 and 30 minutes; ten failed deliveries in a row disable the endpoint and email the account owners."}}}},"contact.created":{"post":{"operationId":"webhook.contact.created","summary":"Contact created","description":"A contact was created.","tags":["Webhooks"],"security":[],"parameters":[{"name":"X-Labeeq-Event","in":"header","required":true,"schema":{"type":"string","const":"contact.created"}},{"name":"X-Labeeq-Delivery","in":"header","required":true,"description":"The delivery id. A retry repeats it, so use it to ignore duplicates.","schema":{"type":"string"}},{"name":"X-Labeeq-Signature","in":"header","required":true,"description":"`sha256=` followed by the HMAC-SHA256 of the raw request body, keyed with the endpoint's signing secret.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","company_id","occurred_at","data"],"properties":{"event":{"type":"string","const":"contact.created"},"company_id":{"type":"integer","description":"The workspace the event happened in."},"occurred_at":{"type":"string","format":"date-time"},"data":{"type":"object","required":["contact"],"properties":{"contact":{"$ref":"#/components/schemas/Contact"}}}}}}}},"responses":{"2XX":{"description":"Received. Any other answer, or no answer within 10 seconds, is retried after 1, 5 and 30 minutes; ten failed deliveries in a row disable the endpoint and email the account owners."}}}},"contact.updated":{"post":{"operationId":"webhook.contact.updated","summary":"Contact details changed","description":"A contact's name, email, phone, avatar or language changed. Bookkeeping such as last-seen times sends nothing.","tags":["Webhooks"],"security":[],"parameters":[{"name":"X-Labeeq-Event","in":"header","required":true,"schema":{"type":"string","const":"contact.updated"}},{"name":"X-Labeeq-Delivery","in":"header","required":true,"description":"The delivery id. A retry repeats it, so use it to ignore duplicates.","schema":{"type":"string"}},{"name":"X-Labeeq-Signature","in":"header","required":true,"description":"`sha256=` followed by the HMAC-SHA256 of the raw request body, keyed with the endpoint's signing secret.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","company_id","occurred_at","data"],"properties":{"event":{"type":"string","const":"contact.updated"},"company_id":{"type":"integer","description":"The workspace the event happened in."},"occurred_at":{"type":"string","format":"date-time"},"data":{"type":"object","required":["contact"],"properties":{"contact":{"$ref":"#/components/schemas/Contact"}}}}}}}},"responses":{"2XX":{"description":"Received. Any other answer, or no answer within 10 seconds, is retried after 1, 5 and 30 minutes; ten failed deliveries in a row disable the endpoint and email the account owners."}}}},"note.created":{"post":{"operationId":"webhook.note.created","summary":"Internal note added","description":"A teammate added an internal note to a conversation. The customer never sees notes.","tags":["Webhooks"],"security":[],"parameters":[{"name":"X-Labeeq-Event","in":"header","required":true,"schema":{"type":"string","const":"note.created"}},{"name":"X-Labeeq-Delivery","in":"header","required":true,"description":"The delivery id. A retry repeats it, so use it to ignore duplicates.","schema":{"type":"string"}},{"name":"X-Labeeq-Signature","in":"header","required":true,"description":"`sha256=` followed by the HMAC-SHA256 of the raw request body, keyed with the endpoint's signing secret.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","company_id","occurred_at","data"],"properties":{"event":{"type":"string","const":"note.created"},"company_id":{"type":"integer","description":"The workspace the event happened in."},"occurred_at":{"type":"string","format":"date-time"},"data":{"type":"object","required":["message","conversation_id","contact_id"],"properties":{"message":{"$ref":"#/components/schemas/Message"},"conversation_id":{"type":"integer"},"contact_id":{"type":"integer"}}}}}}}},"responses":{"2XX":{"description":"Received. Any other answer, or no answer within 10 seconds, is retried after 1, 5 and 30 minutes; ten failed deliveries in a row disable the endpoint and email the account owners."}}}},"contact.lifecycle_changed":{"post":{"operationId":"webhook.contact.lifecycle_changed","summary":"Contact lifecycle changed","description":"A contact moved to another lifecycle stage.","tags":["Webhooks"],"security":[],"parameters":[{"name":"X-Labeeq-Event","in":"header","required":true,"schema":{"type":"string","const":"contact.lifecycle_changed"}},{"name":"X-Labeeq-Delivery","in":"header","required":true,"description":"The delivery id. A retry repeats it, so use it to ignore duplicates.","schema":{"type":"string"}},{"name":"X-Labeeq-Signature","in":"header","required":true,"description":"`sha256=` followed by the HMAC-SHA256 of the raw request body, keyed with the endpoint's signing secret.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","company_id","occurred_at","data"],"properties":{"event":{"type":"string","const":"contact.lifecycle_changed"},"company_id":{"type":"integer","description":"The workspace the event happened in."},"occurred_at":{"type":"string","format":"date-time"},"data":{"type":"object","required":["contact"],"properties":{"contact":{"$ref":"#/components/schemas/Contact"}}}}}}}},"responses":{"2XX":{"description":"Received. Any other answer, or no answer within 10 seconds, is retried after 1, 5 and 30 minutes; ten failed deliveries in a row disable the endpoint and email the account owners."}}}},"contact.label_changed":{"post":{"operationId":"webhook.contact.label_changed","summary":"Contact labels changed","description":"Labels were added to or removed from a contact. The payload lists the current labels and what was added and removed.","tags":["Webhooks"],"security":[],"parameters":[{"name":"X-Labeeq-Event","in":"header","required":true,"schema":{"type":"string","const":"contact.label_changed"}},{"name":"X-Labeeq-Delivery","in":"header","required":true,"description":"The delivery id. A retry repeats it, so use it to ignore duplicates.","schema":{"type":"string"}},{"name":"X-Labeeq-Signature","in":"header","required":true,"description":"`sha256=` followed by the HMAC-SHA256 of the raw request body, keyed with the endpoint's signing secret.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","company_id","occurred_at","data"],"properties":{"event":{"type":"string","const":"contact.label_changed"},"company_id":{"type":"integer","description":"The workspace the event happened in."},"occurred_at":{"type":"string","format":"date-time"},"data":{"type":"object","required":["contact","labels","added","removed"],"properties":{"contact":{"$ref":"#/components/schemas/Contact"},"labels":{"type":"array","items":{"type":"string"}},"added":{"type":"array","items":{"type":"string"}},"removed":{"type":"array","items":{"type":"string"}}}}}}}}},"responses":{"2XX":{"description":"Received. Any other answer, or no answer within 10 seconds, is retried after 1, 5 and 30 minutes; ten failed deliveries in a row disable the endpoint and email the account owners."}}}},"deal.created":{"post":{"operationId":"webhook.deal.created","summary":"Deal created","description":"A deal was opened on a pipeline.","tags":["Webhooks"],"security":[],"parameters":[{"name":"X-Labeeq-Event","in":"header","required":true,"schema":{"type":"string","const":"deal.created"}},{"name":"X-Labeeq-Delivery","in":"header","required":true,"description":"The delivery id. A retry repeats it, so use it to ignore duplicates.","schema":{"type":"string"}},{"name":"X-Labeeq-Signature","in":"header","required":true,"description":"`sha256=` followed by the HMAC-SHA256 of the raw request body, keyed with the endpoint's signing secret.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","company_id","occurred_at","data"],"properties":{"event":{"type":"string","const":"deal.created"},"company_id":{"type":"integer","description":"The workspace the event happened in."},"occurred_at":{"type":"string","format":"date-time"},"data":{"type":"object","required":["deal"],"properties":{"deal":{"$ref":"#/components/schemas/Deal"}}}}}}}},"responses":{"2XX":{"description":"Received. Any other answer, or no answer within 10 seconds, is retried after 1, 5 and 30 minutes; ten failed deliveries in a row disable the endpoint and email the account owners."}}}},"deal.stage_changed":{"post":{"operationId":"webhook.deal.stage_changed","summary":"Deal stage changed","description":"A deal moved to another stage, on every move and not only the final one.","tags":["Webhooks"],"security":[],"parameters":[{"name":"X-Labeeq-Event","in":"header","required":true,"schema":{"type":"string","const":"deal.stage_changed"}},{"name":"X-Labeeq-Delivery","in":"header","required":true,"description":"The delivery id. A retry repeats it, so use it to ignore duplicates.","schema":{"type":"string"}},{"name":"X-Labeeq-Signature","in":"header","required":true,"description":"`sha256=` followed by the HMAC-SHA256 of the raw request body, keyed with the endpoint's signing secret.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","company_id","occurred_at","data"],"properties":{"event":{"type":"string","const":"deal.stage_changed"},"company_id":{"type":"integer","description":"The workspace the event happened in."},"occurred_at":{"type":"string","format":"date-time"},"data":{"type":"object","required":["deal"],"properties":{"deal":{"$ref":"#/components/schemas/Deal"}}}}}}}},"responses":{"2XX":{"description":"Received. Any other answer, or no answer within 10 seconds, is retried after 1, 5 and 30 minutes; ten failed deliveries in a row disable the endpoint and email the account owners."}}}},"deal.won":{"post":{"operationId":"webhook.deal.won","summary":"Deal won","description":"A deal closed as won.","tags":["Webhooks"],"security":[],"parameters":[{"name":"X-Labeeq-Event","in":"header","required":true,"schema":{"type":"string","const":"deal.won"}},{"name":"X-Labeeq-Delivery","in":"header","required":true,"description":"The delivery id. A retry repeats it, so use it to ignore duplicates.","schema":{"type":"string"}},{"name":"X-Labeeq-Signature","in":"header","required":true,"description":"`sha256=` followed by the HMAC-SHA256 of the raw request body, keyed with the endpoint's signing secret.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","company_id","occurred_at","data"],"properties":{"event":{"type":"string","const":"deal.won"},"company_id":{"type":"integer","description":"The workspace the event happened in."},"occurred_at":{"type":"string","format":"date-time"},"data":{"type":"object","required":["deal"],"properties":{"deal":{"$ref":"#/components/schemas/Deal"}}}}}}}},"responses":{"2XX":{"description":"Received. Any other answer, or no answer within 10 seconds, is retried after 1, 5 and 30 minutes; ten failed deliveries in a row disable the endpoint and email the account owners."}}}},"deal.lost":{"post":{"operationId":"webhook.deal.lost","summary":"Deal lost","description":"A deal closed as lost.","tags":["Webhooks"],"security":[],"parameters":[{"name":"X-Labeeq-Event","in":"header","required":true,"schema":{"type":"string","const":"deal.lost"}},{"name":"X-Labeeq-Delivery","in":"header","required":true,"description":"The delivery id. A retry repeats it, so use it to ignore duplicates.","schema":{"type":"string"}},{"name":"X-Labeeq-Signature","in":"header","required":true,"description":"`sha256=` followed by the HMAC-SHA256 of the raw request body, keyed with the endpoint's signing secret.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","company_id","occurred_at","data"],"properties":{"event":{"type":"string","const":"deal.lost"},"company_id":{"type":"integer","description":"The workspace the event happened in."},"occurred_at":{"type":"string","format":"date-time"},"data":{"type":"object","required":["deal"],"properties":{"deal":{"$ref":"#/components/schemas/Deal"}}}}}}}},"responses":{"2XX":{"description":"Received. Any other answer, or no answer within 10 seconds, is retried after 1, 5 and 30 minutes; ten failed deliveries in a row disable the endpoint and email the account owners."}}}},"order.created":{"post":{"operationId":"webhook.order.created","summary":"Order placed","description":"An order was placed.","tags":["Webhooks"],"security":[],"parameters":[{"name":"X-Labeeq-Event","in":"header","required":true,"schema":{"type":"string","const":"order.created"}},{"name":"X-Labeeq-Delivery","in":"header","required":true,"description":"The delivery id. A retry repeats it, so use it to ignore duplicates.","schema":{"type":"string"}},{"name":"X-Labeeq-Signature","in":"header","required":true,"description":"`sha256=` followed by the HMAC-SHA256 of the raw request body, keyed with the endpoint's signing secret.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","company_id","occurred_at","data"],"properties":{"event":{"type":"string","const":"order.created"},"company_id":{"type":"integer","description":"The workspace the event happened in."},"occurred_at":{"type":"string","format":"date-time"},"data":{"type":"object","required":["order"],"properties":{"order":{"$ref":"#/components/schemas/Order"}}}}}}}},"responses":{"2XX":{"description":"Received. Any other answer, or no answer within 10 seconds, is retried after 1, 5 and 30 minutes; ten failed deliveries in a row disable the endpoint and email the account owners."}}}},"order.status_changed":{"post":{"operationId":"webhook.order.status_changed","summary":"Order status changed","description":"An order moved to another status, on every move.","tags":["Webhooks"],"security":[],"parameters":[{"name":"X-Labeeq-Event","in":"header","required":true,"schema":{"type":"string","const":"order.status_changed"}},{"name":"X-Labeeq-Delivery","in":"header","required":true,"description":"The delivery id. A retry repeats it, so use it to ignore duplicates.","schema":{"type":"string"}},{"name":"X-Labeeq-Signature","in":"header","required":true,"description":"`sha256=` followed by the HMAC-SHA256 of the raw request body, keyed with the endpoint's signing secret.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","company_id","occurred_at","data"],"properties":{"event":{"type":"string","const":"order.status_changed"},"company_id":{"type":"integer","description":"The workspace the event happened in."},"occurred_at":{"type":"string","format":"date-time"},"data":{"type":"object","required":["order"],"properties":{"order":{"$ref":"#/components/schemas/Order"}}}}}}}},"responses":{"2XX":{"description":"Received. Any other answer, or no answer within 10 seconds, is retried after 1, 5 and 30 minutes; ten failed deliveries in a row disable the endpoint and email the account owners."}}}},"order.paid":{"post":{"operationId":"webhook.order.paid","summary":"Order paid","description":"An order was marked paid. Sent once per order.","tags":["Webhooks"],"security":[],"parameters":[{"name":"X-Labeeq-Event","in":"header","required":true,"schema":{"type":"string","const":"order.paid"}},{"name":"X-Labeeq-Delivery","in":"header","required":true,"description":"The delivery id. A retry repeats it, so use it to ignore duplicates.","schema":{"type":"string"}},{"name":"X-Labeeq-Signature","in":"header","required":true,"description":"`sha256=` followed by the HMAC-SHA256 of the raw request body, keyed with the endpoint's signing secret.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","company_id","occurred_at","data"],"properties":{"event":{"type":"string","const":"order.paid"},"company_id":{"type":"integer","description":"The workspace the event happened in."},"occurred_at":{"type":"string","format":"date-time"},"data":{"type":"object","required":["order"],"properties":{"order":{"$ref":"#/components/schemas/Order"}}}}}}}},"responses":{"2XX":{"description":"Received. Any other answer, or no answer within 10 seconds, is retried after 1, 5 and 30 minutes; ten failed deliveries in a row disable the endpoint and email the account owners."}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"A personal access token from Settings → Integrations → Developer API."}},"schemas":{"Me":{"type":"object","properties":{"user":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"email":{"type":"string","format":"email"}}},"company":{"type":"object","description":"The workspace the token acts in.","properties":{"id":{"type":["integer","null"]},"name":{"type":["string","null"]}}},"account":{"type":"object","description":"The account that owns the workspace.","properties":{"id":{"type":["integer","null"]},"name":{"type":["string","null"]}}},"abilities":{"type":"array","items":{"type":"string","enum":["contacts:read","contacts:write","contacts:delete","conversations:read","conversations:write","messages:send","deals:read","deals:write","orders:read","orders:write","products:read","tasks:read","tasks:write"]}}}},"Contact":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":["string","null"]},"phone":{"type":["string","null"],"description":"Masked (`+201•••••••67`) when the token's teammate lacks the view-phone permission."},"email":{"type":["string","null"],"format":"email"},"lifecycle_stage_id":{"type":["integer","null"]},"blocked":{"type":"boolean","description":"Blocked contacts stop reaching your inbox."},"created_at":{"type":["string","null"],"format":"date-time","examples":["2026-07-26T09:41:00+00:00"]},"updated_at":{"type":["string","null"],"format":"date-time","examples":["2026-07-26T09:41:00+00:00"]},"labels":{"type":"array","description":"Only when asked for with `include=labels`.","items":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"}}}},"lifecycle":{"type":["object","null"],"description":"Only when asked for with `include=lifecycle`.","properties":{"id":{"type":"integer"},"name":{"type":"string"},"role":{"type":["string","null"],"examples":["default"]}}}}},"SegmentRule":{"type":"object","required":["attribute","operator"],"properties":{"attribute":{"type":"string","examples":["label"]},"operator":{"type":"string","examples":["has"]},"value":{"type":["string","integer","null"],"description":"Required unless the operator takes none. A label or lifecycle rule takes the id.","examples":[5]}}},"ClosingNote":{"type":"object","properties":{"category":{"type":"string","examples":["Resolved"]},"description":{"type":["string","null"],"examples":["The customer got what they needed"]}}},"Task":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"description":{"type":["string","null"]},"type":{"type":"string","examples":["call"]},"status":{"type":"string","examples":["open"]},"due_at":{"type":["string","null"],"format":"date-time"},"completed_at":{"type":["string","null"],"format":"date-time"},"assignee_id":{"type":["integer","null"]},"created_by":{"type":["integer","null"]},"contact_id":{"type":["integer","null"]}}},"LabelRef":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string","examples":["VIP"]}}},"StageRef":{"type":["object","null"],"properties":{"id":{"type":"integer"},"name":{"type":"string","examples":["Customer"]},"role":{"type":["string","null"],"description":"Set on the stages that carry a role: `default` where contacts start, `won`, `lost`. Usually `null`, since the winning stage is normally derived rather than designated.","examples":[null]},"outcome":{"type":["string","null"],"description":"What landing here does to the contact's deals: `won`, `lost`, or `null` for a stage still in play. Read this rather than `role` — the winning stage is usually the last one before the Lost group rather than one carrying a role.","examples":["won"]}}},"ContactLifecycle":{"type":"object","properties":{"contact_id":{"type":"integer"},"previous":{"allOf":[{"$ref":"#/components/schemas/StageRef"}],"description":"The stage they were on before, or `null` when they were already on the one you asked for."},"lifecycle":{"$ref":"#/components/schemas/StageRef"}}},"Conversation":{"type":"object","properties":{"id":{"type":"integer"},"contact_id":{"type":"integer"},"channel":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"type":{"type":"string","examples":["whatsapp"]}}},"status":{"type":"string","enum":["open","pending","snoozed","closed"]},"audience":{"type":"string","enum":["public","private"],"description":"`public` is a thread of comments under a page post — replies are published on the post for everyone. `private` is a direct conversation."},"reply_target":{"type":["object","null"],"description":"On a `public` thread, the newest comment a reply would answer — pass its `comment_id` as `reply_to` when sending. `null` on a `private` thread.","properties":{"comment_id":{"type":"string"},"body":{"type":["string","null"]}}},"snoozed_until":{"type":["string","null"],"format":"date-time","examples":["2026-07-26T09:41:00+00:00"],"description":"When a snoozed thread wakes up again. `null` unless `status` is `snoozed`."},"subject":{"type":["string","null"]},"assignee":{"type":["object","null"],"description":"Who holds the thread — a teammate or an AI agent. `null` when unassigned.","properties":{"type":{"type":"string","enum":["user","ai"]},"id":{"type":"integer"}}},"last_message_at":{"type":["string","null"],"format":"date-time","examples":["2026-07-26T09:41:00+00:00"]},"closing_category":{"type":["string","null"],"description":"Why it was closed, from the workspace's closing notes. Cleared when a thread is reopened."},"closing_summary":{"type":["string","null"]},"closed_at":{"type":["string","null"],"format":"date-time","examples":["2026-07-26T09:41:00+00:00"]},"created_at":{"type":["string","null"],"format":"date-time","examples":["2026-07-26T09:41:00+00:00"]}}},"Message":{"type":"object","properties":{"id":{"type":"integer"},"conversation_id":{"type":"integer"},"type":{"type":"string","enum":["message","note","event"]},"direction":{"type":["string","null"],"enum":["inbound","outbound",null]},"sender_type":{"type":"string","enum":["contact","user","system"]},"user_id":{"type":["integer","null"],"description":"The teammate who sent it, when a human did."},"body":{"type":["string","null"]},"content_type":{"type":"string","examples":["text"]},"status":{"type":["string","null"],"description":"Delivery state of an outbound message.","examples":["sent"]},"created_at":{"type":["string","null"],"format":"date-time","examples":["2026-07-26T09:41:00+00:00"]}}},"Deal":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"pipeline":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":["string","null"]}}},"stage":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":["string","null"]}}},"status":{"type":"string","enum":["open","won","lost"]},"amount":{"type":["string","null"],"description":"Deal value, as a decimal string.","examples":["25000.00"]},"currency":{"type":"string","description":"ISO 4217 code the amount is in, fixed when the deal was created.","examples":["EGP"]},"close_date":{"type":["string","null"],"format":"date"},"owner_id":{"type":["integer","null"]},"organization_id":{"type":["integer","null"]},"source":{"type":"string","examples":["website"]},"closed_at":{"type":["string","null"],"format":"date-time","examples":["2026-07-26T09:41:00+00:00"]},"closed_lost_reason":{"type":["string","null"]},"contact_ids":{"type":"array","items":{"type":"integer"}},"created_at":{"type":["string","null"],"format":"date-time","examples":["2026-07-26T09:41:00+00:00"]}}},"OrderItem":{"type":"object","properties":{"id":{"type":"integer"},"product_id":{"type":["integer","null"]},"product_variant_id":{"type":["integer","null"]},"name":{"type":"string","description":"As it was when the order was placed."},"sku":{"type":["string","null"]},"quantity":{"type":"integer"},"unit_price":{"type":"string","examples":["450.00"]},"line_total":{"type":"string","examples":["900.00"]}}},"Order":{"type":"object","properties":{"id":{"type":"integer"},"number":{"type":"integer","description":"The human number, per workspace, from 1001."},"contact_id":{"type":"integer"},"conversation_id":{"type":["integer","null"]},"deal_id":{"type":["integer","null"]},"owner_id":{"type":["integer","null"]},"status":{"type":"string","enum":["new","confirmed","preparing","out_for_delivery","delivered","cancelled","returned"]},"source":{"type":"string","enum":["manual","ai","flow","api","whatsapp_cart"]},"currency":{"type":"string","description":"ISO 4217 code every amount is in, fixed when the order was placed.","examples":["EGP"]},"payment":{"type":"object","properties":{"method":{"type":"string","enum":["cod","bank_transfer","wallet"]},"status":{"type":"string","enum":["unpaid","paid","refunded"]},"reference":{"type":["string","null"]},"paid_at":{"type":["string","null"],"format":"date-time"}}},"delivery":{"type":"object","properties":{"address":{"type":["string","null"]},"city":{"type":["string","null"]},"area":{"type":["string","null"]},"fee":{"type":"string","examples":["50.00"]}}},"items":{"type":"array","items":{"$ref":"#/components/schemas/OrderItem"}},"subtotal":{"type":"string"},"discount":{"type":"string"},"total":{"type":"string","examples":["950.00"]},"notes":{"type":["string","null"]},"external_id":{"type":["string","null"]},"confirmed_at":{"type":["string","null"],"format":"date-time"},"delivered_at":{"type":["string","null"],"format":"date-time"},"cancelled_at":{"type":["string","null"],"format":"date-time"},"cancel_reason":{"type":["string","null"]},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}},"ProductVariant":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string","examples":["أحمر / L"]},"options":{"type":["object","null"],"additionalProperties":{"type":"string"}},"sku":{"type":["string","null"]},"price":{"type":"string","description":"The variant's own price, or the product's."},"available":{"type":["integer","null"],"description":"What can still be sold; `null` when stock is not tracked."},"is_active":{"type":"boolean"}}},"Product":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":["string","null"]},"sku":{"type":["string","null"]},"price":{"type":"string","examples":["450.00"]},"currency":{"type":"string","description":"ISO 4217 code the price is in.","examples":["EGP"]},"is_active":{"type":"boolean"},"track_stock":{"type":"boolean"},"available":{"type":["integer","null"]},"image_url":{"type":["string","null"]},"variants":{"type":"array","items":{"$ref":"#/components/schemas/ProductVariant"}},"created_at":{"type":["string","null"],"format":"date-time"}}},"CursorMeta":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Pass back as `?cursor=`. `null` on the last page."},"per_page":{"type":"integer"}}},"Error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string","description":"Present only when the failure has a name a program should branch on. Today: `token_expired`."}}},"ValidationError":{"type":"object","properties":{"message":{"type":"string"},"errors":{"type":"object","description":"Field name to the list of things wrong with it.","additionalProperties":{"type":"array","items":{"type":"string"}}}}}},"parameters":{"Id":{"name":"id","in":"path","required":true,"schema":{"type":"integer"},"examples":{"default":{"value":42}}},"ContactId":{"name":"id","in":"path","required":true,"description":"A contact's id, or something you already hold in its place: `42`, `id:42`, `email:mona@example.com`, `phone:+201001234567`. A phone number is matched on its last nine digits, so the local and international spellings of one number both find it. Looking a contact up by phone needs the view-phone permission.","schema":{"type":"string"},"examples":{"default":{"value":"phone:+201001234567"}}},"Include":{"name":"include","in":"query","required":false,"description":"Extra blocks to return with each contact, comma-separated: `labels`, `lifecycle`. Unknown names are ignored.","schema":{"type":"string"},"examples":{"default":{"value":"labels,lifecycle"}}},"Search":{"name":"search","in":"query","required":false,"description":"Matches name and email (and phone, when the token's teammate may see phone numbers).","schema":{"type":"string"}},"Email":{"name":"email","in":"query","required":false,"description":"An exact email address, compared case-insensitively.","schema":{"type":"string","format":"email"}},"Phone":{"name":"phone","in":"query","required":false,"description":"A phone number in any spelling — matched on its last nine digits. Answers `403` without the view-phone permission.","schema":{"type":"string"},"examples":{"default":{"value":"+201001234567"}}},"Label":{"name":"label","in":"query","required":false,"description":"A label id, or its exact name.","schema":{"type":"string"},"examples":{"default":{"value":"VIP"}}},"LifecycleStageId":{"name":"lifecycle_stage_id","in":"query","required":false,"description":"Contacts sitting on one stage of the lifecycle.","schema":{"type":"integer"}},"CreatedAfter":{"name":"created_after","in":"query","required":false,"description":"Contacts added at or after this moment. Any ISO-8601 date or timestamp.","schema":{"type":"string"},"examples":{"default":{"value":"2026-08-01"}}},"CreatedBefore":{"name":"created_before","in":"query","required":false,"description":"Contacts added at or before this moment.","schema":{"type":"string"}},"UpdatedAfter":{"name":"updated_after","in":"query","required":false,"description":"Contacts changed at or after this moment — the parameter a sync job polls with.","schema":{"type":"string"}},"PerPage":{"name":"per_page","in":"query","required":false,"description":"Rows per page. Defaults to 50, capped at 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"Any unique string of your own. Retrying with the same key returns the first response instead of repeating the work. Remembered for 24 hours.","schema":{"type":"string","maxLength":191},"examples":{"default":{"value":"order-4821-shipped"}}},"Cursor":{"name":"cursor","in":"query","required":false,"description":"The `meta.next_cursor` from the previous page. Opaque — pass it back unchanged.","schema":{"type":"string"}}},"responses":{"Unauthorized":{"description":"No token, one that has been revoked, or one past its expiry date — the last carries `code: token_expired` so a caller knows to rotate rather than debug.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"missing":{"value":{"message":"Unauthenticated."}},"expired":{"value":{"message":"This token has expired.","code":"token_expired"}}}}}},"Forbidden":{"description":"The token lacks the required ability, or its teammate lacks the permission — both must allow the call.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"message":"This action is unauthorized."}}}},"NotFound":{"description":"No such record in this workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"message":"Not found."}}}},"ValidationFailed":{"description":"The body failed validation. `errors` lists every field that failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"},"example":{"message":"The body field is required.","errors":{"body":["The body field is required."]}}}}},"IdempotencyConflict":{"description":"A request with this Idempotency-Key is still in flight. Retry in a moment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"message":"A request with this Idempotency-Key is still in progress."}}}},"RateLimited":{"description":"The burst limit for this token, or your plan's monthly call allowance, is used up. `Retry-After` says how many seconds to wait.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the limit resets."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"message":"Too Many Attempts."}}}}}}}