{"openapi": "3.1.0", "info": {"title": "Zellwo API", "version": "1.50.0", "description": "Server-side API for the Zellwo workspace: send analytics events, and keep companies and end users in sync with your own systems.\n\n## Authentication\n\nEvery call sends a Bearer token:\n\n```\nAuthorization: Bearer <token>\n```\n\nGet one in Setup \u203a Integrations \u203a API & MCP (any admin). A **personal token** acts as you: use it for your own scripts and Claude Code. An **integration token** acts as a named integration at staff level: use it for your website or backend, keep it on the server, and rotate by adding a second token before revoking the first. Tokens are shown once and revoked on the same page.\n\nClaude, ChatGPT and other MCP clients need no token: connect `https://<workspace>.zellwo.com/mcp` and approve the consent screen (see MCP below).\n\nThe widget signing secret (Channels \u2192 Inbound \u2192 Widget Chat \u2192 Manage \u2192 User Identity, owners only) signs widget identity JWTs and is not an API credential.\n\n### Levels and areas\n\n- **Levels** gate setup. **admin** manages prompts, channels, the widget, integrations, connectors and permanent deletes; **owner** also the workspace profile and staff. Such operations are marked \"(admin)\" or \"(owner)\". A token acts at the lower of its own level and its owner's current level.\n- **Areas** gate content: `tickets`, `contacts`, `companies`, `tasks`, `knowledge`, `events`, `marketing` (segments, CRM statuses and rules, campaigns, newsletter layouts), `products` and `guides`, each at `none`, `view` or `edit`. Content writes need `edit` on their area and no level. An operation's area is its `x-zellwo-area` extension in this document.\n- Owners, and admins whose areas were never narrowed, hold every area at `edit`; other staff never narrowed hold tickets, contacts, companies and tasks at `edit` and the rest at `view`. Personal and OAuth tokens follow their owner's current areas on every call.\n- A read-only token may only call `GET` endpoints, `\u2026/test`, `\u2026/check` and `run_select`.\n- `403` means level too low, a write with a read-only token, `Needs the <area> area` or `Needs edit on the <area> area`. `401` means the token was used on a surface it was not minted for.\n- `GET /workspace/me` reports the effective `level`, `read_only`, `areas`, `ticket_scope` and `preset`. With tickets at `view` only an internal note without an @bot mention may be posted. `search` leaves out closed kinds and `fetch` answers them with a `404`.\n- Owners manage access with `invite_staff` and `update_staff`: `preset` (`support_agent`, `support_lead`, `marketing`, `full`) fills `areas` and `ticket_scope`, explicit values apply on top, `department_ids` replaces memberships. Nobody changes their own access, owners and the bot always stay full, and an unknown value is a `422`.\n\n## MCP\n\nEvery operation in this document is also an MCP tool of the same name (the operation id). Connect `https://<workspace>.zellwo.com/mcp` as a custom connector in Claude or ChatGPT, approve the consent screen, and the assistant calls `overview`, `list_tickets`, `reply_to_ticket`, \u2026 with the permissions of the user who connected. Tools the grant does not allow are not offered. `search` and `fetch` follow the ChatGPT connector contract.\n\n## Conventions\n\n- Base URL is your workspace domain, e.g. `https://acme.zellwo.com/api`.\n- JSON in, JSON out. Timestamps are ISO 8601.\n- Errors return `{\"detail\": \"...\"}` with the matching HTTP status: `401` bad token, `403` level too low or area closed, `404` unknown row, `409` conflict, `422` validation, `429` rate limited (120 requests per minute per IP; 10,000 on analytics ingest).\n- Lists are paginated with `limit` (default 100; a larger value is clamped to 100) and `offset` and return `{\"items\": [...], \"count\": n}`.\n- Companies accept an `external_id`, the id from your own CRM. `POST /companies` with a known `external_id` updates that company instead of creating a second one.\n- Linking an end user to a company resolves the person by `enduser_id`, `email`, `phone` or `external_id` and creates them when nobody matches, so one call syncs a contact.\n\n## Identity and sessions\n\nEvery analytics event names its owner with at least one of `session_id`, `email`, `phone`, `external_id`.\n\n- `email`, `phone` and `external_id` are yours to assert. The first that matches an existing profile wins, in that order; if none match, a profile is created.\n- `session_id` is minted by Zellwo only. `widget.js` signs one against your embed key, creates the profile behind it, and stores it in the first-party cookie `zellwo_session_id:<client_id>`. Read that cookie server-side and send it with your events, and the anonymous browsing before sign-up stays on the profile that later carries the email. Never send a framework session key or a Google Analytics client id.\n- A `session_id` that fails verification is discarded; if it was the only identifier, the event is accepted with `204` and dropped. A verified one that no profile owns any more is attached to the customer your other identifiers resolve.\n- The first request of a visit arrives before `widget.js` has run and carries no cookie. Skip it: **Record the first page of a visit** (Channels \u2192 Inbound \u2192 Widget Chat \u2192 Manage \u2192 Events, or `first_pageview` on `PATCH /workspace/widget-style`) is on by default and reports that page once.\n- Without the widget there is no `session_id`, so every event needs an `email`, `phone` or `external_id` you already hold.\n- An event with a verified `session_id` and an `email`, `phone` or `external_id` merges the anonymous profile into the customer that owns any of them, sessions, tickets and events included. A browser needs a signed `userToken` with an `exp` at most 24 hours ahead to assert an identity; a server does not.\n- From an integration token, an event carrying a verified `session_id` and an `external_id` or a verified `email` also proves that browser as the customer in the chat (it lists their conversations and may run tools for them), until 8 hours pass without such an event. A `logout` event with the `session_id` signs the browser out the way `zellwo.event('logout')` does; for 15 minutes, events for a signed-out session are recorded on the contact their other identifiers name, never bound to that session again.\n- `company_name` resolves to a company and links the contact, matched on `company_external_id`, then website domain, then name. A business email alone never creates a company, public providers never become one, and an existing link is never overwritten.\n\nBrowser events (`widget.js`, `zellwo.event()`, the signed `userToken`) are in the [JavaScript reference](/docs/js/).\n\n## Changelog\n\nField rules, defaults and error codes live on the endpoint; the changelog says what changed and where.\n\nChanges to `widget.js`, the chat widget and `zellwo.event()` are in the [JavaScript reference's changelog](/docs/js/#changelog).\n\n### 1.50.0\n- **Changed:** the AI agent may make 10 connector calls (approval requests included) in **one reply**, across all tools, so a looping reply stops while a long conversation never runs out. A tool's `per_ticket_cap` is deprecated and ignored. Staff runs (`run_ticket_tool`) are no longer capped, only rate-limited (120 calls a minute per connector).\n- `save_connector_tool` takes `prefetch` (Load into every reply): on a Fetch with no inputs from the AI agent and no file, set to `true`, the tool runs before each AI reply on a ticket whose department has it on Always allow, and its answer is in the agent's context from the start (the same answer is reused for 30 minutes on a ticket). Each run counts toward that reply's 10 calls; it shows in `list_connector_calls`, not in the ticket thread. The spec in `get_connector_tool` returns it, and changing it keeps the tool tested.\n- **Deprecated:** `PATCH /workspace/widget-style` no longer sets colours: the chat widget, its launcher and customer emails take the brand kit's `primary_color` (`PUT /brand-kit`). `secondary_color` is ignored; a `primary_color` sent there is stored as the kit's primary (`422` unless `#rrggbb`). Every other field now changes only when sent; before, an omitted colour reset the widget colours.\n- `PATCH /workspace/widget-style` takes `home_button_label` and `home_button_url`: a full-width button on the messenger Home, shown when both are set; blank removes it, a link that is not http(s) is a `422`.\n- `PATCH /workspace/widget-style` takes `chat_greeting`: the fixed first bubble a visitor sees when opening a new conversation, before writing; blank restores the default.\n- The brand kit (`get_brand_kit`, `update_brand_kit`) carries `powered_by` (default `true`): `false` drops the \"Powered by Zellwo\" line under the reply notification email.\n- The brand kit carries `logo_on_color_url`: a white or single-colour logo for surfaces in the brand colour, shown in the chat Home header; empty shows the workspace name there.\n- Analytics ingest from an integration token proves the widget session it carries when the event names the customer by `external_id` or a verified `email` (see Identity and sessions); a `logout` event with `session_id` signs that browser out.\n- `draft_knowledge_gap` takes `note`: the stored draft is redone with it and every earlier note; `draft.feedback` lists the notes given so far.\n- **Breaking:** `draft` on a knowledge gap carries `translations: [{language, title, text}]` instead of `title` and `text`; an `update` draft also carries `edits: [{language, old, new}]` in the article's lead language only (`draft.language`: English when the article has it, else its first language), and `translations` holds that language's text with them applied.\n- `approve_knowledge_gap` takes an optional `preview`: an approved update is carried into every other language of its article by background jobs, each tracked in `draft.propagation` (`{language: {status: queued | applied | preview | skipped | failed, text, error}}`); `preview: true` holds each one for review.\n- `update_knowledge_gap_language` (`PATCH /knowledge-gaps/{gap_id}/languages/{language}`, `status: applied | skipped`) and `apply_knowledge_gap_languages` (`POST /knowledge-gaps/{gap_id}/languages/apply`) apply or skip the previewed languages of an approved knowledge gap.\n\n### 1.49.0\n- `set_connector_access` takes `allow` on an Action too: the AI agent then runs it on that department's tickets without an approval, up to the tool's per-ticket cap; your endpoint should refuse anything ineligible. `always_allow_ticket_approval` works on an Action approval the same way.\n\n### 1.48.0\n- `translate_message` also translates the AI agent's replies to the customer, not only the customer's own messages; staff replies and internal notes are still a `404`.\n\n### 1.47.0\n- `update_ticket` takes `external_reference`: what the ticket is about in your system, e.g. `order:KRZZ4Z`; `''` or `null` clears it, and it allows the same characters as on `upsert_ticket` (at most 100). Each change leaves an internal note. Connector tools act on the ticket's reference, so set only one tied to this ticket's contact.\n\n### 1.46.0\n- Contacts carry a typed business flag: `is_business` (`true` business, `false` private, `null` unknown) and `is_business_source` (`api`, `company`, or empty while unknown) on `get_enduser` and `list_endusers`. Your value wins: `update_enduser` and `ingest_event` set it (`ingest_event` takes a top-level `is_business`, and still reads `properties.is_business` when it is a JSON boolean); `update_enduser` with `null` withdraws yours. While you never set it, a company link makes the contact a business with source `company`. Each event and ticket records the flag as it was when created (`is_business` in `run_select` on `event` and `ticket`). Segments filter on `is_business`, and CRM rule conditions use `is_business` and `is_business_source`. `is_business` no longer lands in `customer_information`; existing values and the rule conditions and segment rules that read `customer_information.is_business` were moved to the typed field.\n- Ticket priority has a fourth level, `3` (Urgent), above High.\n- Departments take `accepted_segment_id`, `redirect_reply`, `redirect_cooldown_hours` (default 24) and `starting_priority` (`create_department`, `update_department`, `list_departments`). With an accepted segment, mail to the department's own address opens a ticket only for a member of that segment whose mail proves the sender (DKIM/DMARC-authenticated, or a reply to mail you sent them; a trusted From header never counts); anyone else gets no ticket and, at most once per cooldown, the redirect reply from the department's address. A ticket opened by mail to that address starts at `starting_priority`. `poll_imap` reports turned-away mail as `redirected`.\n\n### 1.45.0\n- Macros are personal or shared. A personal macro is seen, offered and edited only by the staff member who made it; anyone with the tickets area can make their own. A shared macro is offered to the whole team, and creating, changing or deleting one needs edit on the tickets area (`403` otherwise); admin level is no longer required. `create_macro` and `update_macro` take `personal` (default `false`; switching an existing macro between personal and shared needs edit on the tickets area too), and `list_macros`, `create_macro`, `update_macro` and `list_ticket_macros` return it. `list_macros` and `list_ticket_macros` return your personal macros and the shared ones, yours first on a ticket; someone else's personal macro is a `404`. A title is unique per department among the shared macros and among each person's own, so two people may each keep a personal macro with the same title as a shared one.\n\n### 1.44.0\n- Staff have `sees_all_records` (on by default). Off, every paginated list stops at the first 100 rows for them: past `offset` 100 a page comes back with no items, while `count` still reports the full total, so search and filters remain the way to reach the rest. `run_select` and `describe_schema` answer 403 for them, and `list_tickets` ignores their `sort` and `descending` (most recently updated first), so re-sorting cannot reach rows past the first 100 either. Owners, the bot and integration tokens are always on. `invite_staff` and `update_staff` take it, `list_staff` and `me` return it.\n\n### 1.43.0\n- Staff have an `alias`: the name customers see instead of `first_name` in the widget, the email From name, notifications and the `{agent_first_name}` macro placeholder; empty shows `first_name`. `invite_staff` and `update_staff` take it, `list_staff` and `me` return it.\n\n### 1.42.0\n- Connector access has a Routing row: while routing is on, `get_connector_access` lists it first with `id: null` and name `Routing`. It is the AI agent's tool access while AI routing files a new conversation, before it has a category; once filed, the department's access applies. `set_connector_access` with `department_id: null` sets it, a `404` while routing is off. Existing workspaces start with the default department's access copied there, so nothing changes until you edit it.\n\n### 1.41.0\n- Evaluation criteria can be set per department: `create_prompt` and `update_prompt` take `department_id` on `eval_criteria` prompts, and `list_prompts` returns it with `department_name`. A department's active criteria replace the workspace criteria (an `eval_criteria` prompt without `department_id`) for that department's replies; a department without its own uses the workspace criteria, and with neither its replies are not judged. One active `eval_criteria` prompt per department, else a `422`.\n- The judge that scores each draft reply now also reads what the AI agent's tools returned on that turn, and scores low a fact about an order, a status, a date, an amount or a rule that neither the tools nor the conversation gave, and a reply that repeats an earlier answer or promise without moving the customer on.\n\n### 1.40.0\n- `save_connector_tool` takes `bound_optional`: the server-filled inputs a tool can do without. When the ticket has no value for one, the call is still sent with that input empty; before, it was refused. A contact-filled input (email, phone, external id) is only filled for a verified customer, and a tool whose contact inputs are all optional no longer needs a verified customer at all, so one tool can serve both signed-in customers and visitors who prove themselves another way, such as an order number and postcode. `[]` clears.\n- A connector call the API refuses with `{\"error\": \"not_found\" | \"not_verified\" | \"invalid_params\" | \"rate_limited\" | \"unavailable\", \"message\": \"...\"}` passes that message to the AI agent and the approver, so the customer hears why; without `message`, the generic wording stays.\n\n### 1.39.3\n- Content writes need edit on their area instead of the admin level: articles, knowledge gaps, products, catalogue sources, event types and rules, segments, CRM statuses, rules and recipes, campaigns and newsletter layouts. Staff whose areas were never narrowed hold knowledge, events, marketing, products and guides at `view`; the `support_agent` and `support_lead` presets hold knowledge at `view`.\n\n### 1.39.2\n- Setup needs the admin level instead of owner: every `/channels` operation, `update_widget_style`, `create_embed_key`, `update_allowed_origins` and the five integration operations. The workspace profile, staff and department membership stay owner.\n\n### 1.39.1\n- Analytics SQL: `describe_schema` and `run_select` need only the admin level; each caller reads a snapshot holding what their areas, departments and revenue flag allow (closed areas' tables left out, tickets of their departments plus unrouted ones, money columns `null` without revenue access) instead of a `403`.\n\n### 1.39.0\n- Departments and categories: `delete_department` and `delete_ticket_category` need the owner level, as do `is_default` and `is_active` on `create_department` / `update_department` and `department_id` on `update_ticket_category` (`403`). `translate_message` and `preview_segment` need only view on their area.\n- Ticket scope: `get_company_timeline`, `list_campaign_enrolments`, `list_campaign_drafts` and the draft actions leave out hidden tickets; a knowledge gap or a note on a hidden ticket or task is a `404`; an `upsert_ticket` retry whose first ticket is hidden opens a fresh one. Ticket rows (events, gaps, logs, `run_select`) count as hidden without the tickets area too. Existing integrations get the default integration areas.\n\n### 1.38.0\n- Ticket scope: a staff member whose `ticket_scope` is `departments` sees only the tickets and tasks of their departments (plus unrouted, assigned and mentioned ones) on every list, `search`, `fetch`, `overview` count, follow-up, approval and connector call log; a hidden ticket or task is a `404` like an unknown one, `upsert_ticket` with `match: open` opens a new ticket instead of adding to a hidden one, and `list_events` and `list_knowledge_gaps` leave out the rows of hidden tickets. `describe_schema` and `run_select` also need `ticket_scope` `all` (`403`). Scope `all` sees no change.\n\n### 1.37.0\n- Areas: every operation of the tickets, tasks, contacts, companies, knowledge, events, marketing and products surfaces carries `x-zellwo-area` and refuses a caller without that area (`403`); `search` and `fetch` skip closed kinds. `update_staff` refuses changing your own level or deactivating yourself (`422`). Nothing changes for staff whose areas were never narrowed.\n\n### 1.36.0\n- Tickets: `translate_message` (POST /tickets/{id}/messages/{message_id}/translate, staff) translates a customer message into `language` (default the caller's); stored on the message, so repeating it is free; each new translation counts as AI usage `translation`.\n- AI settings: `get_ai_settings`, `update_ai_settings` (`/ai-settings`, owner): the high effort `model` (customer-facing writing and chat) and the `low_effort_model` (profiling, translation, connector tool docs), '' meaning the platform default; the write-only `openai_api_key` ('' removes it, reads only say `has_own_key`); the profiling and web search switches.\n\n### 1.35.0\n- Integrations: `list_integrations`, `create_integration`, `create_integration_token`, `revoke_integration_token`, `delete_integration` (owner). An integration token works on the REST API and analytics ingest at staff level, never on MCP; what it writes is authored by the integration. `expires`: `6m`, `1y` (default) or `never`. Creating and rotating need a personal token over REST.\n- `upsert_ticket`: a customer message carries sender proof `api` only when an integration token sends it; from a personal token it is `none`.\n- `upsert_ticket` marks an email or phone verified only from an integration token, as ingest does.\n- Analytics ingest honours `email_verified` / `phone_verified` only from an integration token (or the legacy signing secret); from any other token both are false.\n- A message's `author_type` is `integration` for an integration's messages, and `author` its name.\n- Ticket categories: `skip_initial_bot` and `template_as_bot_guidance` are replaced by one `first_reply` (post | ai_writes | wait).\n- A proven phone (`userToken`, integration token) follows the email rule: it fills an empty verified phone, and replaces a verified one only under the same `external_id`; never one another contact verified.\n- Removed: action preview (`save_connector_tool_preview`, `preview_ticket_tool`); an approval no longer carries `preview`.\n- A connector tool's `per_ticket_cap` also counts its open approvals on the ticket.\n- Connector tools: `requires` is no longer settable; a tool needs a proven customer exactly when it fills an input from the contact (or, for an action, the ticket reference). `get_connector_tool` and `list_ticket_tools` show it as a derived, read-only `requires`.\n\n### 1.34.0\n- Ticket categories carry default responses: `default_subject`, `default_creation_message`, `default_bot_failure_message`, `skip_initial_bot` and `template_as_bot_guidance` (placeholders `{company_name}`, `{customer_first_name}`, `{ticket_reference}`).\n- `upsert_ticket` that opens a ticket in a category with a creation message posts it from the bot (never on an `email` message), or, with `template_as_bot_guidance`, has the AI agent write that first reply.\n- Each category's id is shown as `ID: \u2026` in Settings \u2192 Routing; `upsert_ticket` should name its category by `category_id` (labels break on rename), or send none and let the routing agent pick.\n- `upsert_ticket` drops `bot`: the workspace decides whether the AI agent answers (the category's AI switch; an escalated ticket stays with people). A `bot` still sent is ignored.\n\n### 1.33.0\n- `list_ticket_tools` answers `blocked` per tool: `unproven_customer` for a verified tool while the customer has not proven who they are, so no run can succeed.\n- `save_connector_tool`: `requires` is the admin's choice for every tool. Left out, a tool that starts binding a contact source, an action bound to the ticket reference or a file tool becomes `verified`; `none` sticks. `public` is gone: a file tool uses `requires` like any other.\n\n### 1.32.0\n- Connector tools can return a file: a Fetch's `file` (`{\"content\", \"name\", \"types\"}`) and `public` in `save_connector_tool`; `run_ticket_tool` answers `file` (`name`, `size_kb`, `attachment_uuid`), `last_answer` shows a file as `{\"$file\", \"bytes\", \"mime_type\"}`, and `reply_to_ticket` takes `attachment_uuids` to attach a fetched file.\n\n### 1.31.0\n- Connectors (admin): everything the Connectors pages do \u2014 connections, tools, test calls, docs, department access, approvals and ticket actions \u2014 is now in the API and MCP (`/connectors`, `/tickets/{id}/approvals`, `/tickets/{id}/tools`). A connection's `secret` is write-only; responses carry `has_credentials`.\n\n### 1.30.1\n- Analytics ingest: `email_verified` and `phone_verified` (default `true`), as on `upsert_ticket`. `false` marks an address or number typed by a guest as unproven: it is kept as the contact's declared one, never matched beside an `external_id` or a proven identifier, and never replaces a verified one.\n\n### 1.30.0\n- `upsert_ticket`: only send an `external_reference` your system has tied to this contact: connector tools act on it without asking the customer. A reference ending in a line break is now a `422`.\n- `upsert_ticket`: a customer message proves who sent it only when sent over the REST API with no contact identifier marked unverified; through MCP, or with `email_verified` or `phone_verified` false, it proves nothing.\n\n### 1.29.0\n- **Breaking:** contacts no longer carry `stop_campaign_email` or `stop_campaign_sms`; `list_endusers` drops both filters. A contact with either on was unsubscribed from campaigns.\n- `update_enduser` takes `marketing_opt_out`: `true` unsubscribes the contact from every campaign and closes its open enrolment at once, `false` resubscribes; null is refused. Contacts gain `marketing_opt_out_at`.\n- **Breaking:** a task's `action` is `bot_follow_up` or empty; `send_email`, `research_company`, `draft_outreach` and `notify_staff` are refused with a `422` on `create_task` and `update_task`.\n- **Deprecated** (removed next release): `get_enduser_todos` and `update_enduser_todo` now read and write the contact's notes for the bot (`customer_information.$bot_notes`, one fact per line): every line is a pending task, and completing or deleting a task removes its line. `get_enduser`'s `todo_list` reads the same lines.\n- **Breaking:** knowledge gaps open only once the team answered the bot's question and are drafted by AI at once; `status` is `drafted`, `dismissed` or `approved` (open gaps became dismissed, answered ones drafted with an empty draft). `update_knowledge_gap` takes `status: dismissed` only, the `answer` field is gone; `draft_knowledge_gap` retries or redrafts a `drafted` gap.\n\n### 1.28.0\n- `list_tickets` takes `campaign`: `true` lists campaign outreach tickets only, `false` support tickets only; omitted, both as before.\n\n### 1.27.0\n- Segments: `event.<event_type>` fields (`event.checkout`, `event.custom:address`) take `within_days` and `not_within_days` with a whole number of days from 1 to 730, counted back from now; `is_set` / `is_not_set` mean the contact ever / never did it. `list_segment_fields` lists one per active event type.\n- Segments: `crm_status_changed_at` (\"In status since\") takes `after` / `before` and the live date tokens; `before today-2d` is \"in the current status for more than 2 days\".\n\n### 1.26.0\n- `upsert_ticket`: `contact.email_verified` and `contact.phone_verified` (default `true`). `false` marks an identifier typed into a public form as unproven: it is kept as the contact's declared one and never overwrites or downgrades a verified one.\n- `upsert_ticket`: `external_reference` is optional. Leave it out for a ticket about nothing in particular, such as a business enquiry; `match: open` then reuses the contact's open ticket in that category.\n- `upsert_ticket`: `message.channel` (`chat` by default, `email` or `sms`) says how the customer reached you, and so how the answer goes back. A web-form customer sent with `email` gets the agent's reply by e-mail, not a nudge back to a chat they never opened.\n- `reply_to_ticket` takes `attachments`, the same base64 files the upsert takes. They are stored on the reply and travel with the e-mail; a file the workspace refuses is a `422` and no reply is written.\n\n### 1.25.1\n- This reference moved from `/api/docs` to `/docs/api/`; the old address redirects. The developer hub is at `/docs/`.\n\n### 1.25.0\n- Campaigns gain `stale_first_follow_up_after_hours` (default 72, 0 turns it off): a contact who has not answered the first message wakes the agent once for a follow-up decision.\n- Campaigns gain `follow_up_channels` (`email`, `sms` or both; empty means the campaign's own channel). Every follow-up now reports its `channel`.\n- The agent's campaign follow-ups keep the gap between messages and stay inside the goal window; staff follow-ups are only held to the message cap and the allowed channels.\n- `max_messages_per_contact` counts the first send plus the agent's follow-ups; its replies to the customer no longer count, and reaching it leaves the enrolment open.\n- Campaign `budget`, `spend` and `cpa` include every SMS part sent on the campaign's tickets, in the workspace currency. SMS campaigns from before this release count their past sends.\n- Contacts gain `stop_campaign_email` and `stop_campaign_sms`: a stopped channel blocks the agent's campaign messages on it, nothing else. Set with `update_enduser`, filter on `list_endusers`, usable in segments.\n- `send_new_message` takes `department_id`: the conversation is filed under that department and sent from its address, display name and signature. Omitted, the sender's own department.\n\n### 1.24.0\n- Inbound settings gain `department_addresses_only` (keep only mail addressed to an active department) and `unthreaded_opens_new_ticket` (mail that threads onto nothing opens a new ticket).\n- `update_inbound_settings` and `update_outbound_settings` answer `422` for a bad field instead of `500`, and save nothing from a refused request.\n- A department's `email_address` now routes mail: tickets addressed to it land in the department, and replies on its tickets go out from it with its display name and signature.\n- Knowledge base (`/articles`): `list_articles`, `get_article` (staff), `create_article`, `update_article`, `delete_article` (admin). An article carries `translations` by language and an optional `external_id`.\n- `search_knowledge` (`POST /knowledge/search`): what the bot's search returns for a query and language. Read-only tokens may call it.\n- Knowledge gaps (`/knowledge-gaps`, *Article suggestions* in the app): `list_knowledge_gaps`, `update_knowledge_gap`, `draft_knowledge_gap`, `approve_knowledge_gap` turn the team's answers to the bot's questions into articles.\n- Knowledge categories: an article's `category` is a slug; an unknown slug creates the category.\n- `draft_outreach`: a failed draft returns a fixed `detail`; the provider's error text is logged, not returned.\n- Staff channels (`/staff-channels`, owner): `list_staff_channels`, `create_staff_channel`, `update_staff_channel`, `delete_staff_channel`, `test_staff_channel`. A channel is a Slack channel plus your app's bot token (write-only). Writes are refused through MCP.\n- The AI agent asks the team: a question is an unassigned bot task on the ticket, posted to the department's Slack channel, that holds the ticket until answered in the thread or with `create_task_note`.\n- Support settings and departments gain `working_hours`, in the workspace timezone.\n- Macros (`/macros`): `list_macros`, `create_macro`, `update_macro`, `delete_macro` (admin), and `list_ticket_macros` with the placeholders filled in for one ticket.\n- Support settings gain `draft_only`: every AI agent reply becomes an internal `bot_draft` note that staff send from the ticket page.\n- Tickets gain `next_reply_due_at`, set from `first_reply_hours` and `next_reply_hours` on support settings in working hours. `list_tickets?sort=next_reply_due` is the urgency queue.\n- An overdue ticket is announced once in its department's staff channel.\n- Support settings gain `routing_enabled` (the AI agent files unrouted tickets under a category once its gates hold) and `routing_turn_budget` (bot replies before an unrouted ticket is handed over).\n- `update_support_settings`: `draft_only`, `routing_enabled` and `routing_turn_budget` need the owner level.\n- Departments gain `assignment_strategy` (`shared_queue`, the default; `manual`; `round_robin`; `least_busy`) and `max_open_per_agent` (owner). Each assignment leaves an internal note.\n- A staff reply makes the replier the assignee in a `manual`, `round_robin` or `least_busy` department.\n- Staff gain `accepts_tickets`: off, no strategy assigns them new tickets. Removing or deactivating a member hands back their open tickets.\n\n### 1.23.0\n- Departments (`/departments`): `list_departments` (staff), `create_department`, `update_department`, `delete_department` (admin), `set_department_members` (owner). Each comes with a fallback category `Other`; one department is the default.\n- Ticket categories (`/ticket-categories`): `list_ticket_categories` (staff), `create_ticket_category`, `update_ticket_category`, `delete_ticket_category` (admin), with `chat_bot_initiated`, the routing gates and `priority`.\n- The AI agent answers a categorised ticket only while its category's `chat_bot_initiated` is on; an uncategorised ticket is always answered.\n- Support settings (`/support-settings`): `get_support_settings` (staff), `update_support_settings` (admin) with the status timers and `reference_url_template`.\n- `update_ticket` (`PATCH /tickets/{id}`): `assignee_email`, `category_id`, `priority`. Each change leaves an internal note; the customer is never messaged.\n- Tickets gain `external_reference`, `category_id`, `category_label`, `department_id` and `merged_into`.\n- `list_tickets` filters: `department_id`, `category_id`, `unrouted`, `assignee`, `external_reference`, `external_reference__startswith`, `updated_since`.\n- `merge_ticket` (`POST /tickets/{id}/merge`): folds a ticket into another of the same contact, moving its messages, tasks, notes and todos. Answers with the ticket that now holds the conversation.\n- `upsert_ticket` (`POST /tickets/upsert`): your server records something that happened for a customer as a ticket, created or updated by `external_reference`, without messaging anyone. Supports `idempotency_key`.\n\n### 1.22.0\n- `schedule_follow_up` and `update_follow_up` take `channel` (`email` or `sms`); follow-ups return `channel`, null for the customer's own channel.\n- `configure_sms`: a custom `sender` goes out as set, nothing approves it. `test_sms` is `success` whenever the gateway answered.\n\n### 1.21.0\n- `reply_to_ticket` takes `channel` (`email` or `sms`) and returns it; `emailed` is true only for an e-mail.\n\n### 1.20.0\n- SMS channel (owner): `get_sms`, `configure_sms`, `test_sms`, `remove_sms`. SMS goes out through the platform gateway and is billed per part.\n- `get_channels` gains `sms`, `overview` gains `sms_configured`, `send_new_message` takes `channel` (`email` or `sms`).\n\n### 1.19.0\n- Campaigns gain `sender_name`, `writing_mode` (`templates` or `ai`), `ai_scope`, `research_missing`, `languages` and `daily_cap`, and return `status_reason` and `launch_checks`. Templates carry a `language`.\n- `preview_campaign` returns the eligible contacts per `language` and `no_company`. Enrolments add `language`, `has_draft`, `approved_at`, `approved_by`.\n- AI mode: `list_campaign_drafts`, `approve_campaign_draft`, `regenerate_campaign_draft`, `skip_campaign_draft`, `estimate_campaign_cost`. `generate_campaign_templates` takes `language` and `missing_languages`.\n\n### 1.18.0\n- Analytics SQL gains `llm_span` (one row per model or tool call of an AI run) and `llm_call.run_id`. Text and JSON are scrubbed of IPs and tracking parameters; a JSON value over 64 KB reads as `{\"cut\": true, \"chars\": N}`.\n\n### 1.17.0\n- `describe_schema`: `json_keys` is now `{column: {key: rows}}`, up to 150 keys per column. New fields `timezone`, `utc_offset`, `notes` and `event_types`.\n- Workspaces keep an `analytics_notes` prompt that `describe_schema` returns; `me` reports `sees_revenue`.\n- `enduser` and `event` gain typed columns lifted out of `customer_information`: order, amount, page, device, country, referrer and UTM fields, purchase totals and `first_touch_channel`.\n- Snapshot JSON columns no longer carry IPs or widget session ids; URLs keep only their campaign parameters.\n- The snapshot is rebuilt in the background after 10 minutes; only a workspace's very first call gets a `503`.\n- A `run_select` stopped by a limit says which one: memory, time or result size.\n\n### 1.16.0\n- Permanent deletes (admin): `delete_ticket` removes the conversation and everything logged for it; `delete_enduser` removes the contact with every ticket, event and enrolment. Companies stay.\n\n### 1.15.0\n- Analytics SQL (admins who see revenue): `describe_schema` (`GET /sql/schema`) and `run_select` (`POST /sql/select`), one SQLite SELECT against the workspace snapshot. 500 rows, 5 s, 30 calls a minute. Read-only tokens may call `run_select`.\n\n### 1.14.0\n- Feed wizard: `preview_catalogue_feed` detects the feed type and proposes a mapping; `check_catalogue_source` reads one page without writing; `sync_catalogue_sources` starts every active source.\n- A product without a source may not repeat another manual product's `sku` (`409`); feed rows may.\n- `price` may be null for price-on-request items.\n\n### 1.13.0\n- Products carry the columns every feed has (`brand`, `group_key`, `availability`, `sale_price`, `gtin`, `mpn`, `category`, `images`, `is_active` and more) and `attributes` by property key. New `get_product`; `list_products` filters on them.\n- A sku may repeat: `create_product` no longer answers `409`.\n- Catalogue sources (`/catalogue-sources`): list, get, create, update, delete, `sync_catalogue_source` and `list_catalogue_syncs`. Types `google_feed`, `csv`, `json`, `shopify`, `woocommerce`; credentials are write-only.\n\n### 1.12.0\n- Campaigns (`/campaigns`): list, get, create, update, archive, `delete_campaign` (only one that never enrolled anyone and is not running, else a 409), `activate_campaign`, `pause_campaign`, `preview_campaign`, `list_campaign_goals`. A campaign enrols a segment on one channel for one goal event; each enrolment opens a ticket. Money fields are in the workspace currency.\n- Campaign templates are the arms of a bandit: list, create, update, delete and `generate_campaign_templates`, each with its send, reply and conversion counts and `win_probability`. Editing a sent template forks a new arm. `list_campaign_enrolments` gives the per-contact rows.\n- One-click unsubscribe headers and footer on campaign mail. Contacts carry `marketing_opt_out`, filterable on `list_endusers`.\n- New CRM stage `unaware`, below `cold`, for a contact known only by an address.\n\n### 1.11.0\n- Segments (`/segments`): list, get, create, update, delete, plus `list_segment_fields` (valid keys and operators) and `preview_segment` (counts matches without saving). A segment is a rule list with `match` `all` or `any`.\n- `list_endusers` takes `segment_id` and keeps only that segment's members.\n- Segment date rules may be relative (`today`, `today-7d`, `today+3d`), resolved on every evaluation; with the derived `$purchase_*` keys they replace fixed windows.\n- A segment's `prompt` is added to the agent's instructions while it talks to a member.\n\n### 1.10.0\n- `update_enduser` takes `properties` by key; contacts return `properties` and `initial_properties`. Rules and filters address them as `customer_information__<key>`.\n- A contact definition may not take a server-metadata or per-event key (`country`, `url`, `utm_source`, `event_id`, \u2026): `create_property_definition` answers `422`.\n- Notes on companies, contacts and tasks: `list_*_notes`, `create_*_note` and `delete_note`. A note with no `author_email` was written by Zellwo itself.\n- `get_company_timeline` merges the linked contacts' events, their tickets opened and solved, and the notes on the company, newest first.\n- **Breaking:** companies no longer carry `notes`. The field is gone from `create_company`, `update_company` and every company payload; existing summary text was folded into a first note.\n\n### 1.9.0\n- Tasks (`/tasks`): `list_tasks`, `create_task`, `update_task`, `complete_task`. A task belongs to a company, contact or ticket; the follow-up endpoints are the same rows with `bot_follow_up`.\n- Follow-up statuses now read `open` / `done` / `cancelled` / `failed` instead of `pending` / `executed`.\n- Workspace-defined properties: `list_property_definitions`, `create_property_definition`, `update_property_definition` (admin) define the fields kept on companies, contacts and products.\n- `update_company` takes `properties` by key; companies return `properties` and `initial_properties` (the first value each ever held).\n\n### 1.8.2\n- SendGrid email channel (owner): `configure_sendgrid`, `test_sendgrid`, `remove_sendgrid`, the `sendgrid_inbound` endpoints, `update_sendgrid_events`, `select_outbound_channel`. `get_channels` and `update_inbound_settings` gain the matching fields.\n- A SendGrid hard bounce marks the contact undeliverable (`email_bounced` event).\n\n### 1.8.1\n- Ingest accepts `note` and `source` (`bot` or `system`); `source` is ignored on the widget signing secret.\n- `check_contact` takes `external_id` as an alternative to `email`, returns the full contact, and with `profile: true` starts AI profiling (`profiling_started`).\n- `check_contact`, `run_enduser_profiling` and `run_company_profiling` take `wait_seconds` to block until profiling ends.\n\n### 1.8.0\n- Companies carry `industry` and `tags` (patching `tags` replaces the list). `list_companies` takes `tag`; `q` also matches industry and tags.\n- Company profiling fills blank country, address, VAT number, registration number, phone, email, industry and tags. Typed values are never overwritten.\n- Workspace settings carry `currency` (ISO 4217). Contacts gain `$purchase_total` and `$purchase_currency` in that currency.\n- CRM rule conditions reach into the company: `{\"company__country\": \"DE\"}`, `{\"company__tags__contains\": \"Shopify\"}`.\n\n### 1.7.0\n- MCP & API tokens carry a grant: surfaces (MCP, API, ingest), a level and read-only. Outside its surfaces a token is a `401`; a write with a read-only token is a `403`. `GET /workspace/me` gains `read_only`.\n- CRM rules take and return `conditions`, e.g. `{\"customer_information__step\": \"payment\"}`.\n- Inactivity-anchored rules measure from the contact's last non-system event.\n\n### 1.6.0\n- Ingest accepts an `attribution` object, preferred over UTM parameters and click ids parsed from `url`.\n\n### 1.5.0\n- An event with a verified `session_id` and an `email` merges the session profile into the customer owning that address.\n\n### 1.4.0\n- Ingest accepts `ip` and `user_agent`.\n\n### 1.3.0\n- Ingest accepts `company_name`, `company_external_id` and `company_website`: the company is matched or created and the contact linked.\n\n### 1.2.1\n- Documented identity resolution and `session_id` verification.\n\n### 1.2.0\n- OAuth tokens with levels. Every staff UI action is an endpoint and an MCP tool: workspace, tickets, end users, companies, events, event types and rules, CRM statuses and rules, prompts, products, channels.\n\n### 1.1.1\n- Ingest accepts every registered event name, including system events, conversation signals and `custom:*` names (registered on first sight).\n\n### 1.1.0\n- Companies: list, create/upsert, read, patch, delete. End users: read one; link to and unlink from a company.\n- Docs moved from `/api/analytics/docs` to `/api/docs`.\n\n### 1.0.0\n- Analytics ingestion: `POST /analytics/ingest` and `POST /analytics/ingest/batch`.\n"}, "paths": {"/api/workspace/me": {"get": {"operationId": "me", "summary": "Who the token belongs to", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MeOut"}}}}}, "description": "The user behind the credential, the level the token acts at (staff < admin < owner, capped by the token's grant), whether it is read-only, whether the user sees revenue (analytics SQL needs it), and the areas, ticket scope and preset that decide what they may open.", "tags": ["Workspace"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/workspace/me\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/workspace/me', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/workspace/me', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/workspace/overview": {"get": {"operationId": "overview", "summary": "Workspace overview", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/OverviewOut"}}}}}, "description": "Counts and configuration at a glance: tickets by status (the tickets you see), companies, CRM statuses with contact counts, active rules, registered events, channels, embed key. Call this first.", "tags": ["Workspace"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/workspace/overview\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/workspace/overview', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/workspace/overview', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/workspace/getting-started": {"get": {"operationId": "getting_started", "summary": "Getting started checklist (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/GettingStartedOut"}}}}}, "description": "The setup steps of a new workspace in the Getting started page's order (required: profile, your own OpenAI key, widget, AI instructions, knowledge, email, team, a test question; optional: Slack, catalogue, Claude or ChatGPT), each done or not as the workspace shows it now, with the page that sets it up; done_count counts the required steps done. Read it to tell a client what to set up next.", "tags": ["Workspace"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/workspace/getting-started\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/workspace/getting-started', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/workspace/getting-started', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/workspace/settings": {"get": {"operationId": "get_workspace", "summary": "Workspace profile and widget settings (owner)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/WorkspaceOut"}}}}}, "tags": ["Workspace"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/workspace/settings\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/workspace/settings', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/workspace/settings', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "patch": {"operationId": "update_workspace", "summary": "Update the workspace profile (owner)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/WorkspaceOut"}}}}}, "description": "Company name, registration/VAT codes, address, ISO country, website, IANA timezone and currency (an ISO 4217 code the platform knows; EUR until set); the website also seeds the widget's allowed origins.", "tags": ["Workspace"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/WorkspacePatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/workspace/settings\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/workspace/settings', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'name': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/workspace/settings', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"name\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/workspace/widget-style": {"patch": {"operationId": "update_widget_style", "summary": "Set automatic / first pageview and the messenger Home (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/WorkspaceOut"}}}}}, "description": "Each field only when sent: the pageview flags, the Home settings (`home_greeting`, `home_people`, `home_reply_promise`, `home_working_hours`, `home_show_bot`, the custom button's `home_button_label` and `home_button_url`, the chat's first bubble `chat_greeting`) and the AI agent's `bot_name`. The widget's colours come from the brand kit; a deprecated `primary_color` is stored there. A `primary_color` that is not #rrggbb, a `home_people` email that is not a staff member's, a `home_button_url` that is not http(s), or a blank or over-40-character `bot_name`, is a 422.", "tags": ["Workspace"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/WidgetStyleIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/workspace/widget-style\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"primary_color\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/workspace/widget-style', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'primary_color': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/workspace/widget-style', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"primary_color\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/workspace/embed-key": {"post": {"operationId": "create_embed_key", "summary": "Create the widget embed key (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/WorkspaceOut"}}}}}, "description": "Idempotent. The public key id is returned; the secret token is only ever shown under Channels \u203a Widget Chat \u203a Manage \u203a User Identity.", "tags": ["Workspace"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/workspace/embed-key\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/workspace/embed-key', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/workspace/embed-key', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/workspace/allowed-origins": {"put": {"operationId": "update_allowed_origins", "summary": "Replace the widget allowed origins (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/WorkspaceOut"}}}}}, "description": "Origins (scheme://host, `*.` wildcards allowed) the chat widget may load from.", "tags": ["Workspace"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/OriginsIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PUT \"https://zellwo.zellwo.com/api/workspace/allowed-origins\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"origins\": []}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PUT', 'https://zellwo.zellwo.com/api/workspace/allowed-origins', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'origins': []})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/workspace/allowed-origins', { method: 'PUT', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"origins\": []}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/workspace/staff": {"get": {"operationId": "list_staff", "summary": "List staff (owner)", "parameters": [{"in": "query", "name": "q", "schema": {"default": "", "title": "Q", "type": "string"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/StaffOut"}, "title": "Response", "type": "array"}}}}}, "tags": ["Workspace"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/workspace/staff\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/workspace/staff', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/workspace/staff', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "invite_staff", "summary": "Invite a staff member with a preset or areas and ticket scope (owner)", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/StaffOut"}}}}}, "description": "Creates a verified account; `level` is owner, admin or staff, and its access is the preset (full by default) with `areas` and `ticket_scope` on top.", "tags": ["Workspace"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/StaffIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/workspace/staff\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\": \"string\", \"password\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/workspace/staff', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'email': 'string', 'password': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/workspace/staff', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"email\": \"string\", \"password\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/workspace/staff/{user_id}": {"patch": {"operationId": "update_staff", "summary": "Update a staff member, their preset, areas, ticket scope and departments (owner; accepts_tickets alone: self or admin)", "parameters": [{"in": "path", "name": "user_id", "schema": {"format": "uuid", "title": "User Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/StaffOut"}}}}}, "description": "Changes only the fields sent; a preset fills areas and ticket_scope before the explicit ones apply, nobody changes their own access and owners stay full. `accepts_tickets` on its own may be sent by the member themself or an admin; every other field needs the owner level.", "tags": ["Workspace"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/StaffPatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/workspace/staff/{user_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"preset\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/workspace/staff/{user_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'preset': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/workspace/staff/{user_id}', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"preset\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "remove_staff", "summary": "Remove a staff member (owner)", "parameters": [{"in": "path", "name": "user_id", "schema": {"format": "uuid", "title": "User Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "description": "Deactivates and hides the account; their tickets and messages are kept.", "tags": ["Workspace"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/workspace/staff/{user_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/workspace/staff/{user_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/workspace/staff/{user_id}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/workspace/staff/me/photo": {"post": {"operationId": "upload_my_photo", "summary": "Upload your own profile photo", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/StaffOut"}}}}}, "description": "Sets the caller's photo, shown beside their name in the app and on the chat widget. Multipart, field `file`: a JPEG, PNG, WebP or GIF under 5 MB, cropped to a centred square. Every upload gets a new `photo_url` and the previous one stops working. Anything else is a 422. From an MCP client use upload_my_photo_from_url.", "tags": ["Workspace"], "requestBody": {"content": {"multipart/form-data": {"schema": {"properties": {"file": {"format": "binary", "title": "File", "type": "string"}}, "required": ["file"], "title": "FileParams", "type": "object"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/workspace/staff/me/photo\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/workspace/staff/me/photo', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/workspace/staff/me/photo', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "remove_my_photo", "summary": "Remove your own profile photo", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/StaffOut"}}}}}, "description": "Clears the caller's photo; their initials show again.", "tags": ["Workspace"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/workspace/staff/me/photo\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/workspace/staff/me/photo', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/workspace/staff/me/photo', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/workspace/staff/me/photo/from-url": {"post": {"operationId": "upload_my_photo_from_url", "summary": "Set your own profile photo from an image URL", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/StaffOut"}}}}}, "description": "Give a public https image URL; Zellwo downloads it. Sets the caller's photo exactly as upload_my_photo does: a JPEG, PNG, WebP or GIF under 5 MB, cropped to a centred square, with a new `photo_url`. A non-http(s) or private address, a timeout, a file over 5 MB or one that is not an image is a 422.", "tags": ["Workspace"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ImageUrlIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/workspace/staff/me/photo/from-url\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"url\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/workspace/staff/me/photo/from-url', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'url': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/workspace/staff/me/photo/from-url', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"url\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/workspace/staff/{user_id}/photo": {"post": {"operationId": "upload_staff_photo", "summary": "Upload a staff member's profile photo (owner)", "parameters": [{"in": "path", "name": "user_id", "schema": {"format": "uuid", "title": "User Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/StaffOut"}}}}}, "description": "Sets the staff member's photo, shown beside their name in the app and on the chat widget; anyone may also set their own here, or use upload_my_photo. Multipart, field `file`: a JPEG, PNG, WebP or GIF under 5 MB, cropped to a centred square. Every upload gets a new `photo_url` and the previous one stops working. Anything else is a 422. From an MCP client use upload_staff_photo_from_url.", "tags": ["Workspace"], "requestBody": {"content": {"multipart/form-data": {"schema": {"properties": {"file": {"format": "binary", "title": "File", "type": "string"}}, "required": ["file"], "title": "FileParams", "type": "object"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/workspace/staff/{user_id}/photo\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/workspace/staff/{user_id}/photo', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/workspace/staff/{user_id}/photo', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "remove_staff_photo", "summary": "Remove a staff member's profile photo (owner)", "parameters": [{"in": "path", "name": "user_id", "schema": {"format": "uuid", "title": "User Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/StaffOut"}}}}}, "description": "Clears the staff member's photo; their initials show again. Anyone may also clear their own here, or use remove_my_photo.", "tags": ["Workspace"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/workspace/staff/{user_id}/photo\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/workspace/staff/{user_id}/photo', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/workspace/staff/{user_id}/photo', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/workspace/staff/{user_id}/photo/from-url": {"post": {"operationId": "upload_staff_photo_from_url", "summary": "Set a staff member's profile photo from an image URL (owner)", "parameters": [{"in": "path", "name": "user_id", "schema": {"format": "uuid", "title": "User Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/StaffOut"}}}}}, "description": "Give a public https image URL; Zellwo downloads it. Sets the staff member's photo exactly as upload_staff_photo does: a JPEG, PNG, WebP or GIF under 5 MB, cropped to a centred square, with a new `photo_url`; anyone may also set their own here, or use upload_my_photo_from_url. A non-http(s) or private address, a timeout, a file over 5 MB or one that is not an image is a 422.", "tags": ["Workspace"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ImageUrlIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/workspace/staff/{user_id}/photo/from-url\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"url\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/workspace/staff/{user_id}/photo/from-url', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'url': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/workspace/staff/{user_id}/photo/from-url', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"url\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/workspace/integrations": {"get": {"operationId": "list_integrations", "summary": "List integrations and their tokens (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/IntegrationOut"}, "title": "Response", "type": "array"}}}}}, "description": "Each integration is a named non-human identity for one backend; its tokens list the last four characters, never the token.", "tags": ["Workspace"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/workspace/integrations\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/workspace/integrations', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/workspace/integrations', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "create_integration", "summary": "Create an integration and its first token (admin)", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/IntegrationCreatedOut"}}}}}, "description": "The token works on the REST API and analytics ingest at staff level, never on MCP; what it writes is authored by the integration, and only its\ncustomer messages count as proof of the sender in `upsert_ticket`. `expires`: `6m`, `1y` (default) or `never`. The token is returned once. Needs a personal token over REST: refused through MCP and OAuth apps.", "tags": ["Workspace"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/IntegrationIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/workspace/integrations\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/workspace/integrations', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'name': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/workspace/integrations', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"name\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/workspace/integrations/{integration_id}/tokens": {"post": {"operationId": "create_integration_token", "summary": "Add a token to an integration (admin)", "parameters": [{"in": "path", "name": "integration_id", "schema": {"format": "uuid", "title": "Integration Id", "type": "string"}, "required": true}], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/IntegrationTokenOut"}}}}}, "description": "To rotate: add a token, move the backend to it, then revoke the old one; both work in between. Returned once. Needs a personal token over REST: refused through MCP and OAuth apps.", "tags": ["Workspace"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/IntegrationTokenIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/workspace/integrations/{integration_id}/tokens\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"expires\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/workspace/integrations/{integration_id}/tokens', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'expires': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/workspace/integrations/{integration_id}/tokens', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"expires\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/workspace/integrations/{integration_id}/tokens/{token_id}": {"delete": {"operationId": "revoke_integration_token", "summary": "Revoke an integration token (admin)", "parameters": [{"in": "path", "name": "integration_id", "schema": {"format": "uuid", "title": "Integration Id", "type": "string"}, "required": true}, {"in": "path", "name": "token_id", "schema": {"title": "Token Id", "type": "integer"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "description": "Anything still using that token gets 401 from now on; the integration's other tokens keep working.", "tags": ["Workspace"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/workspace/integrations/{integration_id}/tokens/{token_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/workspace/integrations/{integration_id}/tokens/{token_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/workspace/integrations/{integration_id}/tokens/{token_id}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/workspace/integrations/{integration_id}": {"delete": {"operationId": "delete_integration", "summary": "Delete an integration and revoke its tokens (admin)", "parameters": [{"in": "path", "name": "integration_id", "schema": {"format": "uuid", "title": "Integration Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "description": "Every token stops working; notes and messages it wrote stay, still authored by it.", "tags": ["Workspace"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/workspace/integrations/{integration_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/workspace/integrations/{integration_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/workspace/integrations/{integration_id}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/workspace/search": {"get": {"operationId": "search", "summary": "Search tickets, contacts and companies", "parameters": [{"in": "query", "name": "query", "schema": {"title": "Query", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SearchOut"}}}}}, "description": "Free-text search across ticket subjects/customers, contacts (name, email, phone) and companies (name, domain), each kind only when the caller has its area. Ids are `ticket:<uuid>`, `enduser:<uuid>`, `company:<uuid>`; pass one to fetch.", "tags": ["Workspace"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/workspace/search\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/workspace/search', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/workspace/search', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/workspace/fetch": {"get": {"operationId": "fetch", "summary": "Fetch one search result in full", "parameters": [{"in": "query", "name": "id", "schema": {"title": "Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/FetchOut"}}}}}, "description": "The full text behind a search result id (`ticket:<uuid>` conversation, `enduser:<uuid>` facts and profile, `company:<uuid>` facts and profile); a kind outside the caller's areas is a 404.", "tags": ["Workspace"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/workspace/fetch\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/workspace/fetch', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/workspace/fetch', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/tickets": {"get": {"operationId": "list_tickets", "summary": "List tickets", "parameters": [{"in": "query", "name": "status", "schema": {"default": "", "title": "Status", "type": "string"}, "required": false}, {"in": "query", "name": "enduser_id", "schema": {"default": "", "title": "Enduser Id", "type": "string"}, "required": false}, {"in": "query", "name": "q", "schema": {"default": "", "title": "Q", "type": "string"}, "required": false}, {"in": "query", "name": "sort", "schema": {"default": "updated", "title": "Sort", "type": "string"}, "required": false}, {"in": "query", "name": "descending", "schema": {"default": true, "title": "Descending", "type": "boolean"}, "required": false}, {"in": "query", "name": "department_id", "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Department Id"}, "required": false}, {"in": "query", "name": "category_id", "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Category Id"}, "required": false}, {"in": "query", "name": "unrouted", "schema": {"default": false, "title": "Unrouted", "type": "boolean"}, "required": false}, {"in": "query", "name": "assignee", "schema": {"default": "", "title": "Assignee", "type": "string"}, "required": false}, {"in": "query", "name": "external_reference", "schema": {"default": "", "title": "External Reference", "type": "string"}, "required": false}, {"in": "query", "name": "external_reference__startswith", "schema": {"default": "", "title": "External Reference  Startswith", "type": "string"}, "required": false}, {"in": "query", "name": "updated_since", "schema": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Updated Since"}, "required": false}, {"in": "query", "name": "campaign", "schema": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Campaign"}, "required": false}, {"in": "query", "name": "limit", "schema": {"default": 100, "title": "Limit", "type": "integer"}, "required": false}, {"in": "query", "name": "offset", "schema": {"default": 0, "minimum": 0, "title": "Offset", "type": "integer"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PagedTicketOut"}}}}}, "description": "Tickets newest-activity first. `status` is one of New/Open/Pending/On-Hold/Solved/Closed/Spam, or Queue for New and Open together (the work queue), and no status lists everything but Spam; `q` matches the customer's name, email or the ticket id; `sort` is updated/created/status/priority/messages/next_reply_due; `sort=next_reply_due&descending=false` is the urgency queue: overdue first, then the soonest due, tickets owing no reply last. `department_id` keeps the tickets whose category belongs to that department, `category_id` one category, `unrouted` the tickets with no category yet; `assignee` takes me, none or a staff email; `external_reference` matches exactly and `external_reference__startswith` by prefix; `updated_since` (ISO 8601) keeps tickets changed at or after it; `campaign` true keeps campaign outreach tickets only, false support tickets only, omitted both. A caller whose `sees_all_records` is off always gets the default order (`sort` and `descending` are ignored).", "tags": ["Tickets"], "security": [{"ApiAuth": []}], "x-zellwo-area": "tickets", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/tickets\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/tickets', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tickets', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/tickets/{ticket_id}": {"get": {"operationId": "get_ticket", "summary": "Get a ticket with its conversation", "parameters": [{"in": "path", "name": "ticket_id", "schema": {"format": "uuid", "title": "Ticket Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TicketDetailOut"}}}}}, "description": "The ticket, its last 50 messages (internal notes included), its todo list and scheduled follow-ups.", "tags": ["Tickets"], "security": [{"ApiAuth": []}], "x-zellwo-area": "tickets", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/tickets/{ticket_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/tickets/{ticket_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tickets/{ticket_id}', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "patch": {"operationId": "update_ticket", "summary": "Assign, re-categorise, re-prioritise or set the reference of a ticket", "parameters": [{"in": "path", "name": "ticket_id", "schema": {"format": "uuid", "title": "Ticket Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TicketOut"}}}}}, "description": "Only the fields you send change: `assignee_email` (a staff member; null unassigns), `category_id` (an active category from `/ticket-categories`; null leaves the ticket unrouted), `priority` (0 low, 1 medium, 2 high, 3 urgent), `external_reference` (what the ticket is about in your system, e.g. `order:KRZZ4Z`; '' or null clears it; same characters as `upsert_ticket`, at most 100). Each real change leaves one internal note naming the old and new value; the customer is never messaged and the agent does not run.", "tags": ["Tickets"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TicketPatchIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "tickets", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/tickets/{ticket_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"assignee_email\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/tickets/{ticket_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'assignee_email': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tickets/{ticket_id}', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"assignee_email\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "delete_ticket", "summary": "Delete a ticket (admin)", "parameters": [{"in": "path", "name": "ticket_id", "schema": {"format": "uuid", "title": "Ticket Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "description": "Permanent: removes the conversation, attachments and their files, tasks, notes and the emails logged for it; the contact, their events and any campaign enrolment stay.", "tags": ["Tickets"], "security": [{"ApiAuth": []}], "x-zellwo-area": "tickets", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/tickets/{ticket_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/tickets/{ticket_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tickets/{ticket_id}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/tickets/{ticket_id}/messages": {"post": {"operationId": "reply_to_ticket", "summary": "Reply on a ticket, or leave an internal note", "parameters": [{"in": "path", "name": "ticket_id", "schema": {"format": "uuid", "title": "Ticket Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ReplyOut"}}}}}, "description": "A staff reply in your name; the ticket moves to Pending and the reply goes out on the customer's own channel: SMS when they last texted, the full e-mail when they wrote by e-mail, a nudge back to the chat for widget customers. `channel` (`email` or `sms`) sends it that way instead, a `422` when the customer has no address or phone for it or the workspace has no such channel. `internal: true` stores a note the customer never sees; one naming `@bot` answers the bot's open question to the team, otherwise it schedules the bot to look again, and each `@Name` of a staff member gives them a task. `attachments` (`[{\"name\": \"price-list.pdf\", \"base64\": \"...\"}]`) are stored on the reply and travel with the e-mail; one the workspace refuses is a `422` and no reply is written. `attachment_uuids` attach files a connector tool fetched on this ticket (its internal line's `file.attachment_uuid`); any other is a `422`. A file fetched for the proven customer is held back from an e-mail copied to others, with an internal note. `channel` in the answer is how the reply went out; when it is null, `reason` says why. `emailed` is true only for e-mail. A ticket merged into another is a `409`: reply on that one.", "tags": ["Tickets"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ReplyIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "tickets", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/tickets/{ticket_id}/messages\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"content\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/tickets/{ticket_id}/messages', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'content': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tickets/{ticket_id}/messages', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"content\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/tickets/{ticket_id}/status": {"post": {"operationId": "update_ticket_status", "summary": "Change a ticket status", "parameters": [{"in": "path", "name": "ticket_id", "schema": {"format": "uuid", "title": "Ticket Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TicketOut"}}}}}, "description": "The change is noted on the ticket as an internal note; Solved records a `ticket_solved` event for the customer, which CRM rules can react to.", "tags": ["Tickets"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TicketStatusIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "tickets", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/tickets/{ticket_id}/status\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"status\": \"New\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/tickets/{ticket_id}/status', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'status': 'New'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tickets/{ticket_id}/status', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"status\": \"New\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/tickets/upsert": {"post": {"operationId": "upsert_ticket", "summary": "Create or update a ticket by external reference; only the category's first reply or the AI agent reaches the customer", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UpsertTicketOut"}}}}}, "description": "Records something that happened for a customer in your own system (an order cancelled, a claim filed) as a ticket. Nothing is sent to the customer, except the category's default responses: a ticket opened in a category with a creation message gets that first reply (never on an `email` message), and a customer message is answered by the AI agent where the category's AI is on and the ticket was not escalated.\nIdentify the contact by `external_id` (matched first), `email` or `phone`; one is created when none matches, with `email_verified: false` or `phone_verified: false` marking an\nidentifier typed into a public form as unproven (both count as false unless an integration token sends them), so it is kept as the contact's declared one and never overwrites a verified one. `external_reference` (e.g. `order:KRZZ4Z`) says what\nthe ticket is about; leave it out for a ticket about nothing in particular, such as a business enquiry. Only send an external_reference your system has tied to this contact: connector tools act\non it without asking the customer. `match: open` (the default) adds to the contact's latest non-closed, unmerged\nticket with the same reference and category, `new` always opens one. Name the category by `category_id` or its label `Department \u203a Parent \u203a Child`. `message.internal: true` leaves a\nnote only staff see, written as you; `false` records the customer's own words and reopens a Solved ticket, on the `message.channel` the customer reached you by (`chat` by default,\n`email` or `sms`), which is how the answer travels back to them. A customer message counts as proof of the sender only when an integration token (Setup \u203a Integrations \u203a API & MCP) sends it over the REST API with no identifier marked unverified; a personal token's never does.\n`status` is applied last; `Solved` or `Closed` closes the ticket.\nResending the same `idempotency_key` returns the first result with `created: false` and writes nothing; the same key with another `external_reference` is a `409`. An unknown category,\na contact without an identifier or an attachment the workspace refuses is a `422`, and nothing is written.", "tags": ["Tickets"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UpsertTicketIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "tickets", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/tickets/upsert\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"contact\": {}}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/tickets/upsert', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'contact': {}})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tickets/upsert', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"contact\": {}}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/tickets/{ticket_id}/bot": {"post": {"operationId": "set_ticket_bot", "summary": "Pause or resume the AI agent on a ticket", "parameters": [{"in": "path", "name": "ticket_id", "schema": {"format": "uuid", "title": "Ticket Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TicketOut"}}}}}, "description": "`paused: true` hands the conversation to humans; `false` lets the agent answer again (and resets its failure counter).", "tags": ["Tickets"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/BotIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "tickets", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/tickets/{ticket_id}/bot\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"paused\": true}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/tickets/{ticket_id}/bot', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'paused': True})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tickets/{ticket_id}/bot', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"paused\": true}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/tickets/{ticket_id}/bot-reply": {"post": {"operationId": "request_bot_reply", "summary": "Ask the AI agent to answer now", "parameters": [{"in": "path", "name": "ticket_id", "schema": {"format": "uuid", "title": "Ticket Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/OkOut"}}}}}, "description": "Queues one agent turn on the ticket even if nothing new came in.", "tags": ["Tickets"], "security": [{"ApiAuth": []}], "x-zellwo-area": "tickets", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/tickets/{ticket_id}/bot-reply\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/tickets/{ticket_id}/bot-reply', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tickets/{ticket_id}/bot-reply', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/tickets/{ticket_id}/todos": {"get": {"operationId": "get_ticket_todos", "summary": "List a ticket todo list", "parameters": [{"in": "path", "name": "ticket_id", "schema": {"format": "uuid", "title": "Ticket Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TodosOut"}}}}}, "description": "The AI agent's checklist for this conversation, grouped by status.", "tags": ["Tickets"], "security": [{"ApiAuth": []}], "x-zellwo-area": "tickets", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/tickets/{ticket_id}/todos\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/tickets/{ticket_id}/todos', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tickets/{ticket_id}/todos', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "update_ticket_todo", "summary": "Add, update or delete a ticket todo", "parameters": [{"in": "path", "name": "ticket_id", "schema": {"format": "uuid", "title": "Ticket Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TodosOut"}}}}}, "description": "`add` a task, `update` its text (`new_task`) or `status` (pending/in_progress/completed), or `delete` it. `task` is matched by exact text.", "tags": ["Tickets"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TodoIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "tickets", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/tickets/{ticket_id}/todos\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"task\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/tickets/{ticket_id}/todos', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'task': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tickets/{ticket_id}/todos', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"task\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/tickets/{ticket_id}/follow-ups": {"get": {"operationId": "list_follow_ups", "summary": "List scheduled follow-ups", "parameters": [{"in": "path", "name": "ticket_id", "schema": {"format": "uuid", "title": "Ticket Id", "type": "string"}, "required": true}, {"in": "query", "name": "include_done", "schema": {"default": false, "title": "Include Done", "type": "boolean"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/FollowUpsOut"}}}}}, "description": "Pending follow-ups (the agent wakes up and writes to the customer at `scheduled_at`); `include_done` adds executed and cancelled ones.", "tags": ["Tickets"], "security": [{"ApiAuth": []}], "x-zellwo-area": "tickets", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/tickets/{ticket_id}/follow-ups\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/tickets/{ticket_id}/follow-ups', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tickets/{ticket_id}/follow-ups', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "schedule_follow_up", "summary": "Schedule a follow-up", "parameters": [{"in": "path", "name": "ticket_id", "schema": {"format": "uuid", "title": "Ticket Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/OkOut"}}}}}, "description": "`scheduled_at` is ISO 8601 in the future; `reason` (max 500 chars) tells the agent what to follow up about. `channel` (`email` or `sms`) sends it that way instead of on the customer's own channel; keep SMS rare, it is intrusive and billed per part.", "tags": ["Tickets"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/FollowUpIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "tickets", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/tickets/{ticket_id}/follow-ups\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"reason\": \"string\", \"scheduled_at\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/tickets/{ticket_id}/follow-ups', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'reason': 'string', 'scheduled_at': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tickets/{ticket_id}/follow-ups', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"reason\": \"string\", \"scheduled_at\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/tickets/follow-ups/{task_id}": {"patch": {"operationId": "update_follow_up", "summary": "Reschedule or reword a pending follow-up", "parameters": [{"in": "path", "name": "task_id", "schema": {"title": "Task Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/OkOut"}}}}}, "tags": ["Tickets"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/FollowUpPatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "tickets", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/tickets/follow-ups/{task_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"reason\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/tickets/follow-ups/{task_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'reason': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tickets/follow-ups/{task_id}', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"reason\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "cancel_follow_up", "summary": "Cancel a pending follow-up", "parameters": [{"in": "path", "name": "task_id", "schema": {"title": "Task Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/OkOut"}}}}}, "tags": ["Tickets"], "security": [{"ApiAuth": []}], "x-zellwo-area": "tickets", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/tickets/follow-ups/{task_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/tickets/follow-ups/{task_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tickets/follow-ups/{task_id}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/tickets/{ticket_id}/merge": {"post": {"operationId": "merge_ticket", "summary": "Merge a ticket into another", "parameters": [{"in": "path", "name": "ticket_id", "schema": {"format": "uuid", "title": "Ticket Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TicketOut"}}}}}, "description": "Both tickets must belong to the same contact. Moves this ticket's messages (with their attachments and e-mail thread), open tasks, notes, CC addresses and todos onto `into`, or onto the ticket `into` was itself merged into, and closes this one; its pending bot follow-ups are cancelled and nothing is sent to the customer. Answers with the ticket that now holds the conversation; a refused merge is a `409` saying why.", "tags": ["Tickets"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/MergeIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "tickets", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/tickets/{ticket_id}/merge\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"loser_id\": \"string\", \"winner_id\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/tickets/{ticket_id}/merge', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'loser_id': 'string', 'winner_id': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tickets/{ticket_id}/merge', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"loser_id\": \"string\", \"winner_id\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/tickets/{ticket_id}/messages/{message_id}/translate": {"post": {"operationId": "translate_message", "summary": "Translate a customer message or an AI agent reply", "parameters": [{"in": "path", "name": "ticket_id", "schema": {"format": "uuid", "title": "Ticket Id", "type": "string"}, "required": true}, {"in": "path", "name": "message_id", "schema": {"format": "uuid", "title": "Message Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TranslationOut"}}}}}, "description": "Translates one customer message or AI agent reply on the ticket into `language` (an ISO 639 code, default your own UI language). The translation is stored on the message, so asking again for the same language is free; each new translation counts as AI usage (`translation`). Staff replies, internal notes and connector lines are a `404`; an unverified workspace without its own key is a `409`, a provider failure a `502`.", "tags": ["Tickets"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TranslateIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "tickets", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/tickets/{ticket_id}/messages/{message_id}/translate\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"language\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/tickets/{ticket_id}/messages/{message_id}/translate', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'language': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tickets/{ticket_id}/messages/{message_id}/translate', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"language\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/tickets/{ticket_id}/macros": {"get": {"operationId": "list_ticket_macros", "summary": "List a ticket's macros, filled in", "parameters": [{"in": "path", "name": "ticket_id", "schema": {"format": "uuid", "title": "Ticket Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/RenderedMacroOut"}, "title": "Response", "type": "array"}}}}}, "description": "The active macros offered on this ticket to you (your personal ones first, then the shared ones; its department's and the all-department ones) with the placeholders filled in for its customer, its reference and you.", "tags": ["Macros"], "security": [{"ApiAuth": []}], "x-zellwo-area": "tickets", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/tickets/{ticket_id}/macros\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/tickets/{ticket_id}/macros', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tickets/{ticket_id}/macros', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/tickets/{ticket_id}/approvals": {"get": {"operationId": "list_ticket_approvals", "summary": "List a ticket's open connector approvals (admin)", "parameters": [{"in": "path", "name": "ticket_id", "schema": {"format": "uuid", "title": "Ticket Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/ApprovalOut"}, "title": "Response", "type": "array"}}}}}, "description": "The calls the AI agent asked to make on this ticket that wait for an admin (or are approved and still sending), oldest first.", "tags": ["Connectors"], "security": [{"ApiAuth": []}], "x-zellwo-area": "tickets", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/tickets/{ticket_id}/approvals\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/tickets/{ticket_id}/approvals', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tickets/{ticket_id}/approvals', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/tickets/{ticket_id}/approvals/{task_id}/approve": {"post": {"operationId": "approve_ticket_approval", "summary": "Approve a connector call (admin)", "parameters": [{"in": "path", "name": "ticket_id", "schema": {"format": "uuid", "title": "Ticket Id", "type": "string"}, "required": true}, {"in": "path", "name": "task_id", "schema": {"format": "uuid", "title": "Task Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/RunOut"}}}}}, "description": "Runs the call once now and posts the result on the ticket; `ok: false` means it was approved but the call failed (the approval is settled either way). Refused with a 422 when it expired or the tool changed since: deny it and let the assistant ask again.", "tags": ["Connectors"], "security": [{"ApiAuth": []}], "x-zellwo-area": "tickets", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/tickets/{ticket_id}/approvals/{task_id}/approve\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/tickets/{ticket_id}/approvals/{task_id}/approve', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tickets/{ticket_id}/approvals/{task_id}/approve', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/tickets/{ticket_id}/approvals/{task_id}/always-allow": {"post": {"operationId": "always_allow_ticket_approval", "summary": "Approve a connector call and always allow the tool (admin)", "parameters": [{"in": "path", "name": "ticket_id", "schema": {"format": "uuid", "title": "Ticket Id", "type": "string"}, "required": true}, {"in": "path", "name": "task_id", "schema": {"format": "uuid", "title": "Task Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/RunOut"}}}}}, "description": "Puts the tool (Fetch or Action) on allow in the approval's department, then approves this call.", "tags": ["Connectors"], "security": [{"ApiAuth": []}], "x-zellwo-area": "tickets", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/tickets/{ticket_id}/approvals/{task_id}/always-allow\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/tickets/{ticket_id}/approvals/{task_id}/always-allow', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tickets/{ticket_id}/approvals/{task_id}/always-allow', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/tickets/{ticket_id}/approvals/{task_id}/deny": {"post": {"operationId": "deny_ticket_approval", "summary": "Deny a connector call (admin)", "parameters": [{"in": "path", "name": "ticket_id", "schema": {"format": "uuid", "title": "Ticket Id", "type": "string"}, "required": true}, {"in": "path", "name": "task_id", "schema": {"format": "uuid", "title": "Task Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/OkOut"}}}}}, "description": "Cancels the approval; the reason is kept as an internal note on the ticket. Nothing is sent.", "tags": ["Connectors"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/DenyIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "tickets", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/tickets/{ticket_id}/approvals/{task_id}/deny\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"reason\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/tickets/{ticket_id}/approvals/{task_id}/deny', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'reason': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tickets/{ticket_id}/approvals/{task_id}/deny', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"reason\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/tickets/{ticket_id}/tools": {"get": {"operationId": "list_ticket_tools", "summary": "List the connector tools offered on a ticket (admin)", "parameters": [{"in": "path", "name": "ticket_id", "schema": {"format": "uuid", "title": "Ticket Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TicketToolsOut"}}}}}, "description": "The tools the ticket's department has on ask or allow, with the server-filled inputs this ticket gives them, what blocks any run of each and whether the customer proved who they are.", "tags": ["Connectors"], "security": [{"ApiAuth": []}], "x-zellwo-area": "tickets", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/tickets/{ticket_id}/tools\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/tickets/{ticket_id}/tools', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tickets/{ticket_id}/tools', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/tickets/{ticket_id}/tools/{connector_id}/{tool_name}/run": {"post": {"operationId": "run_ticket_tool", "summary": "Run a connector tool on a ticket (admin)", "parameters": [{"in": "path", "name": "ticket_id", "schema": {"format": "uuid", "title": "Ticket Id", "type": "string"}, "required": true}, {"in": "path", "name": "connector_id", "schema": {"format": "uuid", "title": "Connector Id", "type": "string"}, "required": true}, {"in": "path", "name": "tool_name", "schema": {"title": "Tool Name", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/RunOut"}}}}}, "description": "As the Actions menu: runs a tool the ticket's department has on ask or allow as you, within its per-ticket cap, posts the result as an internal thread line and returns it. While the sender is not proven, `checked_identity` is required (refused for an action over MCP: a person attests it), and a verified tool refuses whatever it says. The same key replays the first call; a key used for another call is a 422.", "tags": ["Connectors"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/RunToolIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "tickets", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/tickets/{ticket_id}/tools/{connector_id}/{tool_name}/run\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"arguments\": {}}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/tickets/{ticket_id}/tools/{connector_id}/{tool_name}/run', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'arguments': {}})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tickets/{ticket_id}/tools/{connector_id}/{tool_name}/run', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"arguments\": {}}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/macros": {"get": {"operationId": "list_macros", "summary": "List macros", "parameters": [{"in": "query", "name": "department_id", "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Department Id"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/MacroOut"}, "title": "Response", "type": "array"}}}}}, "description": "Your personal macros and the shared ones, inactive ones included, in list order; `department_id` keeps one department's own (the all-department ones have `department_id: null`).", "tags": ["Macros"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/macros\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/macros', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/macros', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "create_macro", "summary": "Create a macro", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MacroOut"}}}}}, "description": "A saved reply for one department, or for every department without `department_id`; `personal: true` keeps it to you, a shared one needs edit on the tickets area. A title already used there is a `422`.", "tags": ["Macros"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/MacroIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/macros\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"title\": \"string\", \"body\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/macros', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'title': 'string', 'body': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/macros', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"title\": \"string\", \"body\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/macros/{macro_id}": {"patch": {"operationId": "update_macro", "summary": "Update a macro", "parameters": [{"in": "path", "name": "macro_id", "schema": {"title": "Macro Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MacroOut"}}}}}, "description": "Changes only the fields sent; `department_id: null` offers it in every department, `personal` moves it between you and the team.", "tags": ["Macros"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/MacroPatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/macros/{macro_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"title\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/macros/{macro_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'title': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/macros/{macro_id}', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"title\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "delete_macro", "summary": "Delete a macro", "parameters": [{"in": "path", "name": "macro_id", "schema": {"title": "Macro Id", "type": "integer"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "description": "Removes your personal macro, or a shared one with edit on the tickets area; replies already sent from it are untouched.", "tags": ["Macros"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/macros/{macro_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/macros/{macro_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/macros/{macro_id}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/tasks": {"get": {"operationId": "list_tasks", "summary": "List tasks", "parameters": [{"in": "query", "name": "assignee", "schema": {"default": "", "title": "Assignee", "type": "string"}, "required": false}, {"in": "query", "name": "status", "schema": {"default": "", "title": "Status", "type": "string"}, "required": false}, {"in": "query", "name": "overdue", "schema": {"default": false, "title": "Overdue", "type": "boolean"}, "required": false}, {"in": "query", "name": "company", "schema": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Company"}, "required": false}, {"in": "query", "name": "enduser", "schema": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Enduser"}, "required": false}, {"in": "query", "name": "ticket", "schema": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Ticket"}, "required": false}, {"in": "query", "name": "department", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Department"}, "required": false}, {"in": "query", "name": "limit", "schema": {"default": 100, "title": "Limit", "type": "integer"}, "required": false}, {"in": "query", "name": "offset", "schema": {"default": 0, "minimum": 0, "title": "Offset", "type": "integer"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PagedTaskOut"}}}}}, "description": "Tasks in this workspace, soonest due first. `assignee` takes me, none, bot or a staff id; `overdue` keeps open tasks past their due time; `department` takes a department id or none.", "tags": ["Tasks"], "security": [{"ApiAuth": []}], "x-zellwo-area": "tasks", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/tasks\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/tasks', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tasks', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "create_task", "summary": "Create a task", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TaskOut"}}}}}, "description": "`company_id`, `enduser_id` and `ticket_id` are all optional. Leave `action` empty for a human task; set it to hand the task to the bot.", "tags": ["Tasks"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TaskIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "tasks", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/tasks\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"title\": \"\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/tasks', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'title': ''})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tasks', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"title\": \"\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/tasks/{task_id}": {"patch": {"operationId": "update_task", "summary": "Update a task", "parameters": [{"in": "path", "name": "task_id", "schema": {"format": "uuid", "title": "Task Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TaskOut"}}}}}, "description": "Only the fields you send change. Setting `status` to done records the completion; `department_id: null` clears the department. On the bot's question to the team, done or cancelled closes it (the ticket leaves On-Hold, the bot is not woken); reopening a settled one, `in_progress` or the AI agent as assignee is a `422`. Any status on an approval is a `422`.", "tags": ["Tasks"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TaskPatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "tasks", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/tasks/{task_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"title\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/tasks/{task_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'title': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tasks/{task_id}', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"title\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/tasks/{task_id}/complete": {"post": {"operationId": "complete_task", "summary": "Mark a task done", "parameters": [{"in": "path", "name": "task_id", "schema": {"format": "uuid", "title": "Task Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TaskOut"}}}}}, "description": "Records who finished it and when. An approval is a `422`: only Approve or Deny on the ticket settle it.", "tags": ["Tasks"], "security": [{"ApiAuth": []}], "x-zellwo-area": "tasks", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/tasks/{task_id}/complete\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/tasks/{task_id}/complete', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tasks/{task_id}/complete', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/tasks/{task_id}/reopen": {"post": {"operationId": "reopen_task", "summary": "Reopen a finished task", "parameters": [{"in": "path", "name": "task_id", "schema": {"format": "uuid", "title": "Task Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TaskOut"}}}}}, "description": "Back to open, with the completion record cleared. The bot's settled question to the team or an approval is a `422`: its outcome stands.", "tags": ["Tasks"], "security": [{"ApiAuth": []}], "x-zellwo-area": "tasks", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/tasks/{task_id}/reopen\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/tasks/{task_id}/reopen', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tasks/{task_id}/reopen', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/tasks/{task_id}/notes": {"get": {"operationId": "list_task_notes", "summary": "List the comments on a task", "parameters": [{"in": "path", "name": "task_id", "schema": {"format": "uuid", "title": "Task Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/NoteOut"}, "title": "Response", "type": "array"}}}}}, "description": "Newest first; a task's notes are its comments.", "tags": ["Tasks"], "security": [{"ApiAuth": []}], "x-zellwo-area": "tasks", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/tasks/{task_id}/notes\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/tasks/{task_id}/notes', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tasks/{task_id}/notes', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "create_task_note", "summary": "Comment on a task", "parameters": [{"in": "path", "name": "task_id", "schema": {"format": "uuid", "title": "Task Id", "type": "string"}, "required": true}], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/NoteOut"}}}}}, "tags": ["Tasks"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/NoteIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "tasks", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/tasks/{task_id}/notes\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"body\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/tasks/{task_id}/notes', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'body': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/tasks/{task_id}/notes', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"body\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/departments": {"get": {"operationId": "list_departments", "summary": "List departments", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/DepartmentOut"}, "title": "Response", "type": "array"}}}}}, "description": "Every department with its members' emails and category count. `is_default` marks where an enquiry goes when nothing says which department.", "tags": ["Departments"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/departments\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/departments', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/departments', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "create_department", "summary": "Create a department (admin)", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/DepartmentOut"}}}}}, "description": "Creates it with its fallback category `Other`, where its tickets land when nothing more specific fits. The workspace's first department becomes the default. Sending `assignment_strategy`, `max_open_per_agent`, `is_default` or `is_active` needs the owner level.", "tags": ["Departments"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/DepartmentIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/departments\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/departments', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'name': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/departments', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"name\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/departments/{department_id}": {"patch": {"operationId": "update_department", "summary": "Update a department (admin)", "parameters": [{"in": "path", "name": "department_id", "schema": {"title": "Department Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/DepartmentOut"}}}}}, "description": "Changes only the fields sent. `is_default: true` makes it the default and clears the previous one. Sending `assignment_strategy`, `max_open_per_agent`, `is_default` or `is_active` needs the owner level.", "tags": ["Departments"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/DepartmentPatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/departments/{department_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/departments/{department_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'name': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/departments/{department_id}', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"name\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "delete_department", "summary": "Delete a department (owner)", "parameters": [{"in": "path", "name": "department_id", "schema": {"title": "Department Id", "type": "integer"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "description": "Deletes it with its categories. A `409` for the default department and while any ticket sits in one of its categories.", "tags": ["Departments"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/departments/{department_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/departments/{department_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/departments/{department_id}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/departments/{department_id}/members": {"put": {"operationId": "set_department_members", "summary": "Replace a department's members (owner)", "parameters": [{"in": "path", "name": "department_id", "schema": {"title": "Department Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/DepartmentOut"}}}}}, "description": "Sets the complete member list from staff emails; an email that is no staff member is a `422` and changes nothing.", "tags": ["Departments"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/MembersIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PUT \"https://zellwo.zellwo.com/api/departments/{department_id}/members\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"emails\": []}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PUT', 'https://zellwo.zellwo.com/api/departments/{department_id}/members', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'emails': []})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/departments/{department_id}/members', { method: 'PUT', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"emails\": []}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/ticket-categories": {"get": {"operationId": "list_ticket_categories", "summary": "List ticket categories", "parameters": [{"in": "query", "name": "department_id", "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Department Id"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/CategoryOut"}, "title": "Response", "type": "array"}}}}}, "description": "Categories in tree order with their `label` (`Department \u203a Parent \u203a Child`), the bot switch (`chat_bot_initiated`), the routing gates and the default responses (subject, creation and hand-over messages). `is_fallback` marks each department's `Other`.", "tags": ["Ticket categories"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/ticket-categories\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/ticket-categories', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/ticket-categories', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "create_ticket_category", "summary": "Create a ticket category (admin)", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CategoryOut"}}}}}, "description": "A `parent_id` must be a category of the same department; names are unique under one parent.", "tags": ["Ticket categories"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CategoryIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/ticket-categories\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"string\", \"department_id\": 0}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/ticket-categories', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'name': 'string', 'department_id': 0})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/ticket-categories', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"name\": \"string\", \"department_id\": 0}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/ticket-categories/{category_id}": {"patch": {"operationId": "update_ticket_category", "summary": "Update a ticket category (admin)", "parameters": [{"in": "path", "name": "category_id", "schema": {"title": "Category Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CategoryOut"}}}}}, "description": "Changes only the fields sent. A department's fallback `Other` can be renamed and switched, never moved; a parent never sits below its child. Sending `department_id` needs the owner level.", "tags": ["Ticket categories"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CategoryPatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/ticket-categories/{category_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/ticket-categories/{category_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'name': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/ticket-categories/{category_id}', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"name\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "delete_ticket_category", "summary": "Delete a ticket category (owner)", "parameters": [{"in": "path", "name": "category_id", "schema": {"title": "Category Id", "type": "integer"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "description": "Deletes it with its subcategories; their tickets become uncategorised (unrouted). A `409` for a department's fallback `Other`.", "tags": ["Ticket categories"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/ticket-categories/{category_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/ticket-categories/{category_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/ticket-categories/{category_id}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/support-settings": {"get": {"operationId": "get_support_settings", "summary": "Get the support settings", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SupportSettingsOut"}}}}}, "description": "The status timers (hours a ticket rests Pending, Solved or On-Hold before it moves on) and the link template for a ticket's external reference.", "tags": ["Support settings"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/support-settings\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/support-settings', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/support-settings', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "patch": {"operationId": "update_support_settings", "summary": "Update the support settings (admin; owner for draft_only and routing)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SupportSettingsOut"}}}}}, "description": "Changes only the fields sent; `draft_only`, `routing_enabled` and `routing_turn_budget` need the owner. `reference_url_template` must be an https:// address with {reference} once, after the host, or a `422`.", "tags": ["Support settings"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SupportSettingsPatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/support-settings\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"pending_to_solved_hours\": 0}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/support-settings', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'pending_to_solved_hours': 0})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/support-settings', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"pending_to_solved_hours\": 0}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/ai-settings": {"get": {"operationId": "get_ai_settings", "summary": "Get the AI settings (owner)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AiSettingsOut"}}}}}, "description": "The models of both tiers ('' = platform default), whether an own OpenAI key is stored (never the key) and the AI behaviour switches.", "tags": ["AI settings"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/ai-settings\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/ai-settings', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/ai-settings', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "patch": {"operationId": "update_ai_settings", "summary": "Update the AI settings (owner)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AiSettingsOut"}}}}}, "description": "Changes only the fields sent. An unknown model or a key not starting with `sk-` is a `422`; `openai_api_key: ''` removes the key.", "tags": ["AI settings"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/AiSettingsPatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/ai-settings\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"model\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/ai-settings', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'model': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/ai-settings', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"model\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/staff-channels": {"get": {"operationId": "list_staff_channels", "summary": "List staff channels (owner)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/StaffChannelOut"}, "title": "Response", "type": "array"}}}}}, "description": "Where the bot asks each department's team: the Slack channel id, whether it is active, whether a token is stored and which departments use it.", "tags": ["Staff channels"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/staff-channels\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/staff-channels', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/staff-channels', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "create_staff_channel", "summary": "Create a staff channel (owner)", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/StaffChannelOut"}}}}}, "description": "A Slack channel and the bot token of the workspace's own Slack app (bot scopes chat:write, channels:read, groups:read, channels:history, groups:history, users:read), invited to the channel with `/invite @app`. Link it to a department in Settings \u203a Departments. Refused through MCP.", "tags": ["Staff channels"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/StaffChannelIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/staff-channels\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"string\", \"target\": \"string\", \"bot_token\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/staff-channels', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'name': 'string', 'target': 'string', 'bot_token': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/staff-channels', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"name\": \"string\", \"target\": \"string\", \"bot_token\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/staff-channels/{channel_id}": {"patch": {"operationId": "update_staff_channel", "summary": "Update a staff channel (owner)", "parameters": [{"in": "path", "name": "channel_id", "schema": {"title": "Channel Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/StaffChannelOut"}}}}}, "description": "Changes only the fields sent; a new `bot_token` replaces the stored one. Refused through MCP.", "tags": ["Staff channels"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/StaffChannelPatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/staff-channels/{channel_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/staff-channels/{channel_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'name': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/staff-channels/{channel_id}', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"name\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "delete_staff_channel", "summary": "Delete a staff channel (owner)", "parameters": [{"in": "path", "name": "channel_id", "schema": {"title": "Channel Id", "type": "integer"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "description": "Its departments go back to handing tickets over instead of asking; open questions on it run out under the standard rule. Refused through MCP.", "tags": ["Staff channels"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/staff-channels/{channel_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/staff-channels/{channel_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/staff-channels/{channel_id}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/staff-channels/{channel_id}/test": {"post": {"operationId": "test_staff_channel", "summary": "Test a staff channel (owner)", "parameters": [{"in": "path", "name": "channel_id", "schema": {"title": "Channel Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/StaffChannelTestOut"}}}}}, "description": "Checks the stored token with Slack (`auth.test`) and that the bot can see the channel (`conversations.info`); posts nothing.", "tags": ["Staff channels"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/staff-channels/{channel_id}/test\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/staff-channels/{channel_id}/test', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/staff-channels/{channel_id}/test', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/endusers/{enduser_id}": {"get": {"operationId": "get_enduser", "summary": "Get an end user", "parameters": [{"in": "path", "name": "enduser_id", "schema": {"format": "uuid", "title": "Enduser Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/EnduserDetailOut"}}}}}, "description": "The contact with CRM status, company, profile, notes for the bot (`customer_information.$bot_notes`) and counts; use list_events with `enduser_id` for the timeline.", "tags": ["End Users"], "security": [{"ApiAuth": []}], "x-zellwo-area": "contacts", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/endusers/{enduser_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/endusers/{enduser_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/endusers/{enduser_id}', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "patch": {"operationId": "update_enduser", "summary": "Update contact fields", "parameters": [{"in": "path", "name": "enduser_id", "schema": {"format": "uuid", "title": "Enduser Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/EnduserDetailOut"}}}}}, "description": "Only the fields present in the body change. `properties` is validated against the active contact definitions; a bad value is a 422 and nothing is saved. `blocked: true` also moves the contact's tickets that are not Solved or Closed to Spam; `marketing_opt_out: true` unsubscribes the contact from every campaign and closes its open enrolment, false resubscribes.", "tags": ["End Users"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/EnduserPatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "contacts", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/endusers/{enduser_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"first_name\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/endusers/{enduser_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'first_name': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/endusers/{enduser_id}', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"first_name\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "delete_enduser", "summary": "Delete an end user (admin)", "parameters": [{"in": "path", "name": "enduser_id", "schema": {"format": "uuid", "title": "Enduser Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "description": "Permanent: removes the contact with every ticket, message, attachment file, task, note, event, CRM status history, campaign enrolment and logged email; their company stays.", "tags": ["End Users"], "security": [{"ApiAuth": []}], "x-zellwo-area": "contacts", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/endusers/{enduser_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/endusers/{enduser_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/endusers/{enduser_id}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/endusers": {"get": {"operationId": "list_endusers", "summary": "List end users (contacts)", "parameters": [{"in": "query", "name": "q", "schema": {"default": "", "title": "Q", "type": "string"}, "required": false}, {"in": "query", "name": "stage", "schema": {"default": "", "title": "Stage", "type": "string"}, "required": false}, {"in": "query", "name": "status_id", "schema": {"default": "", "title": "Status Id", "type": "string"}, "required": false}, {"in": "query", "name": "since", "schema": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Since"}, "required": false}, {"in": "query", "name": "segment_id", "schema": {"default": "", "title": "Segment Id", "type": "string"}, "required": false}, {"in": "query", "name": "marketing_opt_out", "schema": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Marketing Opt Out"}, "required": false}, {"in": "query", "name": "limit", "schema": {"default": 100, "title": "Limit", "type": "integer"}, "required": false}, {"in": "query", "name": "offset", "schema": {"default": 0, "minimum": 0, "title": "Offset", "type": "integer"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PagedEnduserDetailOut"}}}}}, "description": "Contacts newest first. `q` matches name, email or phone; `stage` is a CRM stage (unaware, cold, interested, consideration, order_flow, active_customer, follow_up, slipping_away, churned, lost, stale, not_interested, not_a_buyer); `status_id` narrows to one CRM status; `since` keeps contacts created after that ISO timestamp; `segment_id` keeps only the members of that segment (list_segments for ids); `marketing_opt_out` keeps only the contacts who unsubscribed (true) or only those campaigns may still mail (false).", "tags": ["End Users"], "security": [{"ApiAuth": []}], "x-zellwo-area": "contacts", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/endusers\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/endusers', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/endusers', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/endusers/{enduser_id}/status": {"post": {"operationId": "set_enduser_status", "summary": "Move a contact to a CRM status", "parameters": [{"in": "path", "name": "enduser_id", "schema": {"format": "uuid", "title": "Enduser Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/EnduserDetailOut"}}}}}, "description": "A manual move: records CRM history in your name and a `crm_status_changed` event. Use list_crm_statuses for ids.", "tags": ["End Users"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/EnduserStatusIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "contacts", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/endusers/{enduser_id}/status\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"status_id\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/endusers/{enduser_id}/status', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'status_id': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/endusers/{enduser_id}/status', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"status_id\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/endusers/{enduser_id}/todos": {"get": {"operationId": "get_enduser_todos", "summary": "List a contact todo list (deprecated)", "parameters": [{"in": "path", "name": "enduser_id", "schema": {"format": "uuid", "title": "Enduser Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TodosOut"}}}}}, "description": "Deprecated, removed next release: the contact's notes for the bot (`customer_information.$bot_notes`, one fact per line), each line a pending task.\nRead them from get_enduser's `customer_information` instead.", "tags": ["End Users"], "security": [{"ApiAuth": []}], "x-zellwo-area": "contacts", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/endusers/{enduser_id}/todos\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/endusers/{enduser_id}/todos', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/endusers/{enduser_id}/todos', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "update_enduser_todo", "summary": "Add, update or delete a contact todo (deprecated)", "parameters": [{"in": "path", "name": "enduser_id", "schema": {"format": "uuid", "title": "Enduser Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TodosOut"}}}}}, "description": "Deprecated, removed next release: edits the contact's notes for the bot. `add` appends a line, `update` with `new_task` rewrites the line,\n`delete` or `status: completed` removes it; `task` is matched by exact text.", "tags": ["End Users"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TodoIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "contacts", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/endusers/{enduser_id}/todos\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"task\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/endusers/{enduser_id}/todos', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'task': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/endusers/{enduser_id}/todos', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"task\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/endusers/merge": {"post": {"operationId": "merge_endusers", "summary": "Merge two contacts", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MergeOut"}}}}}, "description": "Moves the loser's tickets, messages and events onto the winner, fills the winner's blank fields, deletes the loser. Refused when both have a verified identity that differs.", "tags": ["End Users"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/MergeIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "contacts", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/endusers/merge\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"loser_id\": \"string\", \"winner_id\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/endusers/merge', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'loser_id': 'string', 'winner_id': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/endusers/merge', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"loser_id\": \"string\", \"winner_id\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/endusers/{enduser_id}/profile": {"post": {"operationId": "run_enduser_profiling", "summary": "Run AI profiling on a contact", "parameters": [{"in": "path", "name": "enduser_id", "schema": {"format": "uuid", "title": "Enduser Id", "type": "string"}, "required": true}, {"in": "query", "name": "wait_seconds", "schema": {"default": 0, "description": "Block for up to this many seconds while profiling runs; 0 returns at once.", "maximum": 25, "minimum": 0, "title": "Wait Seconds", "type": "integer"}, "required": false, "description": "Block for up to this many seconds while profiling runs; 0 returns at once."}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProfilingOut"}}}}}, "description": "Researches the person on the web in the background (their company first when it has no profile). Poll get_enduser, or pass `wait_seconds` to block here until the run ends: `profile_status` becomes done or failed.", "tags": ["End Users"], "security": [{"ApiAuth": []}], "x-zellwo-area": "contacts", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/endusers/{enduser_id}/profile\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/endusers/{enduser_id}/profile', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/endusers/{enduser_id}/profile', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "patch": {"operationId": "set_enduser_profile", "summary": "Edit a contact profile text", "parameters": [{"in": "path", "name": "enduser_id", "schema": {"format": "uuid", "title": "Enduser Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/EnduserDetailOut"}}}}}, "tags": ["End Users"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProfileTextIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "contacts", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/endusers/{enduser_id}/profile\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"text\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/endusers/{enduser_id}/profile', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'text': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/endusers/{enduser_id}/profile', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"text\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/endusers/check": {"post": {"operationId": "check_contact", "summary": "Look up a contact by email or external id", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ContactCheckOut"}}}}}, "description": "Dry run of onboarding: the contact this resolves to with their AI profile, CRM stage, customer information and company, and whether either row would be created.\n\nGive `email`, `external_id` (the contact's id in your own system, matched before the address) or both; without either the call is a 422.\nNothing is written unless `profile` is true, which starts AI profiling for the company and then the person whenever theirs is idle or failed,\nand never for a run that is already going or done \u2014 `profiling_started` says whether one began here.\n`profiling_in_progress` is true while any run holds either row, a run this call just started included; `wait_seconds` (0-25, higher is a 422)\nblocks here until both runs end or the budget is spent, so the returned `profile` and `profile_data` are the finished ones \u2014 `profile_status`\ntells you whether it actually finished. Call before onboard_contact.", "tags": ["End Users"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ContactCheckIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "contacts", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/endusers/check\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\": \"\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/endusers/check', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'email': ''})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/endusers/check', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"email\": \"\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/endusers/onboard": {"post": {"operationId": "onboard_contact", "summary": "Find or create a contact and their company", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/OnboardOut"}}}}}, "description": "Creates the contact (names guessed from the address when not given) and, for a business domain, the company; links them and starts AI profiling (company first, then person) unless `profile` is false.", "tags": ["End Users"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/OnboardIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "contacts", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/endusers/onboard\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/endusers/onboard', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'email': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/endusers/onboard', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"email\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/endusers/{enduser_id}/draft-outreach": {"post": {"operationId": "draft_outreach", "summary": "Draft a first email with AI", "parameters": [{"in": "path", "name": "enduser_id", "schema": {"format": "uuid", "title": "Enduser Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/DraftOut"}}}}}, "description": "A cold-outreach subject and body written from the contact's and company's AI profiles under the workspace's `outreach` prompt. Takes a few seconds; nothing is sent.", "tags": ["End Users"], "security": [{"ApiAuth": []}], "x-zellwo-area": "contacts", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/endusers/{enduser_id}/draft-outreach\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/endusers/{enduser_id}/draft-outreach', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/endusers/{enduser_id}/draft-outreach', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/endusers/{enduser_id}/messages": {"post": {"operationId": "send_new_message", "summary": "Start a new conversation by email or SMS", "parameters": [{"in": "path", "name": "enduser_id", "schema": {"format": "uuid", "title": "Enduser Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SendMessageOut"}}}}}, "description": "Opens a ticket with your message as its first entry and sends it by email or SMS. `channel` defaults to email when the contact has an address, else SMS when the workspace has an SMS sender; SMS to a contact without a phone is a 422, SMS in a workspace without it a 409. `department_id` picks the department whose address, name and signature it goes out with (default: the sender's own department, else the workspace default); an unknown or inactive one is a 422. `emailed` is true only for an email; when nothing went out the ticket still exists and `reason` says why (usually: no channel configured).", "tags": ["End Users"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SendMessageIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "tickets", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/endusers/{enduser_id}/messages\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"subject\": \"string\", \"body\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/endusers/{enduser_id}/messages', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'subject': 'string', 'body': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/endusers/{enduser_id}/messages', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"subject\": \"string\", \"body\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/endusers/{enduser_id}/notes": {"get": {"operationId": "list_enduser_notes", "summary": "List the notes on a contact", "parameters": [{"in": "path", "name": "enduser_id", "schema": {"format": "uuid", "title": "Enduser Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/NoteOut"}, "title": "Response", "type": "array"}}}}}, "tags": ["End Users"], "security": [{"ApiAuth": []}], "x-zellwo-area": "contacts", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/endusers/{enduser_id}/notes\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/endusers/{enduser_id}/notes', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/endusers/{enduser_id}/notes', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "create_enduser_note", "summary": "Add a note to a contact", "parameters": [{"in": "path", "name": "enduser_id", "schema": {"format": "uuid", "title": "Enduser Id", "type": "string"}, "required": true}], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/NoteOut"}}}}}, "tags": ["End Users"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/NoteIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "contacts", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/endusers/{enduser_id}/notes\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"body\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/endusers/{enduser_id}/notes', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'body': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/endusers/{enduser_id}/notes', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"body\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/companies": {"get": {"operationId": "list_companies", "summary": "List companies", "parameters": [{"in": "query", "name": "q", "schema": {"default": "", "title": "Q", "type": "string"}, "required": false}, {"in": "query", "name": "tag", "schema": {"default": "", "title": "Tag", "type": "string"}, "required": false}, {"in": "query", "name": "limit", "schema": {"default": 100, "title": "Limit", "type": "integer"}, "required": false}, {"in": "query", "name": "offset", "schema": {"default": 0, "minimum": 0, "title": "Offset", "type": "integer"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PagedCompanyOut"}}}}}, "description": "Companies in this workspace, newest first; `q` matches name, domain, website, industry or a tag; `tag` keeps only companies carrying that tag.", "tags": ["Companies"], "security": [{"ApiAuth": []}], "x-zellwo-area": "companies", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/companies\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/companies', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/companies', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "create_company", "summary": "Create a company, or update it by external_id", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CompanyOut"}}}}, "200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CompanyOut"}}}}}, "description": "201 on create. With an `external_id` that already exists the company is updated instead (200). A taken website domain is a 409.", "tags": ["Companies"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CompanyIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "companies", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/companies\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/companies', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'name': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/companies', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"name\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/companies/{company_id}": {"get": {"operationId": "get_company", "summary": "Get a company", "parameters": [{"in": "path", "name": "company_id", "schema": {"format": "uuid", "title": "Company Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CompanyOut"}}}}}, "tags": ["Companies"], "security": [{"ApiAuth": []}], "x-zellwo-area": "companies", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/companies/{company_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/companies/{company_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/companies/{company_id}', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "patch": {"operationId": "update_company", "summary": "Update company fields", "parameters": [{"in": "path", "name": "company_id", "schema": {"format": "uuid", "title": "Company Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CompanyOut"}}}}}, "description": "Only the fields present in the body change; empty strings are ignored, except `owner_email` where \"\" clears the owner. `tags` replaces the whole list, so `[]` clears it.", "tags": ["Companies"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CompanyPatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "companies", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/companies/{company_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/companies/{company_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'name': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/companies/{company_id}', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"name\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "delete_company", "summary": "Delete a company", "parameters": [{"in": "path", "name": "company_id", "schema": {"format": "uuid", "title": "Company Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "description": "Linked end users are kept and simply lose the link.", "tags": ["Companies"], "security": [{"ApiAuth": []}], "x-zellwo-area": "companies", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/companies/{company_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/companies/{company_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/companies/{company_id}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/companies/{company_id}/endusers": {"get": {"operationId": "list_company_endusers", "summary": "List linked end users", "parameters": [{"in": "path", "name": "company_id", "schema": {"format": "uuid", "title": "Company Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/EnduserOut"}, "title": "Response", "type": "array"}}}}}, "tags": ["Companies"], "security": [{"ApiAuth": []}], "x-zellwo-area": "companies", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/companies/{company_id}/endusers\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/companies/{company_id}/endusers', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/companies/{company_id}/endusers', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "link_company_enduser", "summary": "Link an end user, creating them if needed", "parameters": [{"in": "path", "name": "company_id", "schema": {"format": "uuid", "title": "Company Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/EnduserOut"}}}}}, "description": "Resolves by `enduser_id`, else by email / phone / external_id (creating the end user when nobody matches), then links.", "tags": ["Companies"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/EnduserLinkIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "companies", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/companies/{company_id}/endusers\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"enduser_id\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/companies/{company_id}/endusers', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'enduser_id': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/companies/{company_id}/endusers', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"enduser_id\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/companies/{company_id}/endusers/{enduser_id}": {"delete": {"operationId": "unlink_company_enduser", "summary": "Unlink an end user", "parameters": [{"in": "path", "name": "company_id", "schema": {"format": "uuid", "title": "Company Id", "type": "string"}, "required": true}, {"in": "path", "name": "enduser_id", "schema": {"format": "uuid", "title": "Enduser Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "tags": ["Companies"], "security": [{"ApiAuth": []}], "x-zellwo-area": "companies", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/companies/{company_id}/endusers/{enduser_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/companies/{company_id}/endusers/{enduser_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/companies/{company_id}/endusers/{enduser_id}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/companies/merge": {"post": {"operationId": "merge_companies", "summary": "Merge two companies", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MergeOut"}}}}}, "description": "Moves the loser's contacts onto the winner, keeps the winner's facts, deletes the loser.", "tags": ["Companies"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/MergeIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "companies", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/companies/merge\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"loser_id\": \"string\", \"winner_id\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/companies/merge', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'loser_id': 'string', 'winner_id': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/companies/merge', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"loser_id\": \"string\", \"winner_id\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/companies/{company_id}/profile": {"post": {"operationId": "run_company_profiling", "summary": "Run AI profiling on a company", "parameters": [{"in": "path", "name": "company_id", "schema": {"format": "uuid", "title": "Company Id", "type": "string"}, "required": true}, {"in": "query", "name": "wait_seconds", "schema": {"default": 0, "description": "Block for up to this many seconds while profiling runs; 0 returns at once.", "maximum": 25, "minimum": 0, "title": "Wait Seconds", "type": "integer"}, "required": false, "description": "Block for up to this many seconds while profiling runs; 0 returns at once."}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProfilingOut"}}}}}, "description": "Researches the company on the web in the background; poll get_company until `profile_status` is done or failed, or pass `wait_seconds` to block here until the run ends.", "tags": ["Companies"], "security": [{"ApiAuth": []}], "x-zellwo-area": "companies", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/companies/{company_id}/profile\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/companies/{company_id}/profile', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/companies/{company_id}/profile', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "patch": {"operationId": "set_company_profile", "summary": "Edit a company profile text", "parameters": [{"in": "path", "name": "company_id", "schema": {"format": "uuid", "title": "Company Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CompanyOut"}}}}}, "tags": ["Companies"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProfileTextIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "companies", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/companies/{company_id}/profile\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"text\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/companies/{company_id}/profile', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'text': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/companies/{company_id}/profile', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"text\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/companies/{company_id}/notes": {"get": {"operationId": "list_company_notes", "summary": "List the notes on a company", "parameters": [{"in": "path", "name": "company_id", "schema": {"format": "uuid", "title": "Company Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/NoteOut"}, "title": "Response", "type": "array"}}}}}, "description": "Newest first, the system ones among them (an owner hand-over, a property change) carrying no author.", "tags": ["Companies"], "security": [{"ApiAuth": []}], "x-zellwo-area": "companies", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/companies/{company_id}/notes\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/companies/{company_id}/notes', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/companies/{company_id}/notes', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "create_company_note", "summary": "Add a note to a company", "parameters": [{"in": "path", "name": "company_id", "schema": {"format": "uuid", "title": "Company Id", "type": "string"}, "required": true}], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/NoteOut"}}}}}, "tags": ["Companies"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/NoteIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "companies", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/companies/{company_id}/notes\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"body\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/companies/{company_id}/notes', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'body': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/companies/{company_id}/notes', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"body\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/companies/{company_id}/timeline": {"get": {"operationId": "get_company_timeline", "summary": "The company timeline", "parameters": [{"in": "path", "name": "company_id", "schema": {"format": "uuid", "title": "Company Id", "type": "string"}, "required": true}, {"in": "query", "name": "limit", "schema": {"default": 50, "title": "Limit", "type": "integer"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/TimelineRowOut"}, "title": "Response", "type": "array"}}}}}, "description": "Everything that happened to the account, newest first: its contacts' events and tickets merged with the notes on the company.", "tags": ["Companies"], "security": [{"ApiAuth": []}], "x-zellwo-area": "companies", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/companies/{company_id}/timeline\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/companies/{company_id}/timeline', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/companies/{company_id}/timeline', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/property-definitions": {"get": {"operationId": "list_property_definitions", "summary": "List property definitions", "parameters": [{"in": "query", "name": "target", "schema": {"default": "", "title": "Target", "type": "string"}, "required": false}, {"in": "query", "name": "active_only", "schema": {"default": false, "title": "Active Only", "type": "boolean"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/PropertyDefinitionOut"}, "title": "Response", "type": "array"}}}}}, "description": "The workspace's own fields on companies, contacts and products, in display order. `target` keeps one of company / contact / product; `active_only` drops retired ones.", "tags": ["Property Definitions"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/property-definitions\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/property-definitions', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/property-definitions', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "create_property_definition", "summary": "Create a property definition (admin)", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PropertyDefinitionOut"}}}}}, "description": "`key` is how the value is addressed afterwards (`properties__<key>`, and the `properties` body of update_company); `(target, key)` must be unique.", "tags": ["Property Definitions"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/PropertyDefinitionIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/property-definitions\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"key\": \"string\", \"label\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/property-definitions', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'key': 'string', 'label': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/property-definitions', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"key\": \"string\", \"label\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/property-definitions/{definition_id}": {"patch": {"operationId": "update_property_definition", "summary": "Update a property definition (admin)", "parameters": [{"in": "path", "name": "definition_id", "schema": {"format": "uuid", "title": "Definition Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PropertyDefinitionOut"}}}}}, "description": "Only the fields present change. The key and the target are fixed; `is_active: false` retires the property and keeps every stored value.", "tags": ["Property Definitions"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/PropertyDefinitionPatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/property-definitions/{definition_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"label\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/property-definitions/{definition_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'label': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/property-definitions/{definition_id}', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"label\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/segments": {"get": {"operationId": "list_segments", "summary": "List segments", "parameters": [{"in": "query", "name": "active_only", "schema": {"default": false, "title": "Active Only", "type": "boolean"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/SegmentOut"}, "title": "Response", "type": "array"}}}}}, "description": "Saved rule lists over contacts, by name, each with the contacts it holds right now; `active_only` drops the paused ones.", "tags": ["Segments"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/segments\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/segments', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/segments', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "create_segment", "summary": "Create a segment", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SegmentOut"}}}}}, "description": "A name another segment already uses is a 409; a rule the engine refuses is a 422 naming the rule.", "tags": ["Segments"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SegmentIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/segments\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"string\", \"rules\": []}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/segments', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'name': 'string', 'rules': []})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/segments', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"name\": \"string\", \"rules\": []}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/segments/fields": {"get": {"operationId": "list_segment_fields", "summary": "List the fields and operators rules may use", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SegmentFieldsOut"}}}}}, "description": "Call this before writing rules: the field keys, their types, their select options, and which operators each type takes.", "tags": ["Segments"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/segments/fields\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/segments/fields', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/segments/fields', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/segments/test": {"post": {"operationId": "preview_segment", "summary": "Count the contacts a rule list matches", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SegmentPreviewOut"}}}}}, "description": "A dry run: nothing is saved. Returns the live member count and the rules as they would be stored.", "tags": ["Segments"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SegmentPreviewIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/segments/test\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"rules\": []}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/segments/test', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'rules': []})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/segments/test', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"rules\": []}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/segments/{segment_id}": {"get": {"operationId": "get_segment", "summary": "Get a segment", "parameters": [{"in": "path", "name": "segment_id", "schema": {"format": "uuid", "title": "Segment Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SegmentOut"}}}}}, "tags": ["Segments"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/segments/{segment_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/segments/{segment_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/segments/{segment_id}', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "patch": {"operationId": "update_segment", "summary": "Update a segment", "parameters": [{"in": "path", "name": "segment_id", "schema": {"format": "uuid", "title": "Segment Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SegmentOut"}}}}}, "description": "Only the fields present in the body change; `rules` replaces the whole list and `is_active: false` pauses the segment.", "tags": ["Segments"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SegmentPatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/segments/{segment_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/segments/{segment_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'name': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/segments/{segment_id}', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"name\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "delete_segment", "summary": "Delete a segment", "parameters": [{"in": "path", "name": "segment_id", "schema": {"format": "uuid", "title": "Segment Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "description": "The contacts are untouched; membership was never stored.", "tags": ["Segments"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/segments/{segment_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/segments/{segment_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/segments/{segment_id}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/campaigns": {"get": {"operationId": "list_campaigns", "summary": "List campaigns", "parameters": [{"in": "query", "name": "status", "schema": {"default": "", "title": "Status", "type": "string"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/CampaignOut"}, "title": "Response", "type": "array"}}}}}, "description": "Every campaign with its funnel, its spend and its templates; `status` keeps one of draft, learning, exploiting, paused, exhausted, archived.", "tags": ["Campaigns"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/campaigns\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/campaigns', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "create_campaign", "summary": "Create a campaign", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CampaignOut"}}}}}, "description": "The campaign starts as a draft: generate templates, read them, then activate. Without `department_id` it speaks for your own department, else the workspace default. A name another campaign already uses is a 409, an unknown goal event or inactive department a 422.", "tags": ["Campaigns"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CampaignIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/campaigns\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"string\", \"segment_id\": \"string\", \"goal_event\": \"string\", \"brief\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/campaigns', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'name': 'string', 'segment_id': 'string', 'goal_event': 'string', 'brief': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"name\": \"string\", \"segment_id\": \"string\", \"goal_event\": \"string\", \"brief\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/campaigns/goals": {"get": {"operationId": "list_campaign_goals", "summary": "List the event types a goal may use", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/CampaignGoalOut"}, "title": "Response", "type": "array"}}}}}, "description": "Call this before creating a campaign: the registered event types that still fire, any of which can be the conversion.", "tags": ["Campaigns"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/campaigns/goals\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/campaigns/goals', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/goals', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/campaigns/test": {"post": {"operationId": "preview_campaign", "summary": "Count the contacts a campaign would reach", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CampaignPreviewOut"}}}}}, "description": "A dry run: nothing is saved. `eligible` is the segment minus everyone the guardrails hold back (opted out, bounced on email, already in a campaign, holding an open ticket), `holdout` how many of them would be measured rather than mailed, `languages` how they split by the language they read (pick the campaign languages from it) and `no_company` how many have no company to research.", "tags": ["Campaigns"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CampaignPreviewIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/campaigns/test\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"segment_id\": \"string\", \"goal_event\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/campaigns/test', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'segment_id': 'string', 'goal_event': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/test', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"segment_id\": \"string\", \"goal_event\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/campaigns/{campaign_id}": {"get": {"operationId": "get_campaign", "summary": "Get a campaign", "parameters": [{"in": "path", "name": "campaign_id", "schema": {"format": "uuid", "title": "Campaign Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CampaignOut"}}}}}, "description": "The campaign with its funnel, its spend, its cost per conversion and every template's own figures.", "tags": ["Campaigns"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/campaigns/{campaign_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/campaigns/{campaign_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/{campaign_id}', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "patch": {"operationId": "update_campaign", "summary": "Update a campaign", "parameters": [{"in": "path", "name": "campaign_id", "schema": {"format": "uuid", "title": "Campaign Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CampaignOut"}}}}}, "description": "Only the fields present in the body change; the brief may be rewritten at any status and takes effect on the next enrolment. Never raise `budget` without asking the human.", "tags": ["Campaigns"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CampaignPatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/campaigns/{campaign_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/campaigns/{campaign_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'name': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/{campaign_id}', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"name\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "archive_campaign", "summary": "Archive a campaign", "parameters": [{"in": "path", "name": "campaign_id", "schema": {"format": "uuid", "title": "Campaign Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "description": "Archiving stops enrolment and deletes nothing: templates, enrolments and their tickets stay as the history.", "tags": ["Campaigns"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/campaigns/{campaign_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/campaigns/{campaign_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/{campaign_id}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/campaigns/{campaign_id}/discard": {"delete": {"operationId": "delete_campaign", "summary": "Delete a campaign that never ran", "parameters": [{"in": "path", "name": "campaign_id", "schema": {"format": "uuid", "title": "Campaign Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "description": "Deletes the campaign and its templates for good. Only for one that never enrolled anyone and is not running: anything else is a 409, archive it instead.", "tags": ["Campaigns"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/campaigns/{campaign_id}/discard\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/campaigns/{campaign_id}/discard', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/{campaign_id}/discard', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/campaigns/{campaign_id}/activate": {"post": {"operationId": "activate_campaign", "summary": "Activate a campaign", "parameters": [{"in": "path", "name": "campaign_id", "schema": {"format": "uuid", "title": "Campaign Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CampaignOut"}}}}}, "description": "Starts enrolling contacts wave by wave. Refused with a 409 when the channel has no outbound sender, when its department is inactive or, on email, has no address, when no template is active, when the campaign is archived, or when an SMS campaign is in AI mode.", "tags": ["Campaigns"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/campaigns/{campaign_id}/activate\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/campaigns/{campaign_id}/activate', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/{campaign_id}/activate', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/campaigns/{campaign_id}/pause": {"post": {"operationId": "pause_campaign", "summary": "Pause a campaign", "parameters": [{"in": "path", "name": "campaign_id", "schema": {"format": "uuid", "title": "Campaign Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CampaignOut"}}}}}, "description": "Stops new enrolments at once; the enrolments already open run to their goal window, and follow-ups falling due while the campaign is not running are cancelled. A campaign that is not running is a 409.", "tags": ["Campaigns"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/campaigns/{campaign_id}/pause\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/campaigns/{campaign_id}/pause', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/{campaign_id}/pause', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/campaigns/{campaign_id}/generate": {"post": {"operationId": "generate_campaign_templates", "summary": "Write campaign templates with AI", "parameters": [{"in": "path", "name": "campaign_id", "schema": {"format": "uuid", "title": "Campaign Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/CampaignTemplateOut"}, "title": "Response", "type": "array"}}}}}, "description": "One model call turns the brief into `count` new arms (1-6) written natively in `language`, each taking a different angle. `missing_languages: true` instead writes every campaign language that has no active template: three arms where enough contacts read it, else one. It bills the campaign's budget and takes a few seconds. Read them before activating.", "tags": ["Campaigns"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CampaignGenerateIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/campaigns/{campaign_id}/generate\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"count\": 3}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/campaigns/{campaign_id}/generate', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'count': 3})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/{campaign_id}/generate', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"count\": 3}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/campaigns/{campaign_id}/templates": {"get": {"operationId": "list_campaign_templates", "summary": "List campaign templates", "parameters": [{"in": "path", "name": "campaign_id", "schema": {"format": "uuid", "title": "Campaign Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/CampaignTemplateOut"}, "title": "Response", "type": "array"}}}}}, "description": "Every arm of the campaign, retired ones included, with the messages it sent and where its posterior stands.", "tags": ["Campaigns"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/campaigns/{campaign_id}/templates\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/campaigns/{campaign_id}/templates', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/{campaign_id}/templates', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "create_campaign_template", "summary": "Add a campaign template", "parameters": [{"in": "path", "name": "campaign_id", "schema": {"format": "uuid", "title": "Campaign Id", "type": "string"}, "required": true}], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CampaignTemplateOut"}}}}}, "description": "A new arm with a zero posterior; on a newsletter's first email a variant, the next free letter of A-C unless `variant` names one. Anything but plain text and the whitelisted merge fields, a fourth variant, a taken letter or a variant past step 1 or on outreach is a 422.", "tags": ["Campaigns"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CampaignTemplateIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/campaigns/{campaign_id}/templates\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/campaigns/{campaign_id}/templates', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'name': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/{campaign_id}/templates', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"name\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/campaigns/{campaign_id}/templates/{template_id}": {"patch": {"operationId": "update_campaign_template", "summary": "Update a campaign template", "parameters": [{"in": "path", "name": "campaign_id", "schema": {"format": "uuid", "title": "Campaign Id", "type": "string"}, "required": true}, {"in": "path", "name": "template_id", "schema": {"format": "uuid", "title": "Template Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CampaignTemplateOut"}}}}}, "description": "Editing the subject or body of an arm that has already been sent never rewrites history: the old arm is retired and the edited text comes back as a new arm with a zero posterior.", "tags": ["Campaigns"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CampaignTemplatePatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/campaigns/{campaign_id}/templates/{template_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/campaigns/{campaign_id}/templates/{template_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'name': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/{campaign_id}/templates/{template_id}', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"name\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "delete_campaign_template", "summary": "Delete a campaign template", "parameters": [{"in": "path", "name": "campaign_id", "schema": {"format": "uuid", "title": "Campaign Id", "type": "string"}, "required": true}, {"in": "path", "name": "template_id", "schema": {"format": "uuid", "title": "Template Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "description": "An arm that has sent something is only retired, so its enrolments keep their history; one that never sent is removed.", "tags": ["Campaigns"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/campaigns/{campaign_id}/templates/{template_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/campaigns/{campaign_id}/templates/{template_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/{campaign_id}/templates/{template_id}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/campaigns/{campaign_id}/templates/{template_id}/convert": {"post": {"operationId": "convert_campaign_template_html", "summary": "Convert the html blocks of a newsletter template to native blocks with AI", "parameters": [{"in": "path", "name": "campaign_id", "schema": {"format": "uuid", "title": "Campaign Id", "type": "string"}, "required": true}, {"in": "path", "name": "template_id", "schema": {"format": "uuid", "title": "Template Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CampaignTemplateOut"}}}}}, "description": "One model call per html block rebuilds it as native blocks in its place; the theme it suggests fills only the keys the template does not set. It bills the campaign's budget. A sent arm forks, as on update_campaign_template. No html block is a 409, markup the conversion refuses a 422.", "tags": ["Campaigns"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/campaigns/{campaign_id}/templates/{template_id}/convert\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/campaigns/{campaign_id}/templates/{template_id}/convert', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/{campaign_id}/templates/{template_id}/convert', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/campaigns/{campaign_id}/templates/generate-layout": {"post": {"operationId": "generate_newsletter_layout", "summary": "Write a newsletter issue from the brief with AI", "parameters": [{"in": "path", "name": "campaign_id", "schema": {"format": "uuid", "title": "Campaign Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/CampaignTemplateOut"}, "title": "Response", "type": "array"}}}}}, "description": "Writes email `step` in `language` from the brief, the knowledge base and the catalogue: on the email's current blocks when it has content, else on the library layout picked for the brief, whose theme comes with it; product cards are added. Email 1 is stored on variant A with the first suggested subject; a later email on its one template. It bills the campaign's budget and takes a few seconds. Read the result before activating.", "tags": ["Campaigns"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/NewsletterGenerateIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/campaigns/{campaign_id}/templates/generate-layout\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"language\": \"en\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/campaigns/{campaign_id}/templates/generate-layout', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'language': 'en'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/{campaign_id}/templates/generate-layout', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"language\": \"en\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/campaigns/{campaign_id}/variants": {"post": {"operationId": "generate_campaign_variant", "summary": "Write another variant of a newsletter's first email with AI", "parameters": [{"in": "path", "name": "campaign_id", "schema": {"format": "uuid", "title": "Campaign Id", "type": "string"}, "required": true}], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CampaignTemplateOut"}}}}}, "description": "One model call rewrites the main variant (A) of the first email in `language` from a different angle with a new subject, same blocks, stored as the next free letter (B, then C) with A's theme, so the bandit can A/B test them. A fourth variant, or a variant A without text, is a 409; it bills the campaign's budget.", "tags": ["Campaigns"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/NewsletterVariantIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/campaigns/{campaign_id}/variants\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"language\": \"en\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/campaigns/{campaign_id}/variants', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'language': 'en'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/{campaign_id}/variants', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"language\": \"en\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/campaigns/{campaign_id}/translate": {"post": {"operationId": "translate_campaign_emails", "summary": "Translate the emails a language lacks with AI", "parameters": [{"in": "path", "name": "campaign_id", "schema": {"format": "uuid", "title": "Campaign Id", "type": "string"}, "required": true}], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/CampaignTemplateOut"}, "title": "Response", "type": "array"}}}}}, "description": "Fills every email `language` lacks that the source has written (every ticked language without Email 1 when null) by translating the language with Email 1 and the most readers, same blocks, links, theme and variant letter; variants B and C only for a language with 300+ readers. They come back flagged recipe.written_by \"ai\" with recipe.translated_from until a person saves them. No language with Email 1 to translate from is a 409; it bills the campaign's budget, one model call per email.", "tags": ["Campaigns"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/NewsletterTranslateIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/campaigns/{campaign_id}/translate\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"language\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/campaigns/{campaign_id}/translate', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'language': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/{campaign_id}/translate', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"language\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/campaigns/{campaign_id}/templates/{template_id}/html": {"get": {"operationId": "render_campaign_template", "summary": "Render a newsletter template as the HTML a contact would get", "parameters": [{"in": "path", "name": "campaign_id", "schema": {"format": "uuid", "title": "Campaign Id", "type": "string"}, "required": true}, {"in": "path", "name": "template_id", "schema": {"format": "uuid", "title": "Template Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/RenderedTemplateOut"}}}}}, "description": "The template rendered for the segment's first contact (a blank one while the segment is empty) under its theme, with the layout's block errors and the lint warnings. Outreach is a 409.", "tags": ["Campaigns"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/campaigns/{campaign_id}/templates/{template_id}/html\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/campaigns/{campaign_id}/templates/{template_id}/html', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/{campaign_id}/templates/{template_id}/html', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/campaigns/{campaign_id}/images": {"post": {"operationId": "upload_campaign_image", "summary": "Upload a newsletter image", "parameters": [{"in": "path", "name": "campaign_id", "schema": {"format": "uuid", "title": "Campaign Id", "type": "string"}, "required": true}], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CampaignImageOut"}}}}}, "description": "Multipart, field `file`: a JPEG, PNG, GIF or WebP of at most 5 MB. The answer's `url` is public: put it in an image block. Anything else is a 422. From an MCP client use upload_campaign_image_from_url.", "tags": ["Campaigns"], "requestBody": {"content": {"multipart/form-data": {"schema": {"properties": {"file": {"format": "binary", "title": "File", "type": "string"}}, "required": ["file"], "title": "FileParams", "type": "object"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/campaigns/{campaign_id}/images\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/campaigns/{campaign_id}/images', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/{campaign_id}/images', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/campaigns/{campaign_id}/images/from-url": {"post": {"operationId": "upload_campaign_image_from_url", "summary": "Add a newsletter image from an image URL", "parameters": [{"in": "path", "name": "campaign_id", "schema": {"format": "uuid", "title": "Campaign Id", "type": "string"}, "required": true}], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CampaignImageOut"}}}}}, "description": "Give a public https image URL; Zellwo downloads it and stores it as upload_campaign_image does: a JPEG, PNG, GIF or WebP of at most 5 MB. The answer's `url` is public: put it in an image block. A non-http(s) or private address, a timeout, a file over 5 MB or one that is not an image is a 422.", "tags": ["Campaigns"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ImageUrlIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/campaigns/{campaign_id}/images/from-url\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"url\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/campaigns/{campaign_id}/images/from-url', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'url': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/{campaign_id}/images/from-url', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"url\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/campaigns/{campaign_id}/enrolments": {"get": {"operationId": "list_campaign_enrolments", "summary": "List campaign enrolments", "parameters": [{"in": "path", "name": "campaign_id", "schema": {"format": "uuid", "title": "Campaign Id", "type": "string"}, "required": true}, {"in": "query", "name": "outcome", "schema": {"default": "", "title": "Outcome", "type": "string"}, "required": false}, {"in": "query", "name": "limit", "schema": {"default": 100, "title": "Limit", "type": "integer"}, "required": false}, {"in": "query", "name": "offset", "schema": {"default": 0, "minimum": 0, "title": "Offset", "type": "integer"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PagedCampaignEnrolmentOut"}}}}}, "description": "One row per contact the campaign took, newest first. `outcome` keeps `open` (still running) or one close reason: goal, window, opted_out, bounced, paused, max_messages, not_interested, skipped, unresponsive (a newsletter contact who opened and clicked nothing by Email 3).", "tags": ["Campaigns"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/campaigns/{campaign_id}/enrolments\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/campaigns/{campaign_id}/enrolments', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/{campaign_id}/enrolments', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/campaigns/{campaign_id}/estimate": {"get": {"operationId": "estimate_campaign_cost", "summary": "Estimate what a campaign would cost", "parameters": [{"in": "path", "name": "campaign_id", "schema": {"format": "uuid", "title": "Campaign Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CampaignEstimateOut"}}}}}, "description": "What AI writing and company research would cost over the contacts the campaign can reach now, in the workspace currency; templates mode writes nothing per contact. An SMS campaign also prices its sending (`sending_total`, parts x price per part, not in `total`). Show it to the human before they switch to AI mode, turn research on or activate an SMS campaign.", "tags": ["Campaigns"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/campaigns/{campaign_id}/estimate\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/campaigns/{campaign_id}/estimate', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/{campaign_id}/estimate', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/campaigns/{campaign_id}/drafts": {"get": {"operationId": "list_campaign_drafts", "summary": "List the AI drafts waiting for approval", "parameters": [{"in": "path", "name": "campaign_id", "schema": {"format": "uuid", "title": "Campaign Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/CampaignDraftOut"}, "title": "Response", "type": "array"}}}}}, "description": "AI mode: the emails written to contacts that wait for a person, oldest first, each with what the model used. Until 20 drafts have been approved nothing sends on its own.", "tags": ["Campaigns"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/campaigns/{campaign_id}/drafts\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/campaigns/{campaign_id}/drafts', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/{campaign_id}/drafts', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/campaigns/{campaign_id}/drafts/{enrolment_id}/approve": {"post": {"operationId": "approve_campaign_draft", "summary": "Approve and send an AI draft", "parameters": [{"in": "path", "name": "campaign_id", "schema": {"format": "uuid", "title": "Campaign Id", "type": "string"}, "required": true}, {"in": "path", "name": "enrolment_id", "schema": {"format": "uuid", "title": "Enrolment Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CampaignEnrolmentOut"}}}}}, "description": "Only when the human explicitly asks. Sends the draft now, with `subject` and `body` replacing the written ones when given, and records who approved it. No draft waiting is a 404, one sent or closed meanwhile a 409.", "tags": ["Campaigns"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CampaignDraftApproveIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/campaigns/{campaign_id}/drafts/{enrolment_id}/approve\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"subject\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/campaigns/{campaign_id}/drafts/{enrolment_id}/approve', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'subject': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/{campaign_id}/drafts/{enrolment_id}/approve', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"subject\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/campaigns/{campaign_id}/drafts/{enrolment_id}/regenerate": {"post": {"operationId": "regenerate_campaign_draft", "summary": "Rewrite an AI draft", "parameters": [{"in": "path", "name": "campaign_id", "schema": {"format": "uuid", "title": "Campaign Id", "type": "string"}, "required": true}, {"in": "path", "name": "enrolment_id", "schema": {"format": "uuid", "title": "Enrolment Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CampaignDraftOut"}}}}}, "description": "Only when the human explicitly asks. One more model call writes this contact's draft again, billed to the campaign's budget; nothing is sent.", "tags": ["Campaigns"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/campaigns/{campaign_id}/drafts/{enrolment_id}/regenerate\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/campaigns/{campaign_id}/drafts/{enrolment_id}/regenerate', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/{campaign_id}/drafts/{enrolment_id}/regenerate', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/campaigns/{campaign_id}/drafts/{enrolment_id}/skip": {"post": {"operationId": "skip_campaign_draft", "summary": "Skip an AI draft without sending", "parameters": [{"in": "path", "name": "campaign_id", "schema": {"format": "uuid", "title": "Campaign Id", "type": "string"}, "required": true}, {"in": "path", "name": "enrolment_id", "schema": {"format": "uuid", "title": "Enrolment Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CampaignEnrolmentOut"}}}}}, "description": "Only when the human explicitly asks. Closes the enrolment as `skipped` without sending; the template learns nothing.", "tags": ["Campaigns"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/campaigns/{campaign_id}/drafts/{enrolment_id}/skip\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/campaigns/{campaign_id}/drafts/{enrolment_id}/skip', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/campaigns/{campaign_id}/drafts/{enrolment_id}/skip', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/brand-kit": {"get": {"operationId": "get_brand_kit", "summary": "Get the brand kit (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/BrandKitOut"}}}}}, "description": "Logo, colours, font stack, footer lines and the Powered by switch the newsletters, the chat widget and customer emails share, defaults filled in.", "tags": ["Brand kit"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/brand-kit\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/brand-kit', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/brand-kit', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "put": {"operationId": "update_brand_kit", "summary": "Replace the brand kit (owner)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/BrandKitOut"}}}}}, "description": "Replaces the whole kit; an empty field falls back to its default. Colours are #rrggbb, font_stack system, serif or rounded; anything else is a 422.", "tags": ["Brand kit"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/BrandKitIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PUT \"https://zellwo.zellwo.com/api/brand-kit\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"logo_url\": \"\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PUT', 'https://zellwo.zellwo.com/api/brand-kit', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'logo_url': ''})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/brand-kit', { method: 'PUT', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"logo_url\": \"\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/newsletter-layouts": {"get": {"operationId": "list_newsletter_layouts", "summary": "List the newsletter layout library", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/NewsletterLayoutOut"}, "title": "Response", "type": "array"}}}}}, "description": "Starred layouts first, then the five built-ins (read-only, each with its own theme), then the ones people saved, oldest first.", "tags": ["Newsletter layouts"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/newsletter-layouts\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/newsletter-layouts', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/newsletter-layouts', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "create_newsletter_layout", "summary": "Save a layout to the library", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/NewsletterLayoutOut"}}}}}, "description": "Blocks as a template's layout and theme overrides as a template's theme; an empty name or a refused theme value is a 422.", "tags": ["Newsletter layouts"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/NewsletterLayoutIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/newsletter-layouts\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/newsletter-layouts', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'name': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/newsletter-layouts', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"name\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/newsletter-layouts/{layout_id}": {"put": {"operationId": "replace_newsletter_layout", "summary": "Replace a saved layout", "parameters": [{"in": "path", "name": "layout_id", "schema": {"format": "uuid", "title": "Layout Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/NewsletterLayoutOut"}}}}}, "description": "Replaces the blocks and the theme; a built-in is read-only, a 422.", "tags": ["Newsletter layouts"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/NewsletterLayoutReplaceIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PUT \"https://zellwo.zellwo.com/api/newsletter-layouts/{layout_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"layout\": []}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PUT', 'https://zellwo.zellwo.com/api/newsletter-layouts/{layout_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'layout': []})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/newsletter-layouts/{layout_id}', { method: 'PUT', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"layout\": []}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "delete_newsletter_layout", "summary": "Delete a saved layout", "parameters": [{"in": "path", "name": "layout_id", "schema": {"format": "uuid", "title": "Layout Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "description": "Removes a layout a person saved; a built-in cannot be deleted, a 422.", "tags": ["Newsletter layouts"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/newsletter-layouts/{layout_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/newsletter-layouts/{layout_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/newsletter-layouts/{layout_id}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/newsletter-layouts/{layout_id}/favorite": {"put": {"operationId": "set_newsletter_layout_favorite", "summary": "Star or unstar a template in the layout library", "parameters": [{"in": "path", "name": "layout_id", "schema": {"format": "uuid", "title": "Layout Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/NewsletterLayoutOut"}}}}}, "description": "A starred layout is listed first and preferred when generating a newsletter; built-ins can be starred too.", "tags": ["Newsletter layouts"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/NewsletterLayoutFavoriteIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PUT \"https://zellwo.zellwo.com/api/newsletter-layouts/{layout_id}/favorite\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"favorite\": true}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PUT', 'https://zellwo.zellwo.com/api/newsletter-layouts/{layout_id}/favorite', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'favorite': True})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/newsletter-layouts/{layout_id}/favorite', { method: 'PUT', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"favorite\": true}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/notes/{note_id}": {"delete": {"operationId": "delete_note", "summary": "Delete a note", "parameters": [{"in": "path", "name": "note_id", "schema": {"format": "uuid", "title": "Note Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/OkOut"}}}}}, "description": "The author may delete their own note; an admin or owner may delete anyone's.", "tags": ["Notes"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/notes/{note_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/notes/{note_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/notes/{note_id}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/events": {"get": {"operationId": "list_events", "summary": "List events", "parameters": [{"in": "query", "name": "event_type", "schema": {"default": "", "title": "Event Type", "type": "string"}, "required": false}, {"in": "query", "name": "enduser_id", "schema": {"default": "", "title": "Enduser Id", "type": "string"}, "required": false}, {"in": "query", "name": "q", "schema": {"default": "", "title": "Q", "type": "string"}, "required": false}, {"in": "query", "name": "since", "schema": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Since"}, "required": false}, {"in": "query", "name": "limit", "schema": {"default": 100, "title": "Limit", "type": "integer"}, "required": false}, {"in": "query", "name": "offset", "schema": {"default": 0, "minimum": 0, "title": "Offset", "type": "integer"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PagedEventOut"}}}}}, "description": "The event stream newest first: a contact's timeline with `enduser_id`, one event name with `event_type`, contacts by name/email/phone with `q`, and `since` (ISO timestamp) for a window. Each item carries `properties`, the event's own stored properties (the caller's extraData, `engagement_time_msec`, the first-pageview diagnostics).", "tags": ["Events"], "security": [{"ApiAuth": []}], "x-zellwo-area": "events", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/events\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/events', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/events', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "log_event", "summary": "Log an event for a contact", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/EventOut"}}}}}, "description": "Records the event (a registered system name or `custom:<name>`, registered on first sight) with an optional `note`, and lets CRM rules react to it.", "tags": ["Events"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/LogEventIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "events", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/events\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"enduser_id\": \"string\", \"event_type\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/events', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'enduser_id': 'string', 'event_type': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/events', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"enduser_id\": \"string\", \"event_type\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/event-types": {"get": {"operationId": "list_event_types", "summary": "List event types", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/EventTypeOut"}, "title": "Response", "type": "array"}}}}}, "description": "Every registered event name with its description, whether it is a system event, whether it is active (inactive events are stored but never trigger rules), how often it was seen in 30 days and how many rules listen.", "tags": ["Event Types"], "security": [{"ApiAuth": []}], "x-zellwo-area": "events", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/event-types\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/event-types', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/event-types', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "register_event_type", "summary": "Register a custom event type", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/EventTypeOut"}}}}}, "description": "The name is normalised to `custom:<snake_case>` (type it with or without the prefix). Use the name in log_event and CRM rules.", "tags": ["Event Types"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/EventTypeIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "events", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/event-types\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/event-types', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'name': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/event-types', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"name\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/event-types/{name}": {"patch": {"operationId": "update_event_type", "summary": "Describe or toggle an event type", "parameters": [{"in": "path", "name": "name", "schema": {"title": "Name", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/EventTypeOut"}}}}}, "description": "Names never change; system events can be described and disabled, not deleted.", "tags": ["Event Types"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/EventTypePatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "events", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/event-types/{name}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"description\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/event-types/{name}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'description': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/event-types/{name}', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"description\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "delete_event_type", "summary": "Delete a custom event type", "parameters": [{"in": "path", "name": "name", "schema": {"title": "Name", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "description": "Refused for system events and for custom events an active rule still listens to.", "tags": ["Event Types"], "security": [{"ApiAuth": []}], "x-zellwo-area": "events", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/event-types/{name}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/event-types/{name}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/event-types/{name}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/event-rules": {"get": {"operationId": "list_event_rules", "summary": "List event transformation rules", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/EventRuleOut"}, "title": "Response", "type": "array"}}}}}, "description": "Rules that rename incoming widget/API events (e.g. a pageview whose URL contains /thank-you becomes `purchase`) before CRM rules see them.", "tags": ["Event Rules"], "security": [{"ApiAuth": []}], "x-zellwo-area": "events", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/event-rules\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/event-rules', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/event-rules', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "create_event_rule", "summary": "Create an event transformation rule", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/EventRuleOut"}}}}}, "description": "When an event of `source_event_type` (`*` = any) has `match_field` matching `match_value` (`match_key` for customer_information), it is recorded as `target_event_type`.", "tags": ["Event Rules"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/EventRuleIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "events", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/event-rules\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"string\", \"target_event_type\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/event-rules', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'name': 'string', 'target_event_type': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/event-rules', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"name\": \"string\", \"target_event_type\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/event-rules/{rule_id}": {"patch": {"operationId": "update_event_rule", "summary": "Update an event transformation rule", "parameters": [{"in": "path", "name": "rule_id", "schema": {"title": "Rule Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/EventRuleOut"}}}}}, "tags": ["Event Rules"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/EventRulePatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "events", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/event-rules/{rule_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/event-rules/{rule_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'name': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/event-rules/{rule_id}', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"name\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "delete_event_rule", "summary": "Delete an event transformation rule", "parameters": [{"in": "path", "name": "rule_id", "schema": {"title": "Rule Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "tags": ["Event Rules"], "security": [{"ApiAuth": []}], "x-zellwo-area": "events", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/event-rules/{rule_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/event-rules/{rule_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/event-rules/{rule_id}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/event-rules/{rule_id}/test": {"post": {"operationId": "test_event_rule", "summary": "Dry-run an event transformation rule", "parameters": [{"in": "path", "name": "rule_id", "schema": {"title": "Rule Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/EventRuleTestOut"}}}}}, "description": "Which of the last 100 events the caller sees the rule would have renamed (up to 20 shown). Nothing is changed.", "tags": ["Event Rules"], "security": [{"ApiAuth": []}], "x-zellwo-area": "events", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/event-rules/{rule_id}/test\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/event-rules/{rule_id}/test', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/event-rules/{rule_id}/test', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/crm/statuses": {"get": {"operationId": "list_crm_statuses", "summary": "List CRM statuses", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/StatusOut"}, "title": "Response", "type": "array"}}}}}, "description": "Every status with its stage, plus the names of the active rules that bring contacts in (`entering_rules`) and take them out (`leaving_rules`).", "tags": ["CRM"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/crm/statuses\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/crm/statuses', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/crm/statuses', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "create_crm_status", "summary": "Create a CRM status", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/StatusOut"}}}}}, "description": "`stage` gives the status its meaning for rules and prompts; `(stage, substatus)` must be unique.", "tags": ["CRM"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/StatusIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/crm/statuses\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"stage\": \"cold\", \"name\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/crm/statuses', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'stage': 'cold', 'name': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/crm/statuses', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"stage\": \"cold\", \"name\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/crm/statuses/{status_id}": {"patch": {"operationId": "update_crm_status", "summary": "Update a CRM status", "parameters": [{"in": "path", "name": "status_id", "schema": {"format": "uuid", "title": "Status Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/StatusOut"}}}}}, "tags": ["CRM"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/StatusPatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/crm/statuses/{status_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"stage\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/crm/statuses/{status_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'stage': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/crm/statuses/{status_id}', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"stage\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "delete_crm_status", "summary": "Delete a CRM status", "parameters": [{"in": "path", "name": "status_id", "schema": {"format": "uuid", "title": "Status Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"additionalProperties": true, "title": "Response", "type": "object"}}}}}, "description": "Contacts in it move to the default status and rules targeting it are removed; refused when it is the only default and contacts use it.", "tags": ["CRM"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/crm/statuses/{status_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/crm/statuses/{status_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/crm/statuses/{status_id}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/crm/statuses/{status_id}/migrate": {"post": {"operationId": "migrate_crm_status", "summary": "Move every contact of a status to another", "parameters": [{"in": "path", "name": "status_id", "schema": {"format": "uuid", "title": "Status Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MigrateOut"}}}}}, "description": "Bulk move with one history row per contact and no rule evaluation; the source status stays (rename it with PATCH, delete it with DELETE).", "tags": ["CRM"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/MigrateIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/crm/statuses/{status_id}/migrate\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"to_status_id\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/crm/statuses/{status_id}/migrate', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'to_status_id': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/crm/statuses/{status_id}/migrate', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"to_status_id\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/crm/rules": {"get": {"operationId": "list_crm_rules", "summary": "List CRM rules", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/RuleOut"}, "title": "Response", "type": "array"}}}}}, "description": "Event rules fire when `trigger_signal` happens; scheduled rules fire after `for_duration` measured from a status (`from_statuses`), an event (`since_event`, cancelled by `unless_event`) or inactivity.", "tags": ["CRM"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/crm/rules\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/crm/rules', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/crm/rules', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "create_crm_rule", "summary": "Create a CRM rule", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/RuleOut"}}}}}, "description": "`mode: event` needs a registered `trigger_signal` (see list_event_types). `mode: scheduled` needs `duration_amount` + `duration_unit` and a `time_anchor`: `status` (with `from_statuses`), `event` (with `since_event`, optional `unless_event`) or `inactivity`. Leave `name` empty to name it from the sentence. `conditions` are extra checks on the contact that must all hold: `{\"customer_information__step\": \"payment\"}`, `{\"customer_information__page_name__contains\": \"FAQ\"}`, `{\"company__isnull\": false}`.", "tags": ["CRM"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/RuleIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/crm/rules\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"to_status_id\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/crm/rules', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'to_status_id': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/crm/rules', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"to_status_id\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/crm/rules/{rule_id}": {"patch": {"operationId": "update_crm_rule", "summary": "Update a CRM rule", "parameters": [{"in": "path", "name": "rule_id", "schema": {"format": "uuid", "title": "Rule Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/RuleOut"}}}}}, "description": "Only the fields present change; the rest is kept from the rule as it is.", "tags": ["CRM"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/RulePatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/crm/rules/{rule_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"mode\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/crm/rules/{rule_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'mode': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/crm/rules/{rule_id}', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"mode\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "delete_crm_rule", "summary": "Delete a CRM rule", "parameters": [{"in": "path", "name": "rule_id", "schema": {"format": "uuid", "title": "Rule Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "tags": ["CRM"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/crm/rules/{rule_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/crm/rules/{rule_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/crm/rules/{rule_id}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/crm/rules/{rule_id}/test": {"post": {"operationId": "test_crm_rule", "summary": "Dry-run a CRM rule", "parameters": [{"in": "path", "name": "rule_id", "schema": {"format": "uuid", "title": "Rule Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/RuleTestOut"}}}}}, "description": "Which of the first 200 contacts the rule would move right now (up to 20 shown). Nothing is changed.", "tags": ["CRM"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/crm/rules/{rule_id}/test\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/crm/rules/{rule_id}/test', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/crm/rules/{rule_id}/test', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/crm/recipes": {"get": {"operationId": "list_rule_recipes", "summary": "List rule recipes not adopted yet", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/RecipeOut"}, "title": "Response", "type": "array"}}}}}, "description": "Ready-made rules Zellwo suggests (e.g. \"When purchase happens \u00b7 any stage \u2192 Customer\"); adopt one with adopt_recipe.", "tags": ["CRM"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/crm/recipes\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/crm/recipes', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/crm/recipes', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/crm/recipes/adopt": {"post": {"operationId": "adopt_recipe", "summary": "Adopt a rule recipe", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/RuleOut"}}}}}, "description": "Creates the recipe as an active rule pointed at this workspace's status for its stage.", "tags": ["CRM"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/AdoptIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/crm/recipes/adopt\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/crm/recipes/adopt', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'name': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/crm/recipes/adopt', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"name\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/crm/setup-defaults": {"post": {"operationId": "setup_default_crm_configuration", "summary": "Install the default statuses, rules and prompts (owner)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SetupOut"}}}}}, "description": "Idempotent: existing statuses, rules (by name) and prompts are kept.", "tags": ["CRM"], "security": [{"ApiAuth": []}], "x-zellwo-area": "marketing", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/crm/setup-defaults\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/crm/setup-defaults', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/crm/setup-defaults', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/prompts": {"get": {"operationId": "list_prompts", "summary": "List prompts", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/PromptOut"}, "title": "Response", "type": "array"}}}}}, "description": "Every prompt the AI uses: `company_main` (who you are), `department` (who speaks for one department), `eval_criteria` (how answers are judged: with a `department_id` for that department's replies, without one for the rest of the workspace), `crm_behavior` (per CRM status), `profiling`, `outreach`, `analytics_notes` (metric definitions `describe_schema` returns).", "tags": ["Prompts"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/prompts\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/prompts', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/prompts', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "create_prompt", "summary": "Create a prompt (admin)", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PromptOut"}}}}}, "description": "Only one active prompt per type (per CRM status for `crm_behavior`; per department for `department` and department `eval_criteria`); `todo_list` lines become the agent's checklist on new tickets.", "tags": ["Prompts"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/PromptIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/prompts\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"prompt_type\": \"company_main\", \"content\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/prompts', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'prompt_type': 'company_main', 'content': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/prompts', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"prompt_type\": \"company_main\", \"content\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/prompts/{prompt_id}": {"get": {"operationId": "get_prompt", "summary": "Get a prompt", "parameters": [{"in": "path", "name": "prompt_id", "schema": {"title": "Prompt Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PromptOut"}}}}}, "tags": ["Prompts"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/prompts/{prompt_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/prompts/{prompt_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/prompts/{prompt_id}', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "patch": {"operationId": "update_prompt", "summary": "Update a prompt (admin)", "parameters": [{"in": "path", "name": "prompt_id", "schema": {"title": "Prompt Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PromptOut"}}}}}, "tags": ["Prompts"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/PromptPatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/prompts/{prompt_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"prompt_type\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/prompts/{prompt_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'prompt_type': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/prompts/{prompt_id}', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"prompt_type\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "delete_prompt", "summary": "Delete a prompt (admin)", "parameters": [{"in": "path", "name": "prompt_id", "schema": {"title": "Prompt Id", "type": "integer"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "tags": ["Prompts"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/prompts/{prompt_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/prompts/{prompt_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/prompts/{prompt_id}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/prompts/{prompt_id}/preview": {"get": {"operationId": "preview_prompt_text", "summary": "Preview the assembled prompt", "parameters": [{"in": "path", "name": "prompt_id", "schema": {"title": "Prompt Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PromptPreviewOut"}}}}}, "description": "The full system prompt the model receives with this prompt in place, and whether this prompt actually made it in.", "tags": ["Prompts"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/prompts/{prompt_id}/preview\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/prompts/{prompt_id}/preview', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/prompts/{prompt_id}/preview', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/products": {"get": {"operationId": "list_products", "summary": "List products", "parameters": [{"in": "query", "name": "q", "schema": {"default": "", "title": "Q", "type": "string"}, "required": false}, {"in": "query", "name": "brand", "schema": {"default": "", "title": "Brand", "type": "string"}, "required": false}, {"in": "query", "name": "availability", "schema": {"default": "", "title": "Availability", "type": "string"}, "required": false}, {"in": "query", "name": "category", "schema": {"default": "", "title": "Category", "type": "string"}, "required": false}, {"in": "query", "name": "group_key", "schema": {"default": "", "title": "Group Key", "type": "string"}, "required": false}, {"in": "query", "name": "active_only", "schema": {"default": true, "title": "Active Only", "type": "boolean"}, "required": false}, {"in": "query", "name": "limit", "schema": {"default": 100, "title": "Limit", "type": "integer"}, "required": false}, {"in": "query", "name": "offset", "schema": {"default": 0, "minimum": 0, "title": "Offset", "type": "integer"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PagedProductOut"}}}}}, "description": "`q` matches title, description or SKU; `brand` is an exact match ignoring case, `availability` one of in_stock / out_of_stock / preorder / backorder, `category` a path prefix (\"Tyres > Winter\"), `group_key` the variants of one product; `active_only=false` also lists inactive rows. Workspace product properties filter as `prop_<key>=<value>` (one exact match each; a value the definition rejects is ignored).", "tags": ["Products"], "security": [{"ApiAuth": []}], "x-zellwo-area": "products", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/products\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/products', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/products', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "create_product", "summary": "Create a product", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProductOut"}}}}}, "description": "`attributes` are validated against the product property definitions; a bad value is a 422 and nothing is created.", "tags": ["Products"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProductIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "products", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/products\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"title\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/products', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'title': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/products', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"title\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/products/{product_id}": {"get": {"operationId": "get_product", "summary": "Get a product", "parameters": [{"in": "path", "name": "product_id", "schema": {"format": "uuid", "title": "Product Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProductOut"}}}}}, "description": "One product with its columns and typed `attributes`.", "tags": ["Products"], "security": [{"ApiAuth": []}], "x-zellwo-area": "products", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/products/{product_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/products/{product_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/products/{product_id}', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "patch": {"operationId": "update_product", "summary": "Update a product", "parameters": [{"in": "path", "name": "product_id", "schema": {"format": "uuid", "title": "Product Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProductOut"}}}}}, "description": "Only the fields present change; `attributes` are merged into the stored ones after validation (422 on a bad value, nothing saved).", "tags": ["Products"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProductPatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "products", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/products/{product_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"title\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/products/{product_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'title': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/products/{product_id}', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"title\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "delete_product", "summary": "Delete a product", "parameters": [{"in": "path", "name": "product_id", "schema": {"format": "uuid", "title": "Product Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "tags": ["Products"], "security": [{"ApiAuth": []}], "x-zellwo-area": "products", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/products/{product_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/products/{product_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/products/{product_id}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/catalogue-sources": {"get": {"operationId": "list_catalogue_sources", "summary": "List catalogue sources", "parameters": [{"in": "query", "name": "limit", "schema": {"default": 100, "title": "Limit", "type": "integer"}, "required": false}, {"in": "query", "name": "offset", "schema": {"default": 0, "minimum": 0, "title": "Offset", "type": "integer"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PagedCatalogueSourceOut"}}}}}, "description": "Every feed or platform this workspace imports products from, by name, each with its last run.", "tags": ["Catalogue sources"], "security": [{"ApiAuth": []}], "x-zellwo-area": "products", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/catalogue-sources\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/catalogue-sources', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/catalogue-sources', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "create_catalogue_source", "summary": "Create a catalogue source", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CatalogueSourceOut"}}}}}, "description": "A scheduled source runs for the first time on the next beat tick; call sync_catalogue_source to run it now. `config.path` is a 422: only a file uploaded in the settings sets it.", "tags": ["Catalogue sources"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CatalogueSourceIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "products", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/catalogue-sources\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"type\": \"string\", \"name\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/catalogue-sources', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'type': 'string', 'name': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/catalogue-sources', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"type\": \"string\", \"name\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/catalogue-sources/{source_id}": {"get": {"operationId": "get_catalogue_source", "summary": "Get a catalogue source", "parameters": [{"in": "path", "name": "source_id", "schema": {"format": "uuid", "title": "Source Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CatalogueSourceOut"}}}}}, "tags": ["Catalogue sources"], "security": [{"ApiAuth": []}], "x-zellwo-area": "products", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/catalogue-sources/{source_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/catalogue-sources/{source_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/catalogue-sources/{source_id}', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "patch": {"operationId": "update_catalogue_source", "summary": "Update a catalogue source", "parameters": [{"in": "path", "name": "source_id", "schema": {"format": "uuid", "title": "Source Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CatalogueSourceOut"}}}}}, "description": "Only the fields present change; `credentials: null` clears the stored credentials; `config.path` is a 422 and a config without it keeps the uploaded file.", "tags": ["Catalogue sources"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CatalogueSourcePatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "products", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/catalogue-sources/{source_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"type\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/catalogue-sources/{source_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'type': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/catalogue-sources/{source_id}', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"type\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "delete_catalogue_source", "summary": "Delete a catalogue source", "parameters": [{"in": "path", "name": "source_id", "schema": {"format": "uuid", "title": "Source Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "description": "The run log goes with it; its products stay and lose the link (`source_id` becomes null).", "tags": ["Catalogue sources"], "security": [{"ApiAuth": []}], "x-zellwo-area": "products", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/catalogue-sources/{source_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/catalogue-sources/{source_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/catalogue-sources/{source_id}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/catalogue-sources/preview": {"post": {"operationId": "preview_catalogue_feed", "summary": "Preview a product feed before creating its source", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/FeedPreviewOut"}}}}}, "description": "Reads at most 2 MB of the feed at `url` (basic auth `username`/`password`, or a bearer `token`), detects the format (google_feed, csv or json), and returns the first records, every key with its kind and fill rate, and a proposed mapping onto the product columns and the workspace's product properties. Nothing is stored; a `422` says in plain words why the feed cannot be read (a web page, a 401, XML that is no product feed, no record list).", "tags": ["Catalogue sources"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/FeedPreviewIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "products", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/catalogue-sources/preview\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"url\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/catalogue-sources/preview', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'url': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/catalogue-sources/preview', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"url\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/catalogue-sources/sync": {"post": {"operationId": "sync_catalogue_sources", "summary": "Sync every catalogue source now", "parameters": [], "responses": {"202": {"description": "Accepted", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SyncAllOut"}}}}}, "description": "Starts every active source whose last run is not still going (each one's `next_due_at` moves forward first); `queued` is how many. Without a task queue they run inline before the answer.", "tags": ["Catalogue sources"], "security": [{"ApiAuth": []}], "x-zellwo-area": "products", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/catalogue-sources/sync\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/catalogue-sources/sync', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/catalogue-sources/sync', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/catalogue-sources/{source_id}/sync": {"post": {"operationId": "sync_catalogue_source", "summary": "Sync a catalogue source now", "parameters": [{"in": "path", "name": "source_id", "schema": {"format": "uuid", "title": "Source Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CatalogueSyncOut"}}}}, "202": {"description": "Accepted", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/QueuedOut"}}}}}, "description": "Runs the source's connector: `202 {queued: true}` and the run appears in list_catalogue_syncs; without a task queue it runs inline and answers `200` with the run. An inactive source is a `409`: activate it first.", "tags": ["Catalogue sources"], "security": [{"ApiAuth": []}], "x-zellwo-area": "products", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/catalogue-sources/{source_id}/sync\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/catalogue-sources/{source_id}/sync', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/catalogue-sources/{source_id}/sync', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/catalogue-sources/{source_id}/check": {"post": {"operationId": "check_catalogue_source", "summary": "Check that a catalogue source can be read", "parameters": [{"in": "path", "name": "source_id", "schema": {"format": "uuid", "title": "Source Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SourceCheckOut"}}}}}, "description": "Reads one page of a platform source (the first 50 records at most), or previews a feed with the stored config and credentials, writing nothing: `records` seen and up to three `sample` records; a `422` carries the error the connector hit.", "tags": ["Catalogue sources"], "security": [{"ApiAuth": []}], "x-zellwo-area": "products", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/catalogue-sources/{source_id}/check\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/catalogue-sources/{source_id}/check', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/catalogue-sources/{source_id}/check', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/catalogue-sources/{source_id}/syncs": {"get": {"operationId": "list_catalogue_syncs", "summary": "List a catalogue source's runs", "parameters": [{"in": "path", "name": "source_id", "schema": {"format": "uuid", "title": "Source Id", "type": "string"}, "required": true}, {"in": "query", "name": "limit", "schema": {"default": 100, "title": "Limit", "type": "integer"}, "required": false}, {"in": "query", "name": "offset", "schema": {"default": 0, "minimum": 0, "title": "Offset", "type": "integer"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PagedCatalogueSyncOut"}}}}}, "description": "The source's runs, newest first: counts, status, the first errors and `unmapped_keys` (define the property, then sync again).", "tags": ["Catalogue sources"], "security": [{"ApiAuth": []}], "x-zellwo-area": "products", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/catalogue-sources/{source_id}/syncs\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/catalogue-sources/{source_id}/syncs', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/catalogue-sources/{source_id}/syncs', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/articles": {"get": {"operationId": "list_articles", "summary": "List knowledge base articles", "parameters": [{"in": "query", "name": "visible", "schema": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Visible"}, "required": false}, {"in": "query", "name": "public", "schema": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Public"}, "required": false}, {"in": "query", "name": "category", "schema": {"default": "", "title": "Category", "type": "string"}, "required": false}, {"in": "query", "name": "updated_since", "schema": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Updated Since"}, "required": false}, {"in": "query", "name": "q", "schema": {"default": "", "title": "Q", "type": "string"}, "required": false}, {"in": "query", "name": "limit", "schema": {"default": 100, "title": "Limit", "type": "integer"}, "required": false}, {"in": "query", "name": "offset", "schema": {"default": 0, "minimum": 0, "title": "Offset", "type": "integer"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PagedArticleOut"}}}}}, "description": "Articles with every translation, pinned first. `visible` false lists hidden drafts; `updated_since` keeps those changed at or after it;\n`q` matches words in any translation's title or text. A help-center mirror pulls `public=true&visible=true&updated_since=<last run>`.", "tags": ["Knowledge"], "security": [{"ApiAuth": []}], "x-zellwo-area": "knowledge", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/articles\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/articles', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/articles', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "create_article", "summary": "Create a knowledge base article", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ArticleOut"}}}}, "409": {"description": "Conflict", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ExistingArticleOut"}}}}}, "description": "Creates an article with at least one translation. Omit `visible` to keep it a hidden draft the bot does not read. Re-running a seed is safe:\nan `external_id` that already exists is a `409` whose `id` is the existing article, to update with `update_article` instead.", "tags": ["Knowledge"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ArticleIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "knowledge", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/articles\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"translations\": []}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/articles', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'translations': []})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/articles', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"translations\": []}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/articles/public": {"get": {"operationId": "list_public_articles", "summary": "Public help articles by embed key (no staff token)", "parameters": [{"in": "query", "name": "key", "schema": {"title": "Key", "type": "string"}, "required": true}, {"in": "query", "name": "category", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Category"}, "required": false}, {"in": "query", "name": "q", "schema": {"default": "", "title": "Q", "type": "string"}, "required": false}, {"in": "query", "name": "language", "schema": {"default": "", "title": "Language", "type": "string"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/PublicArticleOut"}, "title": "Response", "type": "array"}}}}}, "description": "What the widget's Help tab shows: public, visible, unexpired articles, one translation each in `language` (default: the browser's Accept-Language)\nwith a fallback to the default language and then the article's first. `category` narrows to one category slug ('' for uncategorised); `q` runs the\nHelp tab's keyword search (title and text, full-text in the language's configuration), up to 8 results. Needs the widget embed `key`, is refused from an origin outside the key's allowed origins,\nand is held to 60 requests a minute per IP. Service updates are never listed; a `language` that is not a language is a 422.", "tags": ["Knowledge"], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/articles/public\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/articles/public', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/articles/public', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/articles/{article_id}": {"get": {"operationId": "get_article", "summary": "Get a knowledge base article", "parameters": [{"in": "path", "name": "article_id", "schema": {"title": "Article Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ArticleOut"}}}}}, "description": "One article with every translation.", "tags": ["Knowledge"], "security": [{"ApiAuth": []}], "x-zellwo-area": "knowledge", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/articles/{article_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/articles/{article_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/articles/{article_id}', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "patch": {"operationId": "update_article", "summary": "Update a knowledge base article", "parameters": [{"in": "path", "name": "article_id", "schema": {"title": "Article Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ArticleOut"}}}}}, "description": "Changes only the fields sent. Each translation is upserted by `language`; a changed title or text is re-embedded for the search.", "tags": ["Knowledge"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ArticlePatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "knowledge", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/articles/{article_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"slug\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/articles/{article_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'slug': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/articles/{article_id}', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"slug\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "delete_article", "summary": "Delete a knowledge base article", "parameters": [{"in": "path", "name": "article_id", "schema": {"title": "Article Id", "type": "integer"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "description": "Deletes the article with every translation; the bot stops reading it at once. To only hide it, set `visible` false instead.", "tags": ["Knowledge"], "security": [{"ApiAuth": []}], "x-zellwo-area": "knowledge", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/articles/{article_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/articles/{article_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/articles/{article_id}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/knowledge/search": {"post": {"operationId": "search_knowledge", "summary": "Search the knowledge base as the bot does", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/SearchResultOut"}, "title": "Response", "type": "array"}}}}}, "description": "Exactly what the bot's search returns: live service updates first, then up to 3 visible articles in `language` closest to `query`, topped up from the\ndefault language. Texts are cut at 1,500 characters. A `language` that is not a language is a `422`. Changes nothing; allowed for read-only tokens.", "tags": ["Knowledge"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SearchIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "knowledge", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/knowledge/search\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"query\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/knowledge/search', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'query': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/knowledge/search', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"query\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/knowledge-gaps": {"get": {"operationId": "list_knowledge_gaps", "summary": "List knowledge gaps", "parameters": [{"in": "query", "name": "status", "schema": {"anyOf": [{"enum": ["drafted", "dismissed", "approved"], "type": "string"}, {"type": "null"}], "title": "Status"}, "required": false}, {"in": "query", "name": "limit", "schema": {"default": 100, "title": "Limit", "type": "integer"}, "required": false}, {"in": "query", "name": "offset", "schema": {"default": 0, "minimum": 0, "title": "Offset", "type": "integer"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PagedKnowledgeGapOut"}}}}}, "description": "Article suggestions, newest first: questions the bot asked the team, opened once the team answered. Each is drafted by AI at once: `drafted` (an article\nwaiting for approval; an empty `draft` is still being written or failed, retry with `draft_knowledge_gap`) or `dismissed` (a one-off decision);\n`approve_knowledge_gap` \u2192 `approved`; an approved update carries `draft.propagation`, the state of each other language of its article. Read each draft, approve the good ones.", "tags": ["Knowledge"], "security": [{"ApiAuth": []}], "x-zellwo-area": "knowledge", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/knowledge-gaps\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/knowledge-gaps', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/knowledge-gaps', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/knowledge-gaps/{gap_id}": {"patch": {"operationId": "update_knowledge_gap", "summary": "Dismiss a knowledge gap", "parameters": [{"in": "path", "name": "gap_id", "schema": {"title": "Gap Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/KnowledgeGapOut"}}}}}, "description": "`status: dismissed` drops a suggestion the base does not need; dismissing an approved one is a `409`.", "tags": ["Knowledge"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/KnowledgeGapPatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "knowledge", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/knowledge-gaps/{gap_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"status\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/knowledge-gaps/{gap_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'status': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/knowledge-gaps/{gap_id}', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"status\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/knowledge-gaps/{gap_id}/draft": {"post": {"operationId": "draft_knowledge_gap", "summary": "Draft an article from a knowledge gap with AI", "parameters": [{"in": "path", "name": "gap_id", "schema": {"title": "Gap Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/KnowledgeGapOut"}}}}}, "description": "One AI call reads the question, the team's answer and the 3 closest articles in any language with all their translations (hidden drafts included)\nand proposes `draft`: `{verdict: new | update | not_a_gap, article_id, translations: [{language, title, text}], edits: [{language, old, new}], public, reason, language, feedback}`.\n`update` returns `edits` in the article's lead language only (`language`: English when the article has it, else its first language), applied in code to that text;\n`translations` holds the resulting full text, and the other languages follow after approval. `new` writes one translation in the ticket's `language`. `not_a_gap` dismisses the gap; otherwise it stays `drafted`\nand nothing reaches the bot until `approve_knowledge_gap`. Every answered question is drafted once on its own; call this to retry or redraft.\n`note` redoes the stored draft with the note and every earlier one (no stored draft is a `409`); `draft.feedback` lists the notes given so far.\nA gap that is not `drafted` is a `409`. Takes a few seconds and is billed.", "tags": ["Knowledge"], "requestBody": {"content": {"application/json": {"schema": {"anyOf": [{"$ref": "#/components/schemas/KnowledgeGapDraftIn"}, {"type": "null"}]}}}, "required": false}, "security": [{"ApiAuth": []}], "x-zellwo-area": "knowledge", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/knowledge-gaps/{gap_id}/draft\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/knowledge-gaps/{gap_id}/draft', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/knowledge-gaps/{gap_id}/draft', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/knowledge-gaps/{gap_id}/approve": {"post": {"operationId": "approve_knowledge_gap", "summary": "Approve the draft of a knowledge gap", "parameters": [{"in": "path", "name": "gap_id", "schema": {"title": "Gap Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/KnowledgeGapOut"}}}}}, "description": "Writes the draft: a new article, or the update of `draft.article_id`, in every language the draft holds, visible to the bot at once and `public` as drafted.\nAn update is saved in its lead language and carried into every other language of the article by background jobs; `draft.propagation` tracks each\n(`queued`, `applied`, `preview`, `skipped`, `failed` with `error`), `text` holding the carried text. `preview: true` holds them for review with\n`update_knowledge_gap_language` or `apply_knowledge_gap_languages`. Only a `drafted` gap can be approved (`409` otherwise). Edit the article afterwards with `update_article`.", "tags": ["Knowledge"], "requestBody": {"content": {"application/json": {"schema": {"anyOf": [{"$ref": "#/components/schemas/KnowledgeGapApproveIn"}, {"type": "null"}]}}}, "required": false}, "security": [{"ApiAuth": []}], "x-zellwo-area": "knowledge", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/knowledge-gaps/{gap_id}/approve\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/knowledge-gaps/{gap_id}/approve', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/knowledge-gaps/{gap_id}/approve', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/knowledge-gaps/{gap_id}/languages/apply": {"post": {"operationId": "apply_knowledge_gap_languages", "summary": "Apply every previewed language of an approved knowledge gap", "parameters": [{"in": "path", "name": "gap_id", "schema": {"title": "Gap Id", "type": "integer"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/KnowledgeGapOut"}}}}}, "description": "Writes the `text` of every language of `draft.propagation` in `preview` into the article. None waiting is a `409`.", "tags": ["Knowledge"], "security": [{"ApiAuth": []}], "x-zellwo-area": "knowledge", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/knowledge-gaps/{gap_id}/languages/apply\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/knowledge-gaps/{gap_id}/languages/apply', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/knowledge-gaps/{gap_id}/languages/apply', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/knowledge-gaps/{gap_id}/languages/{language}": {"patch": {"operationId": "update_knowledge_gap_language", "summary": "Apply or skip a previewed language of an approved knowledge gap", "parameters": [{"in": "path", "name": "gap_id", "schema": {"title": "Gap Id", "type": "integer"}, "required": true}, {"in": "path", "name": "language", "schema": {"title": "Language", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/KnowledgeGapOut"}}}}}, "description": "For a language of `draft.propagation` in `preview`: `applied` writes its `text` into the article, `skipped` leaves the article as it is.\nA language not waiting for review is a `409`.", "tags": ["Knowledge"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/KnowledgeGapLanguagePatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-zellwo-area": "knowledge", "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/knowledge-gaps/{gap_id}/languages/{language}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"status\": \"applied\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/knowledge-gaps/{gap_id}/languages/{language}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'status': 'applied'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/knowledge-gaps/{gap_id}/languages/{language}', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"status\": \"applied\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/channels": {"get": {"operationId": "get_channels", "summary": "Get channel configuration (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChannelsOut"}}}}}, "description": "SMTP (outbound replies), IMAP (inbound mail) and SendGrid settings without secrets, the SMS channel, plus the widget embed key id.", "tags": ["Channels"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/channels\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/channels', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/channels', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/channels/smtp": {"put": {"operationId": "configure_smtp", "summary": "Configure outbound email (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChannelsOut"}}}}}, "description": "Tests the SMTP login first and refuses (422) when it fails. Replies to email tickets and new conversations are sent through it. Leave the secret empty to keep the stored one when the channel already exists.", "tags": ["Channels"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SmtpIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PUT \"https://zellwo.zellwo.com/api/channels/smtp\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email_address\": \"string\", \"smtp_host\": \"string\", \"smtp_username\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PUT', 'https://zellwo.zellwo.com/api/channels/smtp', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'email_address': 'string', 'smtp_host': 'string', 'smtp_username': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/channels/smtp', { method: 'PUT', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"email_address\": \"string\", \"smtp_host\": \"string\", \"smtp_username\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "remove_smtp", "summary": "Remove outbound email (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/DeletedOut"}}}}}, "tags": ["Channels"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/channels/smtp\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/channels/smtp', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/channels/smtp', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/channels/smtp/test": {"post": {"operationId": "test_smtp", "summary": "Test the SMTP connection (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ConnectionTestOut"}}}}}, "description": "Logs in with the stored SMTP credentials without sending anything.", "tags": ["Channels"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/channels/smtp/test\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/channels/smtp/test', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/channels/smtp/test', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/channels/imap": {"put": {"operationId": "configure_imap", "summary": "Configure inbound email (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChannelsOut"}}}}}, "description": "Tests the IMAP login first and refuses (422) when it fails. Unread mail in each of `mailboxes` becomes tickets, polled every two minutes; `after_poll` is seen, move (into `archive_mailbox`) or delete; `unseen_only=false` imports everything in the folder, deduplicated by Message-ID. `department_addresses_only` imports only mail addressed to an active department's email address and leaves the rest untouched; 422 while no department has one. Leave the secret empty to keep the stored one when the channel already exists.", "tags": ["Channels"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ImapIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PUT \"https://zellwo.zellwo.com/api/channels/imap\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"imap_host\": \"string\", \"imap_username\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PUT', 'https://zellwo.zellwo.com/api/channels/imap', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'imap_host': 'string', 'imap_username': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/channels/imap', { method: 'PUT', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"imap_host\": \"string\", \"imap_username\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "remove_imap", "summary": "Remove inbound email (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/DeletedOut"}}}}}, "tags": ["Channels"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/channels/imap\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/channels/imap', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/channels/imap', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/channels/imap/poll": {"post": {"operationId": "poll_imap", "summary": "Poll the IMAP mailbox now (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ImapPollOut"}}}}}, "description": "Runs one poll synchronously and returns its counts and errors; the same thing the two-minute task does. Use it to see why mail is not arriving.", "tags": ["Channels"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/channels/imap/poll\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/channels/imap/poll', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/channels/imap/poll', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/channels/imap/test": {"post": {"operationId": "test_imap", "summary": "Test the IMAP connection (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ConnectionTestOut"}}}}}, "tags": ["Channels"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/channels/imap/test\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/channels/imap/test', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/channels/imap/test', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/channels/outbound-settings": {"patch": {"operationId": "update_outbound_settings", "summary": "Change how replies are emailed (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChannelsOut"}}}}}, "description": "Signature, quoted history, open tracking, daily limit, and whether chat customers get a notification instead of the full reply.", "tags": ["Channels"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/OutboundSettingsIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/channels/outbound-settings\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"chat_reply_as_notification\": true}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/channels/outbound-settings', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'chat_reply_as_notification': True})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/channels/outbound-settings', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"chat_reply_as_notification\": true}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/channels/inbound-settings": {"patch": {"operationId": "update_inbound_settings", "summary": "Change how inbound mail is routed (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChannelsOut"}}}}}, "description": "Department and default category for new email tickets, whether the AI answers emails, and which Authentication-Results hosts prove a sender. `department_addresses_only` imports only mail addressed to an active department's email address and leaves the rest untouched; 422 while no department has one. `unthreaded_opens_new_ticket` makes an email that threads onto nothing open a new ticket instead of joining the sender's latest open one. `provider` picks the inbound row: custom_email (IMAP) or sendgrid.", "tags": ["Channels"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/InboundSettingsIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/channels/inbound-settings\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"provider\": \"custom_email\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/channels/inbound-settings', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'provider': 'custom_email'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/channels/inbound-settings', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"provider\": \"custom_email\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/channels/sendgrid-inbound": {"put": {"operationId": "configure_sendgrid_inbound", "summary": "Configure SendGrid (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChannelsOut"}}}}}, "description": "Stores the Parse address and returns the destination URL to paste into SendGrid, with \"POST the raw, full MIME message\" ticked. `public_key` is optional: the public key of a SendGrid webhook security policy; with one stored, a request without a fresh valid signature is refused. The webhook secret itself never leaves the server.", "tags": ["Channels"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SendGridInboundIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PUT \"https://zellwo.zellwo.com/api/channels/sendgrid-inbound\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"inbound_address\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PUT', 'https://zellwo.zellwo.com/api/channels/sendgrid-inbound', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'inbound_address': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/channels/sendgrid-inbound', { method: 'PUT', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"inbound_address\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "remove_sendgrid_inbound", "summary": "Remove SendGrid (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/DeletedOut"}}}}}, "description": "Drops the webhook credentials; SendGrid mail posted to the old URL is refused.", "tags": ["Channels"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/channels/sendgrid-inbound\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/channels/sendgrid-inbound', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/channels/sendgrid-inbound', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/channels/sendgrid-inbound/regenerate": {"post": {"operationId": "regenerate_sendgrid_inbound_secret", "summary": "Rotate the SendGrid inbound webhook URL (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChannelsOut"}}}}}, "description": "Issues a new destination URL. The old one stops accepting mail at once, so paste the new one into SendGrid.", "tags": ["Channels"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/channels/sendgrid-inbound/regenerate\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/channels/sendgrid-inbound/regenerate', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/channels/sendgrid-inbound/regenerate', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/channels/sendgrid": {"put": {"operationId": "configure_sendgrid", "summary": "Configure SendGrid sending (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChannelsOut"}}}}}, "description": "Checks the API key carries the Mail Send scope (`mail.send`) first and refuses (422) when it does not. `email_address`: A verified sender or an address on a domain authenticated in SendGrid. Leave the secret empty to keep the stored one when the channel already exists.", "tags": ["Channels"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SendGridIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PUT \"https://zellwo.zellwo.com/api/channels/sendgrid\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email_address\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PUT', 'https://zellwo.zellwo.com/api/channels/sendgrid', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'email_address': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/channels/sendgrid', { method: 'PUT', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"email_address\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "remove_sendgrid", "summary": "Remove SendGrid sending (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/DeletedOut"}}}}}, "tags": ["Channels"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/channels/sendgrid\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/channels/sendgrid', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/channels/sendgrid', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/channels/sendgrid/test": {"post": {"operationId": "test_sendgrid", "summary": "Test the SendGrid API key (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ConnectionTestOut"}}}}}, "description": "Asks SendGrid for the stored key's scopes without sending anything.", "tags": ["Channels"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/channels/sendgrid/test\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/channels/sendgrid/test', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/channels/sendgrid/test', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/channels/sendgrid/events": {"patch": {"operationId": "update_sendgrid_events", "summary": "Set the SendGrid Event Webhook verification key (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChannelsOut"}}}}}, "description": "The base64 key from SendGrid's Signed Event Webhook panel. With a key stored, unsigned delivery and bounce events are refused; send an empty string to accept unsigned ones again.", "tags": ["Channels"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SendGridEventsIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/channels/sendgrid/events\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"public_key\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/channels/sendgrid/events', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'public_key': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/channels/sendgrid/events', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"public_key\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/channels/sms": {"get": {"operationId": "get_sms", "summary": "Get the SMS channel (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SmsOut"}}}}}, "description": "The sender name, the daily limit and today's count; 404 until SMS is set up.", "tags": ["Channels"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/channels/sms\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/channels/sms', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/channels/sms', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "put": {"operationId": "configure_sms", "summary": "Configure SMS (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChannelsOut"}}}}}, "description": "SMS goes out through the platform gateway and is billed to the workspace per SMS part. `sender`: A sender name is 2-11 letters or digits with at least one letter, or 1-15 digits. Blank uses the platform sender; your own goes out as set at once, nothing approves it. `daily_limit`: Messages per day. Empty keeps the current limit; the first setup starts at 500. Refuses (422) an invalid sender, and while SMS is not enabled on this platform.", "tags": ["Channels"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SmsIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PUT \"https://zellwo.zellwo.com/api/channels/sms\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"sender\": \"\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PUT', 'https://zellwo.zellwo.com/api/channels/sms', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'sender': ''})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/channels/sms', { method: 'PUT', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"sender\": \"\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "remove_sms", "summary": "Remove SMS (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/DeletedOut"}}}}}, "description": "Nothing more goes out by SMS until it is set up again; SMS already billed stays billed.", "tags": ["Channels"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/channels/sms\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/channels/sms', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/channels/sms', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/channels/sms/test": {"post": {"operationId": "test_sms", "summary": "Check the SMS gateway and sender (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SmsTestOut"}}}}}, "description": "Asks the gateway whether it answers and has the stored sender registered, without sending anything; `success` is true whenever it answered, since any valid sender goes out.", "tags": ["Channels"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/channels/sms/test\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/channels/sms/test', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/channels/sms/test', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/channels/outbound-channel": {"post": {"operationId": "select_outbound_channel", "summary": "Choose which channel sends replies (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChannelsOut"}}}}}, "description": "Every reply follows this choice from now on. `provider` is custom_email (SMTP) or sendgrid, and must already be configured for sending.", "tags": ["Channels"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SelectOutboundIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/channels/outbound-channel\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"provider\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/channels/outbound-channel', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'provider': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/channels/outbound-channel', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"provider\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/connectors": {"get": {"operationId": "list_connectors", "summary": "List connectors (admin)", "parameters": [{"in": "query", "name": "limit", "schema": {"default": 100, "title": "Limit", "type": "integer"}, "required": false}, {"in": "query", "name": "offset", "schema": {"default": 0, "minimum": 0, "title": "Offset", "type": "integer"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PagedConnectorOut"}}}}}, "description": "The connections to the workspace's own systems whose Fetch and Action tools the AI agent and admins call, by name. The secret is never returned: `has_credentials` reports it.", "tags": ["Connectors"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/connectors\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/connectors', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/connectors', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "post": {"operationId": "create_connector", "summary": "Create a connector (admin)", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ConnectorOut"}}}}}, "description": "A new connection starts off: call test_connector to switch it on. `secret` is write-only.", "tags": ["Connectors"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ConnectorIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/connectors\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"string\", \"base_url\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/connectors', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'name': 'string', 'base_url': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/connectors', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"name\": \"string\", \"base_url\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/connectors/{connector_id}": {"get": {"operationId": "get_connector", "summary": "Get a connector (admin)", "parameters": [{"in": "path", "name": "connector_id", "schema": {"format": "uuid", "title": "Connector Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ConnectorOut"}}}}}, "tags": ["Connectors"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/connectors/{connector_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/connectors/{connector_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/connectors/{connector_id}', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "patch": {"operationId": "update_connector", "summary": "Update a connector (admin)", "parameters": [{"in": "path", "name": "connector_id", "schema": {"format": "uuid", "title": "Connector Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ConnectorOut"}}}}}, "description": "Only the fields sent change; without `secret` the stored one is kept while `auth` and the base URL's host stay the same (a new host needs the secret again). A new base URL or secret switches the connector off until test_connector passes again.", "tags": ["Connectors"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ConnectorPatch"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PATCH \"https://zellwo.zellwo.com/api/connectors/{connector_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PATCH', 'https://zellwo.zellwo.com/api/connectors/{connector_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'name': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/connectors/{connector_id}', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"name\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "delete_connector", "summary": "Delete a connector (admin)", "parameters": [{"in": "path", "name": "connector_id", "schema": {"format": "uuid", "title": "Connector Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "description": "Deletes its tools and department access; its call log stays for audit.", "tags": ["Connectors"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/connectors/{connector_id}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/connectors/{connector_id}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/connectors/{connector_id}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/connectors/check-settings": {"post": {"operationId": "check_connector_settings", "summary": "Test connection settings before saving them (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ConnectionCheckOut"}}}}}, "description": "GETs the typed base URL with the typed auth and changes nothing; with `connector_id` a blank secret uses the stored one, on the same host only.", "tags": ["Connectors"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ConnectionCheckIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/connectors/check-settings\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"string\", \"base_url\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/connectors/check-settings', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'name': 'string', 'base_url': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/connectors/check-settings', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"name\": \"string\", \"base_url\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/connectors/{connector_id}/test-connection": {"post": {"operationId": "test_connector", "summary": "Test a connection (admin)", "parameters": [{"in": "path", "name": "connector_id", "schema": {"format": "uuid", "title": "Connector Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ConnectionCheckOut"}}}}}, "description": "GETs the base URL with the stored auth, redirects off: a 2xx, 401 or 403 switches the connector on; a redirect or 404 only warns; a 5xx or transport failure is recorded as its last_error.", "tags": ["Connectors"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/connectors/{connector_id}/test-connection\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/connectors/{connector_id}/test-connection', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/connectors/{connector_id}/test-connection', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/connectors/{connector_id}/calls": {"get": {"operationId": "list_connector_calls", "summary": "List a connector's calls (admin)", "parameters": [{"in": "path", "name": "connector_id", "schema": {"format": "uuid", "title": "Connector Id", "type": "string"}, "required": true}, {"in": "query", "name": "is_test", "schema": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Test"}, "required": false}, {"in": "query", "name": "limit", "schema": {"default": 100, "title": "Limit", "type": "integer"}, "required": false}, {"in": "query", "name": "offset", "schema": {"default": 0, "minimum": 0, "title": "Offset", "type": "integer"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PagedCallOut"}}}}}, "description": "The call log, newest first: bot, approval, staff and test calls with outcome, status, the error in words and an excerpt of the answer. `is_test` keeps only builder test calls (true) or only real ones (false).", "tags": ["Connectors"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/connectors/{connector_id}/calls\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/connectors/{connector_id}/calls', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/connectors/{connector_id}/calls', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/connectors/{connector_id}/tools": {"get": {"operationId": "list_connector_tools", "summary": "List a connector's tools (admin)", "parameters": [{"in": "path", "name": "connector_id", "schema": {"format": "uuid", "title": "Connector Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/ToolSummaryOut"}, "title": "Response", "type": "array"}}}}}, "description": "Every Fetch and Action with its test status; a tool is bot_ready once tested in its current shape with When to use written.", "tags": ["Connectors"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/connectors/{connector_id}/tools\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/connectors/{connector_id}/tools', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/connectors/{connector_id}/tools', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/connectors/{connector_id}/tools/{tool_name}": {"get": {"operationId": "get_connector_tool", "summary": "Get a connector tool (admin)", "parameters": [{"in": "path", "name": "connector_id", "schema": {"format": "uuid", "title": "Connector Id", "type": "string"}, "required": true}, {"in": "path", "name": "tool_name", "schema": {"title": "Tool Name", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ToolOut"}}}}}, "description": "The stored spec, the description the bot reads, and the last raw answer to pick fields from.", "tags": ["Connectors"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/connectors/{connector_id}/tools/{tool_name}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/connectors/{connector_id}/tools/{tool_name}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/connectors/{connector_id}/tools/{tool_name}', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "put": {"operationId": "save_connector_tool", "summary": "Create or update a connector tool (admin)", "parameters": [{"in": "path", "name": "connector_id", "schema": {"format": "uuid", "title": "Connector Id", "type": "string"}, "required": true}, {"in": "path", "name": "tool_name", "schema": {"title": "Tool Name", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ToolOut"}}}}}, "description": "As the builder's Save tool: the fields sent replace the stored ones, the others (docs, fields, \u2026) are kept; the test stamp and examples stay, so a tool stays tested while its shape is the same (24 tools at most per connector). Then send_connector_tool (or save_connector_tool_example for an action), pick_connector_tool_fields, save_connector_tool_docs and set_connector_access. A broken spec is a 422 naming the rule, a kind change a 422, a concurrent edit a 409.", "tags": ["Connectors"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ToolSpecIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PUT \"https://zellwo.zellwo.com/api/connectors/{connector_id}/tools/{tool_name}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"kind\": \"fetch\", \"method\": \"GET\", \"path\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PUT', 'https://zellwo.zellwo.com/api/connectors/{connector_id}/tools/{tool_name}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'kind': 'fetch', 'method': 'GET', 'path': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/connectors/{connector_id}/tools/{tool_name}', { method: 'PUT', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"kind\": \"fetch\", \"method\": \"GET\", \"path\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}, "delete": {"operationId": "delete_connector_tool", "summary": "Delete a connector tool (admin)", "parameters": [{"in": "path", "name": "connector_id", "schema": {"format": "uuid", "title": "Connector Id", "type": "string"}, "required": true}, {"in": "path", "name": "tool_name", "schema": {"title": "Tool Name", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}}, "description": "Removes the tool and every department's access to it.", "tags": ["Connectors"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X DELETE \"https://zellwo.zellwo.com/api/connectors/{connector_id}/tools/{tool_name}\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('DELETE', 'https://zellwo.zellwo.com/api/connectors/{connector_id}/tools/{tool_name}', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/connectors/{connector_id}/tools/{tool_name}', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/connectors/{connector_id}/paste": {"post": {"operationId": "paste_connector_request", "summary": "Read a pasted request into a tool request (admin)", "parameters": [{"in": "path", "name": "connector_id", "schema": {"format": "uuid", "title": "Connector Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PasteOut"}}}}}, "description": "Reads a cURL command, a full URL or a JSON body into a method, path and inputs (path segments equal to a value become placeholders; auth headers are ignored) and suggests the request part of a spec. Stores nothing.", "tags": ["Connectors"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/PasteIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/connectors/{connector_id}/paste\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"paste\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/connectors/{connector_id}/paste', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'paste': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/connectors/{connector_id}/paste', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"paste\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/connectors/{connector_id}/tools/{tool_name}/send": {"post": {"operationId": "send_connector_tool", "summary": "Send a test call of a connector tool (admin)", "parameters": [{"in": "path", "name": "connector_id", "schema": {"format": "uuid", "title": "Connector Id", "type": "string"}, "required": true}, {"in": "path", "name": "tool_name", "schema": {"title": "Tool Name", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ToolOut"}}}}}, "description": "Calls your API once with the test values, as the builder's Send: every required and server-filled input needs one, an action needs `confirm: true`. A pass stores the tool tested with its example (the first test keeps the top-level keys) and returns the raw answer in last_answer; a failure is a 422 with the reason and stores nothing.", "tags": ["Connectors"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SendIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/connectors/{connector_id}/tools/{tool_name}/send\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"test_values\": {}}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/connectors/{connector_id}/tools/{tool_name}/send', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'test_values': {}})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/connectors/{connector_id}/tools/{tool_name}/send', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"test_values\": {}}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/connectors/{connector_id}/tools/{tool_name}/example": {"post": {"operationId": "save_connector_tool_example", "summary": "Store an action's example answer instead of a call (admin)", "parameters": [{"in": "path", "name": "connector_id", "schema": {"format": "uuid", "title": "Connector Id", "type": "string"}, "required": true}, {"in": "path", "name": "tool_name", "schema": {"title": "Tool Name", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ToolOut"}}}}}, "description": "For an action you would rather not send: stores the answer your API would give (at most 256 KB), raw (to pick fields from) and projected, and stamps the tool tested without any call. A Fetch is a 422.", "tags": ["Connectors"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ExampleIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/connectors/{connector_id}/tools/{tool_name}/example\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"example\": {}}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/connectors/{connector_id}/tools/{tool_name}/example', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'example': {}})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/connectors/{connector_id}/tools/{tool_name}/example', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"example\": {}}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/connectors/{connector_id}/tools/{tool_name}/fields": {"put": {"operationId": "pick_connector_tool_fields", "summary": "Pick a connector tool's kept fields (admin)", "parameters": [{"in": "path", "name": "connector_id", "schema": {"format": "uuid", "title": "Connector Id", "type": "string"}, "required": true}, {"in": "path", "name": "tool_name", "schema": {"title": "Tool Name", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ToolOut"}}}}}, "description": "Sets the kept and free-text fields. While the last raw answer is still at hand (last_answer) the example is re-projected and the test stamp kept; without it the tool needs another send.", "tags": ["Connectors"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/FieldsIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PUT \"https://zellwo.zellwo.com/api/connectors/{connector_id}/tools/{tool_name}/fields\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"fields\": []}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PUT', 'https://zellwo.zellwo.com/api/connectors/{connector_id}/tools/{tool_name}/fields', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'fields': []})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/connectors/{connector_id}/tools/{tool_name}/fields', { method: 'PUT', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"fields\": []}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/connectors/{connector_id}/tools/{tool_name}/docs": {"put": {"operationId": "save_connector_tool_docs", "summary": "Save a connector tool's docs (admin)", "parameters": [{"in": "path", "name": "connector_id", "schema": {"format": "uuid", "title": "Connector Id", "type": "string"}, "required": true}, {"in": "path", "name": "tool_name", "schema": {"title": "Tool Name", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ToolOut"}}}}}, "description": "When to use, a meaning per kept field (others are dropped) and How to answer; compiled into the description the bot reads, at most 1,500 characters.", "tags": ["Connectors"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ToolDocsIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PUT \"https://zellwo.zellwo.com/api/connectors/{connector_id}/tools/{tool_name}/docs\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"when\": \"\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PUT', 'https://zellwo.zellwo.com/api/connectors/{connector_id}/tools/{tool_name}/docs', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'when': ''})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/connectors/{connector_id}/tools/{tool_name}/docs', { method: 'PUT', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"when\": \"\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/connectors/{connector_id}/tools/{tool_name}/docs/draft": {"post": {"operationId": "draft_connector_tool_docs", "summary": "Draft a connector tool's docs with AI (admin)", "parameters": [{"in": "path", "name": "connector_id", "schema": {"format": "uuid", "title": "Connector Id", "type": "string"}, "required": true}, {"in": "path", "name": "tool_name", "schema": {"title": "Tool Name", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ToolDocsIn"}}}}}, "description": "Drafts When to use, field meanings and How to answer from the tool's inputs, kept fields and projected example (never the raw answer). Nothing is saved: read it, fix it, then save_connector_tool_docs. Ten drafts a minute per person; a model failure is a 502.", "tags": ["Connectors"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/connectors/{connector_id}/tools/{tool_name}/docs/draft\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/connectors/{connector_id}/tools/{tool_name}/docs/draft', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/connectors/{connector_id}/tools/{tool_name}/docs/draft', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/connectors/{connector_id}/access": {"get": {"operationId": "get_connector_access", "summary": "Get a connector's department access (admin)", "parameters": [{"in": "path", "name": "connector_id", "schema": {"format": "uuid", "title": "Connector Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AccessOut"}}}}}, "description": "Per row, each tool's bot permission: off, ask (a colleague approves each call) or allow (the bot calls it without approval). While routing is on the first row is Routing (id null): the bot's access while AI routing files a new conversation; then each active department.", "tags": ["Connectors"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/connectors/{connector_id}/access\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/connectors/{connector_id}/access', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/connectors/{connector_id}/access', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}, "put": {"operationId": "set_connector_access", "summary": "Set a tool's access in a department (admin)", "parameters": [{"in": "path", "name": "connector_id", "schema": {"format": "uuid", "title": "Connector Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AccessOut"}}}}}, "description": "Sets one tool's access in a department, or in the Routing row with department_id null (only while routing is on, else 404). off always works; ask and allow need a bot_ready tool; allow on an Action lets the bot run it without approval, a few times per ticket.", "tags": ["Connectors"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/AccessIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X PUT \"https://zellwo.zellwo.com/api/connectors/{connector_id}/access\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"department_id\": 0, \"tool\": \"string\", \"mode\": \"off\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('PUT', 'https://zellwo.zellwo.com/api/connectors/{connector_id}/access', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'department_id': 0, 'tool': 'string', 'mode': 'off'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/connectors/{connector_id}/access', { method: 'PUT', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"department_id\": 0, \"tool\": \"string\", \"mode\": \"off\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/analytics/ingest": {"post": {"operationId": "ingest_event", "summary": "Ingest one event", "parameters": [], "responses": {"204": {"description": "No Content"}}, "description": "Queue a single server-side event for the authenticated tenant.", "tags": ["Analytics"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/IngestEvent"}}}, "required": true}, "security": [{"IngestAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/analytics/ingest\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"event\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/analytics/ingest', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'event': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/analytics/ingest', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"event\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}, "/api/analytics/ingest/batch": {"post": {"operationId": "ingest_event_batch", "summary": "Ingest up to 100 events", "parameters": [], "responses": {"204": {"description": "No Content"}}, "description": "Queue a batch of events; the whole batch is validated before any event is queued.", "tags": ["Analytics"], "requestBody": {"content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/IngestEvent"}, "title": "Events", "type": "array"}}}, "required": true}, "security": [{"IngestAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/analytics/ingest/batch\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '[{\"event\": \"string\"}]'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/analytics/ingest/batch', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json=[{'event': 'string'}])\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/analytics/ingest/batch', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify([{\"event\": \"string\"}]) });\nconsole.log(response.status, await response.text());"}]}}, "/api/sql/schema": {"get": {"operationId": "describe_schema", "summary": "Describe the analytics SQL tables (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SqlSchemaOut"}}}}}, "description": "Call this before run_select. Lists every table of the workspace's analytics snapshot with its row count, its columns and their types (FKs are `<name>_id`) and up to 150 keys seen in each JSON column with how many rows carry each, plus the SQLite dialect notes, the workspace's analytics `notes` (its metric definitions: read them first) and its active `event_types`. `timezone` is the caller's zone and `utc_offset` its current offset for local days. A snapshot is served for at most 15 minutes after its build (rebuilt in the background once 10 minutes old), then deleted; `snapshot_built_at` says when it was built, and a 503 means it is being rebuilt: its detail says when to retry. The snapshot holds only what the caller's areas, departments and revenue flag allow.", "tags": ["Analytics SQL"], "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X GET \"https://zellwo.zellwo.com/api/sql/schema\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\""}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('GET', 'https://zellwo.zellwo.com/api/sql/schema', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/sql/schema', { method: 'GET', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}` } });\nconsole.log(response.status, await response.text());"}]}}, "/api/sql/select": {"post": {"operationId": "run_select", "summary": "Run one SQL SELECT over the analytics snapshot (admin)", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SelectOut"}}}}}, "description": "Runs one read-only SQLite SELECT (WITH, subqueries and window functions work) against the snapshot describe_schema lists, and returns `columns` and `rows`. At most 500 rows come back (`truncated` is true when there were more) and text cells are cut at 2,000 characters, so aggregate in SQL; a query is stopped after 5 s. A SQL error is a 422 whose detail ends with how to fix it. One query at a time and 30 calls a minute; the snapshot holds only what the caller's areas, departments and revenue flag allow.", "tags": ["Analytics SQL"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SelectIn"}}}, "required": true}, "security": [{"ApiAuth": []}], "x-codeSamples": [{"lang": "Shell", "label": "curl", "source": "curl -X POST \"https://zellwo.zellwo.com/api/sql/select\" \\\n  -H \"Authorization: Bearer $ZELLWO_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"sql\": \"string\"}'"}, {"lang": "Python", "label": "Python", "source": "import os\nimport requests\n\nresponse = requests.request('POST', 'https://zellwo.zellwo.com/api/sql/select', headers={'Authorization': f'Bearer {os.environ[\"ZELLWO_TOKEN\"]}'}, json={'sql': 'string'})\nprint(response.status_code, response.text)"}, {"lang": "JavaScript", "label": "JavaScript", "source": "const response = await fetch('https://zellwo.zellwo.com/api/sql/select', { method: 'POST', headers: { Authorization: `Bearer ${process.env.ZELLWO_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({\"sql\": \"string\"}) });\nconsole.log(response.status, await response.text());"}]}}}, "components": {"schemas": {"MeOut": {"properties": {"id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Id"}, "email": {"title": "Email", "type": "string"}, "first_name": {"title": "First Name", "type": "string"}, "last_name": {"title": "Last Name", "type": "string"}, "alias": {"title": "Alias", "type": "string"}, "level": {"title": "Level", "type": "string"}, "via": {"title": "Via", "type": "string"}, "read_only": {"title": "Read Only", "type": "boolean"}, "sees_revenue": {"title": "Sees Revenue", "type": "boolean"}, "uses_api": {"title": "Uses Api", "type": "boolean"}, "sees_all_records": {"title": "Sees All Records", "type": "boolean"}, "areas": {"additionalProperties": {"type": "string"}, "title": "Areas", "type": "object"}, "ticket_scope": {"title": "Ticket Scope", "type": "string"}, "preset": {"title": "Preset", "type": "string"}, "workspace": {"title": "Workspace", "type": "string"}, "host": {"title": "Host", "type": "string"}, "mcp_url": {"title": "Mcp Url", "type": "string"}}, "required": ["id", "email", "first_name", "last_name", "alias", "level", "via", "read_only", "sees_revenue", "uses_api", "sees_all_records", "areas", "ticket_scope", "preset", "workspace", "host", "mcp_url"], "title": "MeOut", "type": "object"}, "OverviewOut": {"properties": {"workspace": {"title": "Workspace", "type": "string"}, "url": {"title": "Url", "type": "string"}, "total_tickets": {"title": "Total Tickets", "type": "integer"}, "tickets_by_status": {"additionalProperties": {"type": "integer"}, "title": "Tickets By Status", "type": "object"}, "total_endusers": {"title": "Total Endusers", "type": "integer"}, "total_companies": {"title": "Total Companies", "type": "integer"}, "statuses": {"items": {"$ref": "#/components/schemas/StatusSummary"}, "title": "Statuses", "type": "array"}, "active_rules": {"title": "Active Rules", "type": "integer"}, "event_types": {"title": "Event Types", "type": "integer"}, "smtp_configured": {"title": "Smtp Configured", "type": "boolean"}, "imap_configured": {"title": "Imap Configured", "type": "boolean"}, "outbound_email_provider": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Outbound Email Provider"}, "sms_configured": {"title": "Sms Configured", "type": "boolean"}, "embed_key_created": {"title": "Embed Key Created", "type": "boolean"}, "mcp_url": {"title": "Mcp Url", "type": "string"}}, "required": ["workspace", "url", "total_tickets", "tickets_by_status", "total_endusers", "total_companies", "statuses", "active_rules", "event_types", "smtp_configured", "imap_configured", "outbound_email_provider", "sms_configured", "embed_key_created", "mcp_url"], "title": "OverviewOut", "type": "object"}, "StatusSummary": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "stage": {"title": "Stage", "type": "string"}, "is_default": {"title": "Is Default", "type": "boolean"}, "endusers": {"title": "Endusers", "type": "integer"}}, "required": ["id", "name", "stage", "is_default", "endusers"], "title": "StatusSummary", "type": "object"}, "GettingStartedOut": {"properties": {"steps": {"items": {"$ref": "#/components/schemas/SetupStepOut"}, "title": "Steps", "type": "array"}, "done_count": {"title": "Done Count", "type": "integer"}, "required_total": {"title": "Required Total", "type": "integer"}}, "required": ["steps", "done_count", "required_total"], "title": "GettingStartedOut", "type": "object"}, "SetupStepOut": {"properties": {"key": {"title": "Key", "type": "string"}, "title": {"title": "Title", "type": "string"}, "line": {"title": "Line", "type": "string"}, "url": {"title": "Url", "type": "string"}, "optional": {"title": "Optional", "type": "boolean"}, "done": {"title": "Done", "type": "boolean"}}, "required": ["key", "title", "line", "url", "optional", "done"], "title": "SetupStepOut", "type": "object"}, "WorkspaceOut": {"properties": {"name": {"title": "Name", "type": "string"}, "vat_code": {"title": "Vat Code", "type": "string"}, "company_code": {"title": "Company Code", "type": "string"}, "address": {"title": "Address", "type": "string"}, "country": {"title": "Country", "type": "string"}, "url": {"title": "Url", "type": "string"}, "timezone": {"title": "Timezone", "type": "string"}, "currency": {"title": "Currency", "type": "string"}, "widget_style": {"additionalProperties": true, "title": "Widget Style", "type": "object"}, "embed_key": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Embed Key"}, "allowed_origins": {"items": {"type": "string"}, "title": "Allowed Origins", "type": "array"}, "bot_name": {"title": "Bot Name", "type": "string"}}, "required": ["name", "vat_code", "company_code", "address", "country", "url", "timezone", "currency", "widget_style", "embed_key", "allowed_origins", "bot_name"], "title": "WorkspaceOut", "type": "object"}, "WorkspacePatch": {"properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "vat_code": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Vat Code"}, "company_code": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Company Code"}, "address": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Address"}, "country": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Country"}, "url": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Url"}, "timezone": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Timezone"}, "currency": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Currency"}}, "title": "WorkspacePatch", "type": "object"}, "WidgetStyleIn": {"properties": {"primary_color": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Deprecated: set the brand kit (PUT /brand-kit) instead; a value sent here is stored as the kit's primary colour.", "title": "Primary Color"}, "auto_pageview": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Auto Pageview"}, "first_pageview": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "First Pageview"}, "home_greeting": {"anyOf": [{"maxLength": 200, "type": "string"}, {"type": "null"}], "description": "The messenger Home greeting; a newline splits the two lines. Blank restores \"Hello there.\\nHow can we help?\".", "title": "Home Greeting"}, "home_people": {"anyOf": [{"items": {"type": "string"}, "maxItems": 3, "type": "array"}, {"type": "null"}], "description": "Staff emails shown as avatars on Home, up to three; an empty list shows the three most recently active staff who take tickets.", "title": "Home People"}, "home_reply_promise": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "Show \"The team typically replies in under N hours\" on Home, N from the support settings' first_reply_hours.", "title": "Home Reply Promise"}, "home_working_hours": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "Show online/away on Home from the support settings' working hours, with the next opening time when away.", "title": "Home Working Hours"}, "home_show_bot": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "Show the AI agent first among the people on Home, always online (24/7); with nobody taking tickets it shows either way, never in draft-only mode.", "title": "Home Show Bot"}, "bot_name": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "The AI agent's name shown to visitors and usable as an @mention; blank or over 40 characters is a 422.", "title": "Bot Name"}, "home_button_label": {"anyOf": [{"maxLength": 60, "type": "string"}, {"type": "null"}], "description": "The full-width button on the messenger Home; blank removes it.", "title": "Home Button Label"}, "home_button_url": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Where the Home button leads, http(s); blank removes it.", "title": "Home Button Url"}, "chat_greeting": {"anyOf": [{"maxLength": 200, "type": "string"}, {"type": "null"}], "description": "The first bubble a visitor sees in a new conversation before writing. Blank restores the default.", "title": "Chat Greeting"}}, "title": "WidgetStyleIn", "type": "object"}, "OriginsIn": {"properties": {"origins": {"items": {"type": "string"}, "title": "Origins", "type": "array"}}, "required": ["origins"], "title": "OriginsIn", "type": "object"}, "StaffOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "email": {"title": "Email", "type": "string"}, "first_name": {"title": "First Name", "type": "string"}, "last_name": {"title": "Last Name", "type": "string"}, "alias": {"description": "The name customers see instead of first_name; empty shows first_name", "title": "Alias", "type": "string"}, "level": {"title": "Level", "type": "string"}, "is_active": {"title": "Is Active", "type": "boolean"}, "is_verified": {"title": "Is Verified", "type": "boolean"}, "accepts_tickets": {"title": "Accepts Tickets", "type": "boolean"}, "uses_api": {"description": "Off: no API & MCP page, and their personal and Claude/ChatGPT tokens stop working; owners always on", "title": "Uses Api", "type": "boolean"}, "sees_all_records": {"description": "Off: every list (web, REST, MCP) stops at the first 100 rows and analytics SQL is refused; owners always on", "title": "Sees All Records", "type": "boolean"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "areas": {"additionalProperties": {"type": "string"}, "description": "{area: none|view|edit} for every area; owners and the bot are always edit everywhere", "title": "Areas", "type": "object"}, "ticket_scope": {"description": "all, or departments: their departments plus unrouted, assigned and mentioning tickets", "title": "Ticket Scope", "type": "string"}, "preset": {"description": "support_agent, support_lead, marketing or full when the areas and scope match one exactly, else custom", "title": "Preset", "type": "string"}, "department_ids": {"items": {"type": "integer"}, "title": "Department Ids", "type": "array"}, "photo_url": {"description": "Public URL of the 96 px profile photo (swap 96 for 256 for the large one); empty without a photo", "title": "Photo Url", "type": "string"}}, "required": ["id", "email", "first_name", "last_name", "alias", "level", "is_active", "is_verified", "accepts_tickets", "uses_api", "sees_all_records", "created_at", "areas", "ticket_scope", "preset", "department_ids", "photo_url"], "title": "StaffOut", "type": "object"}, "StaffIn": {"properties": {"preset": {"default": "full", "description": "support_agent, support_lead, marketing or full (default): fills areas and ticket_scope before the explicit values apply", "title": "Preset", "type": "string"}, "areas": {"anyOf": [{"additionalProperties": {"type": "string"}, "type": "object"}, {"type": "null"}], "description": "{area: none|view|edit} over tickets, contacts, companies, tasks, knowledge, events, marketing, products, guides; merged over the preset, else over their current areas. Marketing covers segments, CRM rules, campaigns and templates", "title": "Areas"}, "ticket_scope": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "all, or departments: their departments plus unrouted tickets, tickets assigned to them and tickets they are mentioned in", "title": "Ticket Scope"}, "department_ids": {"anyOf": [{"items": {"type": "integer"}, "type": "array"}, {"type": "null"}], "description": "Replaces their department memberships", "title": "Department Ids"}, "email": {"title": "Email", "type": "string"}, "password": {"title": "Password", "type": "string"}, "first_name": {"default": "", "title": "First Name", "type": "string"}, "last_name": {"default": "", "title": "Last Name", "type": "string"}, "alias": {"default": "", "title": "Alias", "type": "string"}, "level": {"default": "staff", "title": "Level", "type": "string"}, "uses_api": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "Off: no API & MCP page, and their personal and Claude/ChatGPT tokens stop working; ignored for owners", "title": "Uses Api"}, "sees_all_records": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "Off: every list (web, REST, MCP) stops at the first 100 rows and analytics SQL is refused; owners always on", "title": "Sees All Records"}}, "required": ["email", "password"], "title": "StaffIn", "type": "object"}, "StaffPatch": {"properties": {"preset": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "support_agent, support_lead, marketing or full: fills areas and ticket_scope before the explicit values apply", "title": "Preset"}, "areas": {"anyOf": [{"additionalProperties": {"type": "string"}, "type": "object"}, {"type": "null"}], "description": "{area: none|view|edit} over tickets, contacts, companies, tasks, knowledge, events, marketing, products, guides; merged over the preset, else over their current areas. Marketing covers segments, CRM rules, campaigns and templates", "title": "Areas"}, "ticket_scope": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "all, or departments: their departments plus unrouted tickets, tickets assigned to them and tickets they are mentioned in", "title": "Ticket Scope"}, "department_ids": {"anyOf": [{"items": {"type": "integer"}, "type": "array"}, {"type": "null"}], "description": "Replaces their department memberships", "title": "Department Ids"}, "first_name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "First Name"}, "last_name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Last Name"}, "alias": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Alias"}, "email": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Email"}, "level": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Level"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Active"}, "uses_api": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "Off: no API & MCP page, and their personal and Claude/ChatGPT tokens stop working; ignored for owners", "title": "Uses Api"}, "sees_all_records": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "Off: every list (web, REST, MCP) stops at the first 100 rows and analytics SQL is refused; owners always on", "title": "Sees All Records"}, "accepts_tickets": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "Off: no department's assignment strategy deals this member new tickets. Sent alone it needs only the member themself or an admin.", "title": "Accepts Tickets"}}, "title": "StaffPatch", "type": "object"}, "ImageUrlIn": {"properties": {"url": {"description": "A public https address of the image; Zellwo downloads it.", "title": "Url", "type": "string"}}, "required": ["url"], "title": "ImageUrlIn", "type": "object"}, "IntegrationOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "tokens": {"items": {"$ref": "#/components/schemas/TokenOut"}, "title": "Tokens", "type": "array"}}, "required": ["id", "name", "created_at", "tokens"], "title": "IntegrationOut", "type": "object"}, "TokenOut": {"properties": {"id": {"title": "Id", "type": "integer"}, "last4": {"title": "Last4", "type": "string"}, "created": {"format": "date-time", "title": "Created", "type": "string"}, "expires": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "description": "null: never expires", "title": "Expires"}}, "required": ["id", "last4", "created", "expires"], "title": "TokenOut", "type": "object"}, "IntegrationCreatedOut": {"properties": {"integration": {"$ref": "#/components/schemas/IntegrationOut"}, "token": {"description": "The bearer token, returned this once only", "title": "Token", "type": "string"}}, "required": ["integration", "token"], "title": "IntegrationCreatedOut", "type": "object"}, "IntegrationIn": {"properties": {"name": {"description": "Unique among integrations, e.g. \"Shop backend\"; it is the author name on what the integration writes", "title": "Name", "type": "string"}, "expires": {"default": "1y", "enum": ["6m", "1y", "never"], "title": "Expires", "type": "string"}}, "required": ["name"], "title": "IntegrationIn", "type": "object"}, "IntegrationTokenOut": {"properties": {"token": {"description": "The bearer token, returned this once only", "title": "Token", "type": "string"}}, "required": ["token"], "title": "IntegrationTokenOut", "type": "object"}, "IntegrationTokenIn": {"properties": {"expires": {"anyOf": [{"enum": ["6m", "1y", "never"], "type": "string"}, {"type": "null"}], "description": "Left out: the same choice as the integration's newest token", "title": "Expires"}}, "title": "IntegrationTokenIn", "type": "object"}, "SearchOut": {"properties": {"results": {"items": {"$ref": "#/components/schemas/SearchResult"}, "title": "Results", "type": "array"}}, "required": ["results"], "title": "SearchOut", "type": "object"}, "SearchResult": {"properties": {"id": {"title": "Id", "type": "string"}, "title": {"title": "Title", "type": "string"}, "url": {"title": "Url", "type": "string"}}, "required": ["id", "title", "url"], "title": "SearchResult", "type": "object"}, "FetchOut": {"properties": {"id": {"title": "Id", "type": "string"}, "title": {"title": "Title", "type": "string"}, "text": {"title": "Text", "type": "string"}, "url": {"title": "Url", "type": "string"}, "metadata": {"additionalProperties": true, "title": "Metadata", "type": "object"}}, "required": ["id", "title", "text", "url", "metadata"], "title": "FetchOut", "type": "object"}, "Input": {"properties": {"limit": {"default": 100, "title": "Limit", "type": "integer"}, "offset": {"default": 0, "minimum": 0, "title": "Offset", "type": "integer"}}, "title": "Input", "type": "object"}, "PagedTicketOut": {"properties": {"items": {"items": {"$ref": "#/components/schemas/TicketOut"}, "title": "Items", "type": "array"}, "count": {"title": "Count", "type": "integer"}}, "required": ["items", "count"], "title": "PagedTicketOut", "type": "object"}, "TicketOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "subject": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Subject"}, "status": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Status"}, "priority": {"title": "Priority", "type": "integer"}, "enduser_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Enduser Id"}, "enduser_email": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Enduser Email"}, "enduser_name": {"title": "Enduser Name", "type": "string"}, "assignee_email": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Assignee Email"}, "bot_paused": {"title": "Bot Paused", "type": "boolean"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}, "solved_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Solved At"}, "external_reference": {"title": "External Reference", "type": "string"}, "category_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Category Id"}, "category_label": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Category Label"}, "department_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Department Id"}, "merged_into": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Merged Into"}, "next_reply_due_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "description": "When the customer is owed the next reply, counted in the team's working hours; null when no reply is owed.", "title": "Next Reply Due At"}}, "required": ["id", "subject", "status", "priority", "enduser_id", "enduser_email", "enduser_name", "assignee_email", "bot_paused", "created_at", "updated_at", "solved_at", "external_reference", "category_id", "category_label", "department_id", "merged_into"], "title": "TicketOut", "type": "object"}, "FollowUpOut": {"properties": {"task_id": {"title": "Task Id", "type": "string"}, "reason": {"title": "Reason", "type": "string"}, "scheduled_at": {"title": "Scheduled At", "type": "string"}, "action": {"title": "Action", "type": "string"}, "status": {"title": "Status", "type": "string"}, "created_by_bot": {"title": "Created By Bot", "type": "boolean"}, "channel": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Channel"}}, "required": ["task_id", "reason", "scheduled_at", "action", "status", "created_by_bot"], "title": "FollowUpOut", "type": "object"}, "MessageOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "content": {"title": "Content", "type": "string"}, "is_internal": {"title": "Is Internal", "type": "boolean"}, "channel": {"title": "Channel", "type": "string"}, "author_type": {"title": "Author Type", "type": "string"}, "author": {"title": "Author", "type": "string"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}}, "required": ["id", "content", "is_internal", "channel", "author_type", "author", "created_at"], "title": "MessageOut", "type": "object"}, "TicketDetailOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "subject": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Subject"}, "status": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Status"}, "priority": {"title": "Priority", "type": "integer"}, "enduser_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Enduser Id"}, "enduser_email": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Enduser Email"}, "enduser_name": {"title": "Enduser Name", "type": "string"}, "assignee_email": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Assignee Email"}, "bot_paused": {"title": "Bot Paused", "type": "boolean"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}, "solved_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Solved At"}, "external_reference": {"title": "External Reference", "type": "string"}, "category_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Category Id"}, "category_label": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Category Label"}, "department_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Department Id"}, "merged_into": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Merged Into"}, "next_reply_due_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "description": "When the customer is owed the next reply, counted in the team's working hours; null when no reply is owed.", "title": "Next Reply Due At"}, "messages": {"items": {"$ref": "#/components/schemas/MessageOut"}, "title": "Messages", "type": "array"}, "todos": {"items": {"additionalProperties": true, "type": "object"}, "title": "Todos", "type": "array"}, "follow_ups": {"items": {"$ref": "#/components/schemas/FollowUpOut"}, "title": "Follow Ups", "type": "array"}}, "required": ["id", "subject", "status", "priority", "enduser_id", "enduser_email", "enduser_name", "assignee_email", "bot_paused", "created_at", "updated_at", "solved_at", "external_reference", "category_id", "category_label", "department_id", "merged_into", "messages", "todos", "follow_ups"], "title": "TicketDetailOut", "type": "object"}, "TicketPatchIn": {"properties": {"assignee_email": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Assignee Email"}, "category_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Category Id"}, "priority": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Priority"}, "external_reference": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "External Reference"}}, "title": "TicketPatchIn", "type": "object"}, "ReplyOut": {"properties": {"message": {"$ref": "#/components/schemas/MessageOut"}, "emailed": {"title": "Emailed", "type": "boolean"}, "channel": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Channel"}, "reason": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Reason"}, "ticket_status": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Ticket Status"}}, "required": ["message", "emailed", "channel", "reason", "ticket_status"], "title": "ReplyOut", "type": "object"}, "ReplyIn": {"properties": {"content": {"title": "Content", "type": "string"}, "internal": {"default": false, "title": "Internal", "type": "boolean"}, "channel": {"anyOf": [{"enum": ["email", "sms"], "type": "string"}, {"type": "null"}], "title": "Channel"}, "attachments": {"default": [], "items": {"$ref": "#/components/schemas/UpsertAttachmentIn"}, "title": "Attachments", "type": "array"}, "attachment_uuids": {"default": [], "description": "Files a connector tool fetched on this ticket (a connector line's `file.attachment_uuid`), attached to the reply.", "items": {"format": "uuid", "type": "string"}, "title": "Attachment Uuids", "type": "array"}}, "required": ["content"], "title": "ReplyIn", "type": "object"}, "UpsertAttachmentIn": {"properties": {"name": {"maxLength": 256, "minLength": 1, "title": "Name", "type": "string"}, "content_type": {"default": "", "description": "Ignored: the type is read from the bytes.", "maxLength": 255, "title": "Content Type", "type": "string"}, "base64": {"title": "Base64", "type": "string"}}, "required": ["name", "base64"], "title": "UpsertAttachmentIn", "type": "object"}, "TicketStatusIn": {"properties": {"status": {"enum": ["New", "Open", "Pending", "On-Hold", "Solved", "Closed", "Spam"], "title": "Status", "type": "string"}}, "required": ["status"], "title": "TicketStatusIn", "type": "object"}, "UpsertTicketOut": {"properties": {"ticket": {"$ref": "#/components/schemas/TicketOut"}, "created": {"title": "Created", "type": "boolean"}, "message_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Message Id"}}, "required": ["ticket", "created", "message_id"], "title": "UpsertTicketOut", "type": "object"}, "UpsertContactIn": {"properties": {"external_id": {"anyOf": [{"maxLength": 100, "type": "string"}, {"type": "null"}], "description": "The contact's id in your own system; matched before email and phone.", "title": "External Id"}, "email": {"anyOf": [{"maxLength": 254, "type": "string"}, {"type": "null"}], "title": "Email"}, "phone": {"anyOf": [{"maxLength": 40, "type": "string"}, {"type": "null"}], "title": "Phone"}, "first_name": {"default": "", "maxLength": 100, "title": "First Name", "type": "string"}, "last_name": {"default": "", "maxLength": 100, "title": "Last Name", "type": "string"}, "email_verified": {"default": true, "description": "False when the address is unproven, e.g. typed into a public form: it is stored as the contact's declared e-mail instead of the verified one.", "title": "Email Verified", "type": "boolean"}, "phone_verified": {"default": true, "description": "False when the number is unproven, e.g. typed into a public form: it is stored as the contact's declared number instead of the verified one.", "title": "Phone Verified", "type": "boolean"}}, "title": "UpsertContactIn", "type": "object"}, "UpsertMessageIn": {"properties": {"body": {"maxLength": 20000, "title": "Body", "type": "string"}, "internal": {"default": false, "description": "True: a note only staff see. False: the customer said this.", "title": "Internal", "type": "boolean"}, "channel": {"default": "chat", "description": "How the customer reached you, which is how replies go back to them.", "enum": ["email", "chat", "sms"], "title": "Channel", "type": "string"}, "attachments": {"default": [], "items": {"$ref": "#/components/schemas/UpsertAttachmentIn"}, "title": "Attachments", "type": "array"}}, "required": ["body"], "title": "UpsertMessageIn", "type": "object"}, "UpsertTicketIn": {"properties": {"contact": {"$ref": "#/components/schemas/UpsertContactIn"}, "external_reference": {"default": "", "description": "What the ticket is about in your system, e.g. KRZZ4Z; empty when the ticket is not about one thing.", "maxLength": 100, "title": "External Reference", "type": "string"}, "match": {"default": "open", "description": "open: add to the matching open ticket if there is one. new: always open a ticket.", "enum": ["open", "new"], "title": "Match", "type": "string"}, "category_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "description": "Preferred way to name the category: the id shown as \"ID: \u2026\" in Settings \u2192 Routing. None of category_id and category: the ticket is unrouted and the routing agent picks one.", "title": "Category Id"}, "category": {"anyOf": [{"maxLength": 400, "type": "string"}, {"type": "null"}], "description": "The category label instead of its id, e.g. \"Operations \u203a Collection Problem \u203a Cancel Order\".", "title": "Category"}, "subject": {"default": "", "description": "Used when a ticket is opened.", "maxLength": 255, "title": "Subject", "type": "string"}, "message": {"anyOf": [{"$ref": "#/components/schemas/UpsertMessageIn"}, {"type": "null"}]}, "status": {"anyOf": [{"enum": ["New", "Open", "Pending", "On-Hold", "Solved", "Closed", "Spam"], "type": "string"}, {"type": "null"}], "title": "Status"}, "idempotency_key": {"anyOf": [{"maxLength": 100, "minLength": 1, "type": "string"}, {"type": "null"}], "description": "Resending the same key returns the first result and writes nothing.", "title": "Idempotency Key"}}, "required": ["contact"], "title": "UpsertTicketIn", "type": "object"}, "BotIn": {"properties": {"paused": {"title": "Paused", "type": "boolean"}}, "required": ["paused"], "title": "BotIn", "type": "object"}, "OkOut": {"properties": {"ok": {"default": true, "title": "Ok", "type": "boolean"}, "detail": {"default": "", "title": "Detail", "type": "string"}}, "title": "OkOut", "type": "object"}, "TodoSummaryOut": {"properties": {"pending": {"title": "Pending", "type": "integer"}, "in_progress": {"title": "In Progress", "type": "integer"}, "completed": {"title": "Completed", "type": "integer"}}, "required": ["pending", "in_progress", "completed"], "title": "TodoSummaryOut", "type": "object"}, "TodosOut": {"properties": {"summary": {"$ref": "#/components/schemas/TodoSummaryOut"}, "pending_tasks": {"items": {"type": "string"}, "title": "Pending Tasks", "type": "array"}, "in_progress_tasks": {"items": {"type": "string"}, "title": "In Progress Tasks", "type": "array"}, "completed_tasks": {"default": [], "items": {"type": "string"}, "title": "Completed Tasks", "type": "array"}}, "required": ["summary", "pending_tasks", "in_progress_tasks"], "title": "TodosOut", "type": "object"}, "TodoIn": {"properties": {"task": {"title": "Task", "type": "string"}, "action": {"default": "add", "enum": ["add", "update", "delete"], "title": "Action", "type": "string"}, "new_task": {"default": "", "title": "New Task", "type": "string"}, "status": {"default": "", "enum": ["", "pending", "in_progress", "completed"], "title": "Status", "type": "string"}}, "required": ["task"], "title": "TodoIn", "type": "object"}, "FollowUpsOut": {"properties": {"tasks": {"items": {"$ref": "#/components/schemas/FollowUpOut"}, "title": "Tasks", "type": "array"}}, "required": ["tasks"], "title": "FollowUpsOut", "type": "object"}, "FollowUpIn": {"properties": {"reason": {"title": "Reason", "type": "string"}, "scheduled_at": {"title": "Scheduled At", "type": "string"}, "channel": {"anyOf": [{"enum": ["email", "sms"], "type": "string"}, {"type": "null"}], "title": "Channel"}}, "required": ["reason", "scheduled_at"], "title": "FollowUpIn", "type": "object"}, "FollowUpPatch": {"properties": {"reason": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Reason"}, "scheduled_at": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Scheduled At"}, "channel": {"anyOf": [{"enum": ["email", "sms"], "type": "string"}, {"type": "null"}], "title": "Channel"}}, "title": "FollowUpPatch", "type": "object"}, "MergeIn": {"properties": {"loser_id": {"format": "uuid", "title": "Loser Id", "type": "string"}, "winner_id": {"format": "uuid", "title": "Winner Id", "type": "string"}}, "required": ["loser_id", "winner_id"], "title": "MergeIn", "type": "object"}, "TranslationOut": {"properties": {"language": {"title": "Language", "type": "string"}, "title": {"title": "Title", "type": "string"}, "text": {"title": "Text", "type": "string"}, "embedded": {"description": "False when building the search vector failed; send the translation again to retry.", "title": "Embedded", "type": "boolean"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}}, "required": ["language", "title", "text", "embedded", "updated_at"], "title": "TranslationOut", "type": "object"}, "TranslateIn": {"properties": {"language": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "ISO 639 code to translate into, e.g. `de`; defaults to the caller's language.", "title": "Language"}}, "title": "TranslateIn", "type": "object"}, "RenderedMacroOut": {"properties": {"id": {"title": "Id", "type": "integer"}, "title": {"title": "Title", "type": "string"}, "body": {"title": "Body", "type": "string"}, "personal": {"title": "Personal", "type": "boolean"}}, "required": ["id", "title", "body", "personal"], "title": "RenderedMacroOut", "type": "object"}, "ApprovalOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "connector_id": {"title": "Connector Id", "type": "string"}, "connector_name": {"title": "Connector Name", "type": "string"}, "tool": {"title": "Tool", "type": "string"}, "kind": {"title": "Kind", "type": "string"}, "arguments": {"additionalProperties": true, "title": "Arguments", "type": "object"}, "bound_values": {"additionalProperties": true, "title": "Bound Values", "type": "object"}, "expires_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Expires At"}, "sending": {"description": "Approved and still running.", "title": "Sending", "type": "boolean"}, "approved_by": {"title": "Approved By", "type": "string"}}, "required": ["id", "connector_id", "connector_name", "tool", "kind", "arguments", "bound_values", "expires_at", "sending", "approved_by"], "title": "ApprovalOut", "type": "object"}, "RunOut": {"properties": {"ok": {"title": "Ok", "type": "boolean"}, "error": {"title": "Error", "type": "string"}, "message": {"title": "Message", "type": "string"}, "status_code": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Status Code"}, "data": {"description": "The projected result: kept fields only.", "title": "Data"}, "replayed": {"description": "The idempotency key was used before: the stored result, nothing sent.", "title": "Replayed", "type": "boolean"}, "file": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "description": "A file the tool fetched: name, size_kb and the attachment_uuid on the ticket's internal line (to attach with reply_to_ticket); never the file.", "title": "File"}}, "required": ["ok", "error", "message", "status_code", "data", "replayed", "file"], "title": "RunOut", "type": "object"}, "DenyIn": {"properties": {"reason": {"description": "Required; kept as an internal note on the ticket.", "title": "Reason", "type": "string"}}, "required": ["reason"], "title": "DenyIn", "type": "object"}, "OfferedToolOut": {"properties": {"connector_id": {"format": "uuid", "title": "Connector Id", "type": "string"}, "connector_name": {"title": "Connector Name", "type": "string"}, "name": {"title": "Name", "type": "string"}, "kind": {"title": "Kind", "type": "string"}, "requires": {"description": "Derived, read-only: verified when the tool fills a non-optional input from the contact (or, for an action, the ticket reference); it then runs only for a proven customer.", "enum": ["none", "verified"], "title": "Requires", "type": "string"}, "description": {"title": "Description", "type": "string"}, "params": {"additionalProperties": true, "title": "Params", "type": "object"}, "bound_values": {"additionalProperties": {"type": "string"}, "description": "The server-filled inputs from this ticket; an empty required one means the tool refuses, an optional one is sent empty.", "title": "Bound Values", "type": "object"}, "blocked": {"description": "'' or why no run can succeed on this ticket whatever the inputs: unproven_customer, a tool that needs a proven customer while the customer has not proven who they are.", "title": "Blocked", "type": "string"}}, "required": ["connector_id", "connector_name", "name", "kind", "requires", "description", "params", "bound_values", "blocked"], "title": "OfferedToolOut", "type": "object"}, "TicketToolsOut": {"properties": {"identity_proven": {"description": "False: verified tools refuse, and any run needs checked_identity.", "title": "Identity Proven", "type": "boolean"}, "proof": {"description": "How the answered customer turns were proven: jwt, link, dkim, api, reply, from or none.", "title": "Proof", "type": "string"}, "tools": {"items": {"$ref": "#/components/schemas/OfferedToolOut"}, "title": "Tools", "type": "array"}}, "required": ["identity_proven", "proof", "tools"], "title": "TicketToolsOut", "type": "object"}, "RunToolIn": {"properties": {"arguments": {"additionalProperties": true, "default": {}, "description": "The tool's params.", "title": "Arguments", "type": "object"}, "idempotency_key": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "description": "Required for an action: keep it, a retry with it replays the first call instead of sending again.", "title": "Idempotency Key"}, "checked_identity": {"default": false, "description": "I checked who this is: required while the sender is not proven. A person attests it; over MCP it is refused for an action.", "title": "Checked Identity", "type": "boolean"}}, "title": "RunToolIn", "type": "object"}, "MacroOut": {"properties": {"id": {"title": "Id", "type": "integer"}, "personal": {"title": "Personal", "type": "boolean"}, "department_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Department Id"}, "title": {"title": "Title", "type": "string"}, "body": {"title": "Body", "type": "string"}, "is_active": {"title": "Is Active", "type": "boolean"}, "position": {"title": "Position", "type": "integer"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}}, "required": ["id", "personal", "department_id", "title", "body", "is_active", "position", "created_at", "updated_at"], "title": "MacroOut", "type": "object"}, "MacroIn": {"properties": {"title": {"description": "What staff pick it by in the reply box; unique within its department.", "maxLength": 100, "title": "Title", "type": "string"}, "body": {"description": "The text inserted into the reply, up to 5,000 characters. `{customer_first_name}`, `{company_name}`, `{ticket_reference}` and `{agent_first_name}` (the alias when set) are filled in when it is offered on a ticket; anything else in braces is left as typed.", "maxLength": 5000, "title": "Body", "type": "string"}, "department_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "description": "The department it is offered in; null offers it in every department.", "title": "Department Id"}, "is_active": {"default": true, "description": "Inactive macros are kept but not offered.", "title": "Is Active", "type": "boolean"}, "position": {"default": 0, "description": "Lower numbers come first in the list.", "minimum": 0, "title": "Position", "type": "integer"}, "personal": {"default": false, "description": "True: only you see, use and edit it. False: shared with the team, which needs edit on the tickets area.", "title": "Personal", "type": "boolean"}}, "required": ["title", "body"], "title": "MacroIn", "type": "object"}, "MacroPatch": {"properties": {"title": {"anyOf": [{"maxLength": 100, "type": "string"}, {"type": "null"}], "title": "Title"}, "body": {"anyOf": [{"maxLength": 5000, "type": "string"}, {"type": "null"}], "description": "The text inserted into the reply, up to 5,000 characters. `{customer_first_name}`, `{company_name}`, `{ticket_reference}` and `{agent_first_name}` (the alias when set) are filled in when it is offered on a ticket; anything else in braces is left as typed.", "title": "Body"}, "department_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "description": "null offers it in every department.", "title": "Department Id"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Active"}, "position": {"anyOf": [{"minimum": 0, "type": "integer"}, {"type": "null"}], "title": "Position"}, "personal": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "True: only you see, use and edit it. False: shared with the team, which needs edit on the tickets area.", "title": "Personal"}}, "title": "MacroPatch", "type": "object"}, "PagedTaskOut": {"properties": {"items": {"items": {"$ref": "#/components/schemas/TaskOut"}, "title": "Items", "type": "array"}, "count": {"title": "Count", "type": "integer"}}, "required": ["items", "count"], "title": "PagedTaskOut", "type": "object"}, "TaskOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "title": {"title": "Title", "type": "string"}, "description": {"title": "Description", "type": "string"}, "status": {"enum": ["open", "in_progress", "done", "cancelled", "failed"], "title": "Status", "type": "string"}, "action": {"title": "Action", "type": "string"}, "source": {"title": "Source", "type": "string"}, "due_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Due At"}, "assignee_email": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Assignee Email"}, "created_by_email": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Created By Email"}, "company_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Company Id"}, "enduser_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Enduser Id"}, "ticket_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Ticket Id"}, "department_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Department Id"}, "department": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Department"}, "completed_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Completed At"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}}, "required": ["id", "title", "description", "status", "action", "source", "due_at", "assignee_email", "created_by_email", "company_id", "enduser_id", "ticket_id", "department_id", "department", "completed_at", "created_at"], "title": "TaskOut", "type": "object"}, "TaskIn": {"properties": {"title": {"default": "", "title": "Title", "type": "string"}, "description": {"default": "", "title": "Description", "type": "string"}, "action": {"default": "", "enum": ["", "bot_follow_up"], "title": "Action", "type": "string"}, "due_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Due At"}, "assignee_email": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Assignee Email"}, "company_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Company Id"}, "enduser_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Enduser Id"}, "ticket_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Ticket Id"}, "department_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Department Id"}}, "title": "TaskIn", "type": "object"}, "TaskPatch": {"properties": {"title": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Title"}, "description": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Description"}, "status": {"anyOf": [{"enum": ["open", "in_progress", "done", "cancelled", "failed"], "type": "string"}, {"type": "null"}], "title": "Status"}, "action": {"anyOf": [{"enum": ["", "bot_follow_up"], "type": "string"}, {"type": "null"}], "title": "Action"}, "due_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Due At"}, "assignee_email": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Assignee Email"}, "department_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Department Id"}}, "title": "TaskPatch", "type": "object"}, "NoteOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "body": {"title": "Body", "type": "string"}, "author_email": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Author Email"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}}, "required": ["id", "body", "author_email", "created_at"], "title": "NoteOut", "type": "object"}, "NoteIn": {"properties": {"body": {"description": "Markdown: `##` headings, **bold**, *italic*, `code`, [text](url) links, `-` and `1.` lists; line breaks are kept.", "title": "Body", "type": "string"}}, "required": ["body"], "title": "NoteIn", "type": "object"}, "DepartmentOut": {"properties": {"id": {"title": "Id", "type": "integer"}, "name": {"title": "Name", "type": "string"}, "description": {"title": "Description", "type": "string"}, "is_active": {"title": "Is Active", "type": "boolean"}, "is_default": {"title": "Is Default", "type": "boolean"}, "email_address": {"title": "Email Address", "type": "string"}, "display_name": {"title": "Display Name", "type": "string"}, "signature_html": {"title": "Signature Html", "type": "string"}, "member_emails": {"items": {"type": "string"}, "title": "Member Emails", "type": "array"}, "category_count": {"title": "Category Count", "type": "integer"}, "working_hours": {"additionalProperties": {"items": {"items": {"type": "string"}, "type": "array"}, "type": "array"}, "title": "Working Hours", "type": "object"}, "assignment_strategy": {"title": "Assignment Strategy", "type": "string"}, "max_open_per_agent": {"title": "Max Open Per Agent", "type": "integer"}, "accepted_segment_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Accepted Segment Id"}, "redirect_reply": {"title": "Redirect Reply", "type": "string"}, "redirect_cooldown_hours": {"title": "Redirect Cooldown Hours", "type": "integer"}, "starting_priority": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Starting Priority"}}, "required": ["id", "name", "description", "is_active", "is_default", "email_address", "display_name", "signature_html", "member_emails", "category_count", "working_hours", "assignment_strategy", "max_open_per_agent", "accepted_segment_id", "redirect_reply", "redirect_cooldown_hours", "starting_priority"], "title": "DepartmentOut", "type": "object"}, "DepartmentIn": {"properties": {"name": {"title": "Name", "type": "string"}, "description": {"default": "", "title": "Description", "type": "string"}, "is_active": {"default": true, "title": "Is Active", "type": "boolean"}, "is_default": {"default": false, "description": "Make this the workspace default: where an enquiry goes when nothing says which department.", "title": "Is Default", "type": "boolean"}, "email_address": {"default": "", "description": "Mail sent to this address arrives in this department and replies go out from it.", "title": "Email Address", "type": "string"}, "display_name": {"default": "", "description": "The From name on this department's mail.", "title": "Display Name", "type": "string"}, "signature_html": {"default": "", "description": "HTML signature appended to this department's reply emails.", "title": "Signature Html", "type": "string"}, "working_hours": {"additionalProperties": {"items": {"items": {"type": "string"}, "type": "array"}, "type": "array"}, "description": "When this team works, in the workspace timezone: {\"mon\": [[\"09:00\", \"18:00\"]], ...} (keys mon to sun, at most 4 non-overlapping HH:MM pairs a day). {} uses the workspace's `working_hours`. The bot's questions to this team wait 4 of these hours.", "title": "Working Hours", "type": "object"}, "assignment_strategy": {"default": "shared_queue", "description": "Who answers a ticket (owner): `shared_queue` (default) nobody owns a ticket: everyone in the department takes the one closest to its reply deadline next; `manual` nobody is dealt one, the staff member who replied last owns it; `round_robin` dealt to the available member assigned longest ago; `least_busy` dealt to the available member with the fewest New/Open tickets. Outside `shared_queue` a public staff reply makes the replier the assignee. Available: a member accepting tickets, in working hours.", "enum": ["shared_queue", "manual", "round_robin", "least_busy"], "title": "Assignment Strategy", "type": "string"}, "max_open_per_agent": {"default": 0, "description": "`least_busy` skips a member holding this many New/Open tickets; 0 is no cap (owner).", "minimum": 0, "title": "Max Open Per Agent", "type": "integer"}, "accepted_segment_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "description": "Only contacts in this segment open tickets by mail to this address, and only when the mail proves the sender (DKIM-signed, or a reply to our own mail). Everyone else gets the redirect reply and no ticket. Empty: anyone. A segment id from `/segments`; null: anyone.", "title": "Accepted Segment Id"}, "redirect_reply": {"default": "", "description": "Sent instead of a ticket to a sender the accepted segment turns away. Empty: no reply.", "title": "Redirect Reply", "type": "string"}, "redirect_cooldown_hours": {"default": 24, "description": "Hours before the same sender gets the redirect reply again.", "minimum": 0, "title": "Redirect Cooldown Hours", "type": "integer"}, "starting_priority": {"anyOf": [{"type": "integer"}, {"type": "null"}], "description": "Priority of a ticket opened by mail to this address. Empty: Low. 0 low, 1 medium, 2 high, 3 urgent.", "title": "Starting Priority"}}, "required": ["name"], "title": "DepartmentIn", "type": "object"}, "DepartmentPatch": {"properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "description": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Description"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Active"}, "is_default": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "`true` makes this the default; the default is only handed on, so `false` on the default is a 422.", "title": "Is Default"}, "email_address": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Email Address"}, "display_name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Display Name"}, "signature_html": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Signature Html"}, "working_hours": {"anyOf": [{"additionalProperties": {"items": {"items": {"type": "string"}, "type": "array"}, "type": "array"}, "type": "object"}, {"type": "null"}], "description": "This team's hours; {} goes back to the workspace's.", "title": "Working Hours"}, "assignment_strategy": {"anyOf": [{"enum": ["shared_queue", "manual", "round_robin", "least_busy"], "type": "string"}, {"type": "null"}], "description": "Who answers a ticket (owner): `shared_queue` (default) nobody owns a ticket: everyone in the department takes the one closest to its reply deadline next; `manual` nobody is dealt one, the staff member who replied last owns it; `round_robin` dealt to the available member assigned longest ago; `least_busy` dealt to the available member with the fewest New/Open tickets. Outside `shared_queue` a public staff reply makes the replier the assignee. Available: a member accepting tickets, in working hours.", "title": "Assignment Strategy"}, "max_open_per_agent": {"anyOf": [{"minimum": 0, "type": "integer"}, {"type": "null"}], "description": "`least_busy` skips a member holding this many New/Open tickets; 0 is no cap (owner).", "title": "Max Open Per Agent"}, "accepted_segment_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "description": "Only contacts in this segment open tickets by mail to this address, and only when the mail proves the sender (DKIM-signed, or a reply to our own mail). Everyone else gets the redirect reply and no ticket. Empty: anyone. A segment id from `/segments`; null: anyone.", "title": "Accepted Segment Id"}, "redirect_reply": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Sent instead of a ticket to a sender the accepted segment turns away. Empty: no reply.", "title": "Redirect Reply"}, "redirect_cooldown_hours": {"anyOf": [{"minimum": 0, "type": "integer"}, {"type": "null"}], "description": "Hours before the same sender gets the redirect reply again.", "title": "Redirect Cooldown Hours"}, "starting_priority": {"anyOf": [{"type": "integer"}, {"type": "null"}], "description": "Priority of a ticket opened by mail to this address. Empty: Low. 0 low, 1 medium, 2 high, 3 urgent.", "title": "Starting Priority"}}, "title": "DepartmentPatch", "type": "object"}, "MembersIn": {"properties": {"emails": {"description": "The department's complete member list: staff emails. Empty removes everyone.", "items": {"type": "string"}, "title": "Emails", "type": "array"}}, "required": ["emails"], "title": "MembersIn", "type": "object"}, "CategoryOut": {"properties": {"id": {"title": "Id", "type": "integer"}, "name": {"title": "Name", "type": "string"}, "label": {"title": "Label", "type": "string"}, "department_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Department Id"}, "parent_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Parent Id"}, "is_active": {"title": "Is Active", "type": "boolean"}, "is_fallback": {"title": "Is Fallback", "type": "boolean"}, "chat_bot_initiated": {"title": "Chat Bot Initiated", "type": "boolean"}, "requires_email": {"title": "Requires Email", "type": "boolean"}, "requires_reference": {"title": "Requires Reference", "type": "boolean"}, "requires_verified_identity": {"title": "Requires Verified Identity", "type": "boolean"}, "priority": {"title": "Priority", "type": "integer"}, "default_subject": {"title": "Default Subject", "type": "string"}, "default_creation_message": {"title": "Default Creation Message", "type": "string"}, "default_bot_failure_message": {"title": "Default Bot Failure Message", "type": "string"}, "first_reply": {"enum": ["post", "ai_writes", "wait"], "title": "First Reply", "type": "string"}}, "required": ["id", "name", "label", "department_id", "parent_id", "is_active", "is_fallback", "chat_bot_initiated", "requires_email", "requires_reference", "requires_verified_identity", "priority", "default_subject", "default_creation_message", "default_bot_failure_message", "first_reply"], "title": "CategoryOut", "type": "object"}, "CategoryIn": {"properties": {"name": {"title": "Name", "type": "string"}, "department_id": {"title": "Department Id", "type": "integer"}, "parent_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "description": "A category of the same department to nest under.", "title": "Parent Id"}, "is_active": {"default": true, "title": "Is Active", "type": "boolean"}, "chat_bot_initiated": {"default": true, "description": "Whether the AI agent answers customers on tickets in this category.", "title": "Chat Bot Initiated", "type": "boolean"}, "requires_email": {"default": false, "description": "Route a ticket here only once the customer's email address is known.", "title": "Requires Email", "type": "boolean"}, "requires_reference": {"default": false, "description": "Route a ticket here only once it carries a verified reference, e.g. an order number.", "title": "Requires Reference", "type": "boolean"}, "requires_verified_identity": {"default": false, "description": "Route a ticket here only once the customer has proven who they are.", "title": "Requires Verified Identity", "type": "boolean"}, "priority": {"default": 0, "description": "Higher wins when routing weighs categories.", "minimum": 0, "title": "Priority", "type": "integer"}, "default_subject": {"default": "", "description": "Subject given to a ticket in this category that has none. Placeholders: {company_name}, {customer_first_name}, {ticket_reference}.", "maxLength": 255, "title": "Default Subject", "type": "string"}, "default_creation_message": {"default": "", "description": "Posted to the customer when an upsert opens a ticket here (never on email). Empty: no acknowledgment. Placeholders: {company_name}, {customer_first_name}, {ticket_reference}.", "title": "Default Creation Message", "type": "string"}, "default_bot_failure_message": {"default": "", "description": "Posted in chat when the AI agent gives the ticket to a person. Empty: the built-in apology. Placeholders: {company_name}, {customer_first_name}, {ticket_reference}.", "title": "Default Bot Failure Message", "type": "string"}, "first_reply": {"default": "post", "description": "When an upsert opens a ticket here: `post` posts the creation message as the bot; `ai_writes` has the AI agent write the first reply itself, covering the creation message's points; `wait` posts the creation message and the AI agent answers only the customer's next message.", "enum": ["post", "ai_writes", "wait"], "title": "First Reply", "type": "string"}}, "required": ["name", "department_id"], "title": "CategoryIn", "type": "object"}, "CategoryPatch": {"properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "department_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Department Id"}, "parent_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Parent Id"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Active"}, "chat_bot_initiated": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Chat Bot Initiated"}, "requires_email": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Requires Email"}, "requires_reference": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Requires Reference"}, "requires_verified_identity": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Requires Verified Identity"}, "priority": {"anyOf": [{"minimum": 0, "type": "integer"}, {"type": "null"}], "title": "Priority"}, "default_subject": {"anyOf": [{"maxLength": 255, "type": "string"}, {"type": "null"}], "description": "Subject given to a ticket in this category that has none. Placeholders: {company_name}, {customer_first_name}, {ticket_reference}.", "title": "Default Subject"}, "default_creation_message": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Posted to the customer when an upsert opens a ticket here (never on email). Empty: no acknowledgment. Placeholders: {company_name}, {customer_first_name}, {ticket_reference}.", "title": "Default Creation Message"}, "default_bot_failure_message": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Posted in chat when the AI agent gives the ticket to a person. Empty: the built-in apology. Placeholders: {company_name}, {customer_first_name}, {ticket_reference}.", "title": "Default Bot Failure Message"}, "first_reply": {"anyOf": [{"enum": ["post", "ai_writes", "wait"], "type": "string"}, {"type": "null"}], "description": "When an upsert opens a ticket here: `post` posts the creation message as the bot; `ai_writes` has the AI agent write the first reply itself, covering the creation message's points; `wait` posts the creation message and the AI agent answers only the customer's next message.", "title": "First Reply"}}, "title": "CategoryPatch", "type": "object"}, "SupportSettingsOut": {"properties": {"pending_to_solved_hours": {"title": "Pending To Solved Hours", "type": "integer"}, "solved_to_closed_hours": {"title": "Solved To Closed Hours", "type": "integer"}, "on_hold_to_open_hours": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "On Hold To Open Hours"}, "reference_url_template": {"title": "Reference Url Template", "type": "string"}, "working_hours": {"additionalProperties": {"items": {"items": {"type": "string"}, "type": "array"}, "type": "array"}, "title": "Working Hours", "type": "object"}, "draft_only": {"title": "Draft Only", "type": "boolean"}, "first_reply_hours": {"title": "First Reply Hours", "type": "integer"}, "next_reply_hours": {"title": "Next Reply Hours", "type": "integer"}, "routing_enabled": {"title": "Routing Enabled", "type": "boolean"}, "routing_turn_budget": {"title": "Routing Turn Budget", "type": "integer"}}, "required": ["pending_to_solved_hours", "solved_to_closed_hours", "on_hold_to_open_hours", "reference_url_template", "working_hours", "draft_only", "first_reply_hours", "next_reply_hours", "routing_enabled", "routing_turn_budget"], "title": "SupportSettingsOut", "type": "object"}, "SupportSettingsPatch": {"properties": {"pending_to_solved_hours": {"anyOf": [{"minimum": 0, "type": "integer"}, {"type": "null"}], "description": "Hours a Pending ticket waits for the customer before it is marked Solved.", "title": "Pending To Solved Hours"}, "solved_to_closed_hours": {"anyOf": [{"minimum": 0, "type": "integer"}, {"type": "null"}], "description": "Hours a Solved ticket stays reopenable before it is Closed.", "title": "Solved To Closed Hours"}, "on_hold_to_open_hours": {"anyOf": [{"minimum": 0, "type": "integer"}, {"type": "null"}], "description": "Hours an On-Hold ticket rests before it reopens; null keeps On-Hold tickets on hold.", "title": "On Hold To Open Hours"}, "reference_url_template": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "An https:// address with {reference} in it exactly once, e.g. https://example.com/orders/{reference}/; empty removes the link.", "title": "Reference Url Template"}, "working_hours": {"anyOf": [{"additionalProperties": {"items": {"items": {"type": "string"}, "type": "array"}, "type": "array"}, "type": "object"}, {"type": "null"}], "description": "When the team works, in the workspace timezone: {\"mon\": [[\"09:00\", \"18:00\"]], ...} with keys mon to sun; a missing day is a day off; {} counts every hour. The bot's questions to the team wait 4 of these hours.", "title": "Working Hours"}, "draft_only": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "`true`: the AI agent's replies are saved as internal `bot_draft` notes for staff to send and nothing reaches the customer; its notes, tasks and questions to the team still happen.", "title": "Draft Only"}, "first_reply_hours": {"anyOf": [{"maximum": 720, "minimum": 1, "type": "integer"}, {"type": "null"}], "description": "Working hours a new conversation may wait for its first reply before it is overdue.", "title": "First Reply Hours"}, "next_reply_hours": {"anyOf": [{"maximum": 720, "minimum": 1, "type": "integer"}, {"type": "null"}], "description": "Working hours a customer's follow-up may wait for a reply before it is overdue.", "title": "Next Reply Hours"}, "routing_enabled": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "`true`: the AI agent files each new unrouted conversation under the category the customer describes, behind the category gates, and folds duplicates.", "title": "Routing Enabled"}, "routing_turn_budget": {"anyOf": [{"maximum": 50, "minimum": 1, "type": "integer"}, {"type": "null"}], "description": "Replies the AI agent may give an unrouted conversation before it is filed under the arrival department's Other and handed over.", "title": "Routing Turn Budget"}}, "title": "SupportSettingsPatch", "type": "object"}, "AiSettingsOut": {"properties": {"model": {"description": "High effort model: bot replies, Generate reply, campaign, outreach and help-article drafts, the reply check. An AiSettings model id such as `openai:gpt-6-luna`; '' runs the platform default.", "title": "Model", "type": "string"}, "low_effort_model": {"description": "Low effort model: contact and company profiling, message translation, connector tool docs. An AiSettings model id such as `openai:gpt-6-luna`; '' runs the platform default.", "title": "Low Effort Model", "type": "string"}, "has_own_key": {"description": "True when every AI call is billed to the workspace's own OpenAI key.", "title": "Has Own Key", "type": "boolean"}, "profile_companies_on_create": {"title": "Profile Companies On Create", "type": "boolean"}, "profile_only_with_valid_vat": {"title": "Profile Only With Valid Vat", "type": "boolean"}, "web_search": {"title": "Web Search", "type": "boolean"}}, "required": ["model", "low_effort_model", "has_own_key", "profile_companies_on_create", "profile_only_with_valid_vat", "web_search"], "title": "AiSettingsOut", "type": "object"}, "AiSettingsPatch": {"properties": {"model": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "High effort model. An AiSettings model id such as `openai:gpt-6-luna`; '' runs the platform default.", "title": "Model"}, "low_effort_model": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Low effort model. An AiSettings model id such as `openai:gpt-6-luna`; '' runs the platform default.", "title": "Low Effort Model"}, "openai_api_key": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Write-only. Starts with `sk-`; '' removes it and bills the platform account again.", "title": "Openai Api Key"}, "profile_companies_on_create": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Profile Companies On Create"}, "profile_only_with_valid_vat": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Profile Only With Valid Vat"}, "web_search": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Web Search"}}, "title": "AiSettingsPatch", "type": "object"}, "StaffChannelOut": {"properties": {"id": {"title": "Id", "type": "integer"}, "name": {"title": "Name", "type": "string"}, "provider": {"title": "Provider", "type": "string"}, "target": {"title": "Target", "type": "string"}, "is_active": {"title": "Is Active", "type": "boolean"}, "has_token": {"title": "Has Token", "type": "boolean"}, "department_ids": {"items": {"type": "integer"}, "title": "Department Ids", "type": "array"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}}, "required": ["id", "name", "provider", "target", "is_active", "has_token", "department_ids", "created_at", "updated_at"], "title": "StaffChannelOut", "type": "object"}, "StaffChannelIn": {"properties": {"name": {"title": "Name", "type": "string"}, "provider": {"const": "slack", "default": "slack", "title": "Provider", "type": "string"}, "target": {"description": "The Slack channel id, e.g. C0123456789 (channel details \u2192 About \u2192 Channel ID).", "title": "Target", "type": "string"}, "bot_token": {"description": "The Slack app's Bot User OAuth Token (xoxb-\u2026), with the bot scopes chat:write, channels:read, groups:read, channels:history, groups:history, users:read. Write-only: never returned.", "title": "Bot Token", "type": "string"}, "is_active": {"default": true, "title": "Is Active", "type": "boolean"}}, "required": ["name", "target", "bot_token"], "title": "StaffChannelIn", "type": "object"}, "StaffChannelPatch": {"properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "target": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Target"}, "bot_token": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "The Slack app's Bot User OAuth Token (xoxb-\u2026), with the bot scopes chat:write, channels:read, groups:read, channels:history, groups:history, users:read. Write-only: never returned. Leave it out to keep the stored one.", "title": "Bot Token"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Active"}}, "title": "StaffChannelPatch", "type": "object"}, "StaffChannelTestOut": {"properties": {"ok": {"title": "Ok", "type": "boolean"}, "detail": {"title": "Detail", "type": "string"}}, "required": ["ok", "detail"], "title": "StaffChannelTestOut", "type": "object"}, "EnduserDetailOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "first_name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "First Name"}, "last_name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Last Name"}, "email": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Email"}, "email_verified": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Email Verified"}, "phone_number": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Phone Number"}, "phone_number_verified": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Phone Number Verified"}, "external_id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "External Id"}, "crm_status": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Crm Status"}, "crm_stage": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Crm Stage"}, "company_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Company Id"}, "company_name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Company Name"}, "profile": {"title": "Profile", "type": "string"}, "profile_status": {"title": "Profile Status", "type": "string"}, "profiled_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Profiled At"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "marketing_opt_out": {"default": false, "description": "True once the contact unsubscribed, filed a spam complaint or staff unsubscribed them; campaigns never write to them, replies and staff messages still go out.", "title": "Marketing Opt Out", "type": "boolean"}, "marketing_opt_out_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "description": "When the contact was unsubscribed from campaigns; null while they are subscribed.", "title": "Marketing Opt Out At"}, "blocked": {"default": false, "description": "True when the contact is blocked as a sender: their messages land in Spam without a bot reply.", "title": "Blocked", "type": "boolean"}, "is_business": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "True for a business, false for a private person, null while unknown.", "title": "Is Business"}, "is_business_source": {"default": "", "description": "Where is_business came from: api when you set it (ingest or update_enduser), company when a company link made the contact a business, empty while unknown.", "title": "Is Business Source", "type": "string"}, "properties": {"type": "object", "additionalProperties": true, "default": {}, "description": "Current value of every workspace-defined contact property.", "title": "Properties"}, "initial_properties": {"additionalProperties": true, "default": {}, "description": "The first value ever stored for each of those properties.", "title": "Initial Properties", "type": "object"}, "crm_status_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Crm Status Id"}, "todo_list": {"description": "Deprecated, removed next release: the notes for the bot (customer_information.$bot_notes) as pending items.", "items": {"additionalProperties": true, "type": "object"}, "title": "Todo List", "type": "array"}, "customer_information": {"additionalProperties": true, "title": "Customer Information", "type": "object"}, "profile_data": {"additionalProperties": true, "title": "Profile Data", "type": "object"}, "ticket_count": {"title": "Ticket Count", "type": "integer"}, "event_count": {"title": "Event Count", "type": "integer"}}, "required": ["id", "first_name", "last_name", "email", "email_verified", "phone_number", "phone_number_verified", "external_id", "crm_status", "crm_stage", "company_id", "company_name", "profile", "profile_status", "profiled_at", "created_at", "crm_status_id", "todo_list", "customer_information", "profile_data", "ticket_count", "event_count"], "title": "EnduserDetailOut", "type": "object"}, "EnduserPatch": {"properties": {"first_name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "First Name"}, "last_name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Last Name"}, "email": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Email"}, "phone_number": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Phone Number"}, "marketing_opt_out": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "True unsubscribes the contact from every campaign and closes its open enrolment; false resubscribes.", "title": "Marketing Opt Out"}, "blocked": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "True blocks the sender and moves their tickets that are not Solved or Closed to Spam; false unblocks and leaves the tickets where they are.", "title": "Blocked"}, "properties": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "description": "Workspace-defined contact properties by key (see list_property_definitions); each value must fit its definition.", "title": "Properties"}, "is_business": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "True business, false private; null clears your value so a company link decides again.", "title": "Is Business"}}, "title": "EnduserPatch", "type": "object"}, "PagedEnduserDetailOut": {"properties": {"items": {"items": {"$ref": "#/components/schemas/EnduserDetailOut"}, "title": "Items", "type": "array"}, "count": {"title": "Count", "type": "integer"}}, "required": ["items", "count"], "title": "PagedEnduserDetailOut", "type": "object"}, "EnduserStatusIn": {"properties": {"status_id": {"format": "uuid", "title": "Status Id", "type": "string"}}, "required": ["status_id"], "title": "EnduserStatusIn", "type": "object"}, "MergeOut": {"properties": {"winner_id": {"format": "uuid", "title": "Winner Id", "type": "string"}, "moved": {"additionalProperties": {"type": "integer"}, "title": "Moved", "type": "object"}}, "required": ["winner_id", "moved"], "title": "MergeOut", "type": "object"}, "ProfilingOut": {"properties": {"started": {"title": "Started", "type": "boolean"}, "profile_status": {"title": "Profile Status", "type": "string"}}, "required": ["started", "profile_status"], "title": "ProfilingOut", "type": "object"}, "ProfileTextIn": {"properties": {"text": {"title": "Text", "type": "string"}}, "required": ["text"], "title": "ProfileTextIn", "type": "object"}, "CompanyOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "website": {"title": "Website", "type": "string"}, "domain": {"title": "Domain", "type": "string"}, "external_id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "External Id"}, "email": {"title": "Email", "type": "string"}, "phone": {"title": "Phone", "type": "string"}, "address": {"title": "Address", "type": "string"}, "country": {"title": "Country", "type": "string"}, "vat_code": {"title": "Vat Code", "type": "string"}, "company_code": {"title": "Company Code", "type": "string"}, "industry": {"title": "Industry", "type": "string"}, "tags": {"items": {"type": "string"}, "title": "Tags", "type": "array"}, "profile": {"title": "Profile", "type": "string"}, "profile_data": {"additionalProperties": true, "title": "Profile Data", "type": "object"}, "profile_status": {"title": "Profile Status", "type": "string"}, "profiled_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Profiled At"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}, "owner_email": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Staff member who owns the account.", "title": "Owner Email"}, "contact_count": {"default": 0, "description": "Linked contacts reachable by email, phone or external id.", "title": "Contact Count", "type": "integer"}, "open_ticket_count": {"default": 0, "description": "Those contacts' tickets that are neither Solved nor Closed.", "title": "Open Ticket Count", "type": "integer"}, "first_seen_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "description": "When the earliest of those contacts was created.", "title": "First Seen At"}, "last_active_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "description": "When the most recently updated of those contacts changed.", "title": "Last Active At"}, "last_purchase_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "description": "The most recent purchase any of those contacts made.", "title": "Last Purchase At"}, "next_purchase_by": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "description": "The latest date any of those contacts is still expected to buy by.", "title": "Next Purchase By"}, "purchase_overdue": {"default": false, "description": "Whether that date has passed, i.e. no contact is expected to buy any more.", "title": "Purchase Overdue", "type": "boolean"}, "stage": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Derived CRM stage: the best stage any of those contacts holds.", "title": "Stage"}, "properties": {"type": "object", "additionalProperties": true, "default": {}, "description": "Current value of every workspace-defined property.", "title": "Properties"}, "initial_properties": {"additionalProperties": true, "default": {}, "description": "The first value ever stored for each of those properties.", "title": "Initial Properties", "type": "object"}}, "required": ["id", "name", "website", "domain", "external_id", "email", "phone", "address", "country", "vat_code", "company_code", "industry", "tags", "profile", "profile_data", "profile_status", "profiled_at", "created_at", "updated_at"], "title": "CompanyOut", "type": "object"}, "ContactCheckOut": {"properties": {"email": {"title": "Email", "type": "string"}, "domain": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Domain"}, "freemail": {"title": "Freemail", "type": "boolean"}, "creates_enduser": {"title": "Creates Enduser", "type": "boolean"}, "creates_company": {"title": "Creates Company", "type": "boolean"}, "enduser": {"anyOf": [{"$ref": "#/components/schemas/EnduserDetailOut"}, {"type": "null"}]}, "company": {"anyOf": [{"$ref": "#/components/schemas/CompanyOut"}, {"type": "null"}]}, "profiling_started": {"title": "Profiling Started", "type": "boolean"}, "profiling_in_progress": {"title": "Profiling In Progress", "type": "boolean"}}, "required": ["email", "domain", "freemail", "creates_enduser", "creates_company", "enduser", "company", "profiling_started", "profiling_in_progress"], "title": "ContactCheckOut", "type": "object"}, "ContactCheckIn": {"properties": {"email": {"default": "", "title": "Email", "type": "string"}, "external_id": {"default": "", "title": "External Id", "type": "string"}, "profile": {"default": false, "title": "Profile", "type": "boolean"}, "wait_seconds": {"default": 0, "maximum": 25, "minimum": 0, "title": "Wait Seconds", "type": "integer"}}, "title": "ContactCheckIn", "type": "object"}, "EnduserOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "first_name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "First Name"}, "last_name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Last Name"}, "email": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Email"}, "email_verified": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Email Verified"}, "phone_number": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Phone Number"}, "phone_number_verified": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Phone Number Verified"}, "external_id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "External Id"}, "crm_status": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Crm Status"}, "crm_stage": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Crm Stage"}, "company_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Company Id"}, "company_name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Company Name"}, "profile": {"title": "Profile", "type": "string"}, "profile_status": {"title": "Profile Status", "type": "string"}, "profiled_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Profiled At"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "marketing_opt_out": {"default": false, "description": "True once the contact unsubscribed, filed a spam complaint or staff unsubscribed them; campaigns never write to them, replies and staff messages still go out.", "title": "Marketing Opt Out", "type": "boolean"}, "marketing_opt_out_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "description": "When the contact was unsubscribed from campaigns; null while they are subscribed.", "title": "Marketing Opt Out At"}, "blocked": {"default": false, "description": "True when the contact is blocked as a sender: their messages land in Spam without a bot reply.", "title": "Blocked", "type": "boolean"}, "is_business": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "True for a business, false for a private person, null while unknown.", "title": "Is Business"}, "is_business_source": {"default": "", "description": "Where is_business came from: api when you set it (ingest or update_enduser), company when a company link made the contact a business, empty while unknown.", "title": "Is Business Source", "type": "string"}, "properties": {"type": "object", "additionalProperties": true, "default": {}, "description": "Current value of every workspace-defined contact property.", "title": "Properties"}, "initial_properties": {"additionalProperties": true, "default": {}, "description": "The first value ever stored for each of those properties.", "title": "Initial Properties", "type": "object"}}, "required": ["id", "first_name", "last_name", "email", "email_verified", "phone_number", "phone_number_verified", "external_id", "crm_status", "crm_stage", "company_id", "company_name", "profile", "profile_status", "profiled_at", "created_at"], "title": "EnduserOut", "type": "object"}, "OnboardOut": {"properties": {"enduser": {"$ref": "#/components/schemas/EnduserOut"}, "company": {"anyOf": [{"$ref": "#/components/schemas/CompanyOut"}, {"type": "null"}]}, "created_enduser": {"title": "Created Enduser", "type": "boolean"}, "created_company": {"title": "Created Company", "type": "boolean"}, "profiling_started": {"title": "Profiling Started", "type": "boolean"}}, "required": ["enduser", "company", "created_enduser", "created_company", "profiling_started"], "title": "OnboardOut", "type": "object"}, "OnboardIn": {"properties": {"email": {"title": "Email", "type": "string"}, "first_name": {"default": "", "title": "First Name", "type": "string"}, "last_name": {"default": "", "title": "Last Name", "type": "string"}, "company_name": {"default": "", "title": "Company Name", "type": "string"}, "profile": {"default": true, "title": "Profile", "type": "boolean"}}, "required": ["email"], "title": "OnboardIn", "type": "object"}, "DraftOut": {"properties": {"subject": {"title": "Subject", "type": "string"}, "body": {"title": "Body", "type": "string"}}, "required": ["subject", "body"], "title": "DraftOut", "type": "object"}, "SendMessageOut": {"properties": {"ticket_id": {"format": "uuid", "title": "Ticket Id", "type": "string"}, "message_id": {"format": "uuid", "title": "Message Id", "type": "string"}, "channel": {"description": "email or sms: how the message goes out.", "title": "Channel", "type": "string"}, "emailed": {"description": "True only when an email was sent or queued.", "title": "Emailed", "type": "boolean"}, "reason": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Reason"}}, "required": ["ticket_id", "message_id", "channel", "emailed", "reason"], "title": "SendMessageOut", "type": "object"}, "SendMessageIn": {"properties": {"subject": {"title": "Subject", "type": "string"}, "body": {"title": "Body", "type": "string"}, "channel": {"anyOf": [{"enum": ["email", "sms"], "type": "string"}, {"type": "null"}], "description": "Omitted: email when the contact has an address, else SMS when set up.", "title": "Channel"}, "department_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "description": "The department the conversation goes out from (its address, name and signature). Omitted: the sender's own department, else the workspace default.", "title": "Department Id"}}, "required": ["subject", "body"], "title": "SendMessageIn", "type": "object"}, "PagedCompanyOut": {"properties": {"items": {"items": {"$ref": "#/components/schemas/CompanyOut"}, "title": "Items", "type": "array"}, "count": {"title": "Count", "type": "integer"}}, "required": ["items", "count"], "title": "PagedCompanyOut", "type": "object"}, "CompanyIn": {"properties": {"name": {"title": "Name", "type": "string"}, "website": {"default": "", "title": "Website", "type": "string"}, "external_id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "External Id"}, "email": {"default": "", "title": "Email", "type": "string"}, "phone": {"default": "", "title": "Phone", "type": "string"}, "address": {"default": "", "title": "Address", "type": "string"}, "country": {"default": "", "title": "Country", "type": "string"}, "vat_code": {"default": "", "title": "Vat Code", "type": "string"}, "company_code": {"default": "", "title": "Company Code", "type": "string"}, "industry": {"default": "", "title": "Industry", "type": "string"}, "tags": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], "title": "Tags"}}, "required": ["name"], "title": "CompanyIn", "type": "object"}, "CompanyPatch": {"properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "website": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Website"}, "email": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Email"}, "phone": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Phone"}, "address": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Address"}, "country": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Country"}, "vat_code": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Vat Code"}, "company_code": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Company Code"}, "industry": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Industry"}, "tags": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], "title": "Tags"}, "owner_email": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Email of a staff member of this workspace; \"\" clears the owner.", "title": "Owner Email"}, "properties": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "description": "Workspace-defined properties by key (see list_property_definitions); each value must fit its definition.", "title": "Properties"}}, "title": "CompanyPatch", "type": "object"}, "EnduserLinkIn": {"properties": {"enduser_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Enduser Id"}, "email": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Email"}, "phone": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Phone"}, "external_id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "External Id"}, "first_name": {"default": "", "title": "First Name", "type": "string"}, "last_name": {"default": "", "title": "Last Name", "type": "string"}}, "title": "EnduserLinkIn", "type": "object"}, "TimelineRowOut": {"properties": {"kind": {"title": "Kind", "type": "string"}, "at": {"format": "date-time", "title": "At", "type": "string"}, "title": {"title": "Title", "type": "string"}, "detail": {"title": "Detail", "type": "string"}, "url": {"title": "Url", "type": "string"}, "actor": {"title": "Actor", "type": "string"}}, "required": ["kind", "at", "title", "detail", "url", "actor"], "title": "TimelineRowOut", "type": "object"}, "PropertyDefinitionOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "key": {"title": "Key", "type": "string"}, "label": {"title": "Label", "type": "string"}, "type": {"title": "Type", "type": "string"}, "target": {"title": "Target", "type": "string"}, "options": {"items": {"type": "string"}, "title": "Options", "type": "array"}, "position": {"title": "Position", "type": "integer"}, "required": {"title": "Required", "type": "boolean"}, "is_active": {"title": "Is Active", "type": "boolean"}}, "required": ["id", "key", "label", "type", "target", "options", "position", "required", "is_active"], "title": "PropertyDefinitionOut", "type": "object"}, "PropertyDefinitionIn": {"properties": {"key": {"description": "Lowercase letters, digits and underscores, starting with a letter; rules and filters address it as properties__<key>.", "title": "Key", "type": "string"}, "label": {"title": "Label", "type": "string"}, "type": {"default": "text", "description": "text, number, select, multi_select, boolean, date or country.", "title": "Type", "type": "string"}, "target": {"default": "company", "description": "company, contact or product.", "title": "Target", "type": "string"}, "options": {"default": [], "description": "Allowed values; required for select and multi_select.", "items": {"type": "string"}, "title": "Options", "type": "array"}, "position": {"default": 0, "title": "Position", "type": "integer"}, "required": {"default": false, "title": "Required", "type": "boolean"}}, "required": ["key", "label"], "title": "PropertyDefinitionIn", "type": "object"}, "PropertyDefinitionPatch": {"properties": {"label": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Label"}, "type": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Type"}, "options": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], "title": "Options"}, "position": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Position"}, "required": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Required"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "false retires the property: the field is hidden and stored values are kept.", "title": "Is Active"}}, "title": "PropertyDefinitionPatch", "type": "object"}, "SegmentOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "description": {"title": "Description", "type": "string"}, "match": {"title": "Match", "type": "string"}, "rules": {"items": {"additionalProperties": true, "type": "object"}, "title": "Rules", "type": "array"}, "prompt": {"title": "Prompt", "type": "string"}, "is_active": {"title": "Is Active", "type": "boolean"}, "member_count": {"title": "Member Count", "type": "integer"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}}, "required": ["id", "name", "description", "match", "rules", "prompt", "is_active", "member_count", "created_at", "updated_at"], "title": "SegmentOut", "type": "object"}, "SegmentIn": {"properties": {"name": {"title": "Name", "type": "string"}, "description": {"default": "", "title": "Description", "type": "string"}, "match": {"default": "all", "description": "all = every rule must hold (AND), any = one is enough (OR).", "title": "Match", "type": "string"}, "rules": {"items": {"$ref": "#/components/schemas/SegmentRuleIn"}, "title": "Rules", "type": "array"}, "prompt": {"default": "", "description": "Added to the agent instructions while it talks to a contact in this segment; at most 20000 characters.", "title": "Prompt", "type": "string"}, "is_active": {"default": true, "title": "Is Active", "type": "boolean"}}, "required": ["name", "rules"], "title": "SegmentIn", "type": "object"}, "SegmentRuleIn": {"properties": {"field": {"description": "A key from list_segment_fields, e.g. email, crm_status.stage, customer_information.$initial_utm_source, crm_status_changed_at, event.checkout, or any customer_information.<key> or event.<type>.", "title": "Field", "type": "string"}, "op": {"description": "One of: equals, not_equals, contains, not_contains, starts_with, is_set, is_not_set, gt, gte, lt, lte, after, before, in, within_days, not_within_days; list_segment_fields says which apply to a field.", "title": "Op", "type": "string"}, "value": {"description": "Omitted for is_set and is_not_set; a list for in. A date is an ISO date or a live token: today, today-7d, today+3d. A whole number of days from 1 to 730 for within_days / not_within_days on event.<type> fields.", "title": "Value"}}, "required": ["field", "op"], "title": "SegmentRuleIn", "type": "object"}, "SegmentFieldOut": {"properties": {"key": {"title": "Key", "type": "string"}, "label": {"title": "Label", "type": "string"}, "group": {"title": "Group", "type": "string"}, "type": {"title": "Type", "type": "string"}, "options": {"items": {"items": {"type": "string"}, "type": "array"}, "title": "Options", "type": "array"}, "operators": {"items": {"type": "string"}, "title": "Operators", "type": "array"}}, "required": ["key", "label", "group", "type", "options", "operators"], "title": "SegmentFieldOut", "type": "object"}, "SegmentFieldsOut": {"properties": {"fields": {"items": {"$ref": "#/components/schemas/SegmentFieldOut"}, "title": "Fields", "type": "array"}, "operators": {"items": {"$ref": "#/components/schemas/SegmentOperatorOut"}, "title": "Operators", "type": "array"}}, "required": ["fields", "operators"], "title": "SegmentFieldsOut", "type": "object"}, "SegmentOperatorOut": {"properties": {"key": {"title": "Key", "type": "string"}, "label": {"title": "Label", "type": "string"}, "needs_value": {"title": "Needs Value", "type": "boolean"}, "types": {"items": {"type": "string"}, "title": "Types", "type": "array"}}, "required": ["key", "label", "needs_value", "types"], "title": "SegmentOperatorOut", "type": "object"}, "SegmentPreviewOut": {"properties": {"member_count": {"title": "Member Count", "type": "integer"}, "rules": {"items": {"additionalProperties": true, "type": "object"}, "title": "Rules", "type": "array"}}, "required": ["member_count", "rules"], "title": "SegmentPreviewOut", "type": "object"}, "Match": {"enum": ["all", "any"], "title": "Match", "type": "string"}, "SegmentPreviewIn": {"properties": {"rules": {"items": {"$ref": "#/components/schemas/SegmentRuleIn"}, "title": "Rules", "type": "array"}, "match": {"allOf": [{"$ref": "#/components/schemas/Match"}], "default": "all", "description": "all = every rule must hold (AND), any = one is enough (OR)."}}, "required": ["rules"], "title": "SegmentPreviewIn", "type": "object"}, "SegmentPatch": {"properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "description": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Description"}, "match": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Match"}, "rules": {"anyOf": [{"items": {"$ref": "#/components/schemas/SegmentRuleIn"}, "type": "array"}, {"type": "null"}], "description": "Replaces the whole rule list.", "title": "Rules"}, "prompt": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Prompt"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "false pauses the segment: it stops filtering contacts and stops reaching the agent.", "title": "Is Active"}}, "title": "SegmentPatch", "type": "object"}, "CampaignFunnelOut": {"properties": {"enrolled": {"title": "Enrolled", "type": "integer"}, "holdout": {"title": "Holdout", "type": "integer"}, "sent": {"title": "Sent", "type": "integer"}, "delivered": {"title": "Delivered", "type": "integer"}, "replied": {"title": "Replied", "type": "integer"}, "converted": {"title": "Converted", "type": "integer"}}, "required": ["enrolled", "holdout", "sent", "delivered", "replied", "converted"], "title": "CampaignFunnelOut", "type": "object"}, "CampaignOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "kind": {"title": "Kind", "type": "string"}, "segment_id": {"format": "uuid", "title": "Segment Id", "type": "string"}, "segment_name": {"title": "Segment Name", "type": "string"}, "department_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Department Id"}, "department_name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Department Name"}, "sender_name": {"title": "Sender Name", "type": "string"}, "writing_mode": {"title": "Writing Mode", "type": "string"}, "ai_scope": {"title": "Ai Scope", "type": "string"}, "research_missing": {"title": "Research Missing", "type": "boolean"}, "email_layout": {"title": "Email Layout", "type": "string"}, "languages": {"items": {"type": "string"}, "title": "Languages", "type": "array"}, "daily_cap": {"title": "Daily Cap", "type": "integer"}, "status_reason": {"default": "", "description": "Why the campaign paused or stopped on its own; cleared on activation.", "title": "Status Reason", "type": "string"}, "launch_checks": {"default": [], "description": "What activation needs (archived, channel, department, templates, layout, and issues on a newsletter, whose detail always lists them); the first not ok is the 409.", "items": {"$ref": "#/components/schemas/LaunchCheckOut"}, "title": "Launch Checks", "type": "array"}, "issues": {"additionalProperties": {"type": "integer"}, "default": {}, "description": "How many issues (steps) each ticked language has: a newsletter's series length, 1 on outreach once the language has a template.", "title": "Issues", "type": "object"}, "channel": {"title": "Channel", "type": "string"}, "goal_event": {"title": "Goal Event", "type": "string"}, "goal_window_days": {"title": "Goal Window Days", "type": "integer"}, "brief": {"title": "Brief", "type": "string"}, "todo_list": {"items": {"type": "string"}, "title": "Todo List", "type": "array"}, "product_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Product Id"}, "status": {"title": "Status", "type": "string"}, "budget": {"anyOf": [{"pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", "type": "string"}, {"type": "null"}], "title": "Budget"}, "max_messages_per_contact": {"title": "Max Messages Per Contact", "type": "integer"}, "stale_first_follow_up_after_hours": {"title": "Stale First Follow Up After Hours", "type": "integer"}, "follow_up_channels": {"items": {"type": "string"}, "title": "Follow Up Channels", "type": "array"}, "holdout_percent": {"title": "Holdout Percent", "type": "integer"}, "wave_size": {"title": "Wave Size", "type": "integer"}, "starts_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Starts At"}, "ends_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Ends At"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}, "funnel": {"$ref": "#/components/schemas/CampaignFunnelOut", "description": "Its enrolments counted by how far they got."}, "spend": {"default": "0", "description": "What this campaign has spent: its model calls plus every SMS part sent on its tickets (first messages, follow-ups, replies by the agent or staff). Never a sum of two currencies: an SMS amount no rate converts is left out.", "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", "title": "Spend", "type": "string"}, "cpa": {"anyOf": [{"pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", "type": "string"}, {"type": "null"}], "description": "Spend (model calls and SMS) per attributed conversion, null before the first one.", "title": "Cpa"}, "currency": {"default": "", "description": "The currency `budget`, `spend` and `cpa` are in: the workspace currency.", "title": "Currency", "type": "string"}, "templates": {"items": {"$ref": "#/components/schemas/CampaignTemplateOut"}, "title": "Templates", "type": "array"}}, "required": ["id", "name", "kind", "segment_id", "segment_name", "department_id", "department_name", "sender_name", "writing_mode", "ai_scope", "research_missing", "email_layout", "languages", "daily_cap", "channel", "goal_event", "goal_window_days", "brief", "todo_list", "product_id", "status", "budget", "max_messages_per_contact", "stale_first_follow_up_after_hours", "follow_up_channels", "holdout_percent", "wave_size", "starts_at", "ends_at", "created_at", "updated_at", "funnel", "templates"], "title": "CampaignOut", "type": "object"}, "CampaignTemplateOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "recipe": {"additionalProperties": true, "title": "Recipe", "type": "object"}, "subject": {"title": "Subject", "type": "string"}, "body": {"title": "Body", "type": "string"}, "language": {"default": "en", "description": "The language the template is written in; contacts of the campaign languages it lacks get the English ones.", "title": "Language", "type": "string"}, "step": {"default": 1, "description": "1 is the first send (on a newsletter its variants A-C, each with its own blocks, theme and subject); on a newsletter 2-10 are the follow-up emails, one per language and step. Outreach templates are always 1.", "title": "Step", "type": "integer"}, "variant": {"default": "", "description": "A, B or C on a newsletter's first email (step 1), each with its own blocks, theme and subject; empty on later emails and on outreach.", "title": "Variant", "type": "string"}, "is_active": {"title": "Is Active", "type": "boolean"}, "successes": {"title": "Successes", "type": "number"}, "failures": {"title": "Failures", "type": "number"}, "sent": {"default": 0, "description": "Messages of this template that actually went out.", "title": "Sent", "type": "integer"}, "delivered": {"default": 0, "description": "Messages of this template the provider confirmed reaching the inbox.", "title": "Delivered", "type": "integer"}, "replied": {"title": "Replied", "type": "integer"}, "converted": {"title": "Converted", "type": "integer"}, "posterior_mean": {"default": 0.0, "description": "Expected reward under the arm's Beta posterior.", "title": "Posterior Mean", "type": "number"}, "win_probability": {"default": 0.0, "description": "How often this arm wins a Thompson draw against the other active ones; 0 once it is retired.", "title": "Win Probability", "type": "number"}, "layout": {"default": [], "description": "Newsletter blocks; empty on outreach.", "items": {"additionalProperties": true, "type": "object"}, "title": "Layout", "type": "array"}, "theme": {"additionalProperties": true, "default": {}, "description": "Newsletter theme overrides over the brand kit; empty means the brand kit as is.", "title": "Theme", "type": "object"}, "content_hash": {"title": "Content Hash", "type": "string"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}}, "required": ["id", "name", "recipe", "subject", "body", "is_active", "successes", "failures", "replied", "converted", "content_hash", "created_at"], "title": "CampaignTemplateOut", "type": "object"}, "LaunchCheckOut": {"properties": {"key": {"title": "Key", "type": "string"}, "ok": {"title": "Ok", "type": "boolean"}, "detail": {"default": "", "description": "What is missing; empty once ok.", "title": "Detail", "type": "string"}, "warning": {"default": "", "description": "A note on a passing row that does not block activation, e.g. a department without an address sending from the connected sender.", "title": "Warning", "type": "string"}}, "required": ["key", "ok"], "title": "LaunchCheckOut", "type": "object"}, "AiScope": {"enum": ["opening", "full"], "title": "AiScope", "type": "string"}, "CampaignIn": {"properties": {"name": {"title": "Name", "type": "string"}, "kind": {"allOf": [{"$ref": "#/components/schemas/Kind"}], "default": "outreach", "description": "outreach starts a conversation per contact; newsletter sends a series of issues rendered from block layouts: issue 1 first, then one issue per stale_first_follow_up_after_hours until the goal event or max_messages_per_contact; no AI writing, email_layout blocks."}, "segment_id": {"description": "The segment whose members this campaign enrols; list_segments for the ids.", "format": "uuid", "title": "Segment Id", "type": "string"}, "department_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "description": "The department the campaign speaks for: its address, display name, signature and Slack channel; list_departments for the ids. Omitted on create: the caller's own department, else the workspace default. null: send as the workspace from the channel address.", "title": "Department Id"}, "sender_name": {"default": "", "description": "The From name and the sign-off ({{sender.name}}); empty means the workspace name.", "title": "Sender Name", "type": "string"}, "writing_mode": {"allOf": [{"$ref": "#/components/schemas/WritingMode"}], "default": "templates", "description": "templates sends arms as written; ai writes each email from one."}, "ai_scope": {"allOf": [{"$ref": "#/components/schemas/AiScope"}], "default": "opening", "description": "AI mode: opening rewrites the opening line only, full the whole email."}, "research_missing": {"default": false, "description": "AI mode: research the contacts' companies that have no profile before writing, billed to the budget.", "title": "Research Missing", "type": "boolean"}, "email_layout": {"allOf": [{"$ref": "#/components/schemas/EmailLayout"}], "default": "personal", "description": "personal looks like a one-to-one email (first contact); designed wraps the first touch in the workspace header, product card and button (existing customers); blocks renders the templates' layout and is for newsletters only."}, "languages": {"default": [], "description": "Language codes (ISO 639-1) the campaign writes in, at most 20; every other contact gets English.", "items": {"type": "string"}, "title": "Languages", "type": "array"}, "daily_cap": {"default": 200, "description": "Campaign messages sent per day at most.", "maximum": 10000, "minimum": 1, "title": "Daily Cap", "type": "integer"}, "channel": {"default": "email", "description": "email, the only value: a campaign goes out by email, and SMS joins as a follow-up channel through follow_up_channels.", "title": "Channel", "type": "string"}, "goal_event": {"description": "The event type that counts as the conversion; list_campaign_goals for the usable names.", "title": "Goal Event", "type": "string"}, "goal_window_days": {"default": 7, "description": "Days after the send in which that event still counts.", "title": "Goal Window Days", "type": "integer"}, "brief": {"description": "Objective, offer and tone: what the templates are written from, and the agent instruction while an enrolment is open.", "title": "Brief", "type": "string"}, "todo_list": {"default": [], "description": "Checklist lines the enrolment ticket starts with.", "items": {"type": "string"}, "title": "Todo List", "type": "array"}, "product_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "description": "The only offer the templates may name.", "title": "Product Id"}, "budget": {"anyOf": [{"type": "number"}, {"pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", "type": "string"}, {"type": "null"}], "description": "Hard cap, in the workspace currency, on what this campaign may spend on model calls and SMS parts; it stops enrolling and following up once reached.", "title": "Budget"}, "max_messages_per_contact": {"default": 2, "description": "The first send plus every follow-up the agent runs; its answers to customer replies do not count. The agent is told how many are left and paces them over the goal window.", "title": "Max Messages Per Contact", "type": "integer"}, "stale_first_follow_up_after_hours": {"default": 72, "description": "Hours the first message may stay unanswered before the agent is woken once to decide on a first follow-up (send or stay silent), unless the contact replies first; 0 turns it off. Needs max_messages_per_contact of 2 or more. The follow-ups the agent schedules or moves are at least this many hours (24 when 0) after now and apart from each other, and before the goal window closes; a follow-up due after the window closed is cancelled. Staff are never held to the gap or the window.", "maximum": 720, "minimum": 0, "title": "Stale First Follow Up After Hours", "type": "integer"}, "follow_up_channels": {"default": [], "description": "Channels the agent may follow up on: 'email', 'sms' or both; empty means the campaign's own channel. The first message always uses channel. Every follow-up carries its channel: the one given, else the contact's last channel when allowed, else the first that reaches them. One SMS follow-up per contact, billed per part: on an email campaign a ping asking them to answer the email, on an SMS campaign one more text.", "items": {"type": "string"}, "title": "Follow Up Channels", "type": "array"}, "holdout_percent": {"default": 10, "description": "Share of the segment enrolled to measure lift and never sent to; at most 50.", "title": "Holdout Percent", "type": "integer"}, "wave_size": {"default": 50, "description": "Contacts enrolled per sweep.", "title": "Wave Size", "type": "integer"}, "starts_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Starts At"}, "ends_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "description": "Bounds entry only; enrolments already open run to their window.", "title": "Ends At"}}, "required": ["name", "segment_id", "goal_event", "brief"], "title": "CampaignIn", "type": "object"}, "EmailLayout": {"enum": ["personal", "designed", "blocks"], "title": "EmailLayout", "type": "string"}, "Kind": {"enum": ["outreach", "newsletter"], "title": "Kind", "type": "string"}, "WritingMode": {"enum": ["templates", "ai"], "title": "WritingMode", "type": "string"}, "CampaignGoalOut": {"properties": {"name": {"title": "Name", "type": "string"}, "description": {"title": "Description", "type": "string"}, "is_system": {"title": "Is System", "type": "boolean"}}, "required": ["name", "description", "is_system"], "title": "CampaignGoalOut", "type": "object"}, "CampaignPreviewOut": {"properties": {"eligible": {"title": "Eligible", "type": "integer"}, "holdout": {"title": "Holdout", "type": "integer"}, "languages": {"default": [], "description": "The eligible contacts by the language they read, largest first.", "items": {"$ref": "#/components/schemas/LanguageShareOut"}, "title": "Languages", "type": "array"}, "no_company": {"default": 0, "description": "Eligible contacts without a company, whom company research cannot reach.", "title": "No Company", "type": "integer"}}, "required": ["eligible", "holdout"], "title": "CampaignPreviewOut", "type": "object"}, "LanguageShareOut": {"properties": {"code": {"title": "Code", "type": "string"}, "count": {"title": "Count", "type": "integer"}, "sources": {"additionalProperties": {"type": "integer"}, "description": "How many of them each source decided: reply, page, browser, company_country or default.", "title": "Sources", "type": "object"}}, "required": ["code", "count", "sources"], "title": "LanguageShareOut", "type": "object"}, "CampaignPreviewIn": {"properties": {"segment_id": {"format": "uuid", "title": "Segment Id", "type": "string"}, "channel": {"default": "email", "title": "Channel", "type": "string"}, "holdout_percent": {"default": 10, "title": "Holdout Percent", "type": "integer"}, "goal_event": {"title": "Goal Event", "type": "string"}, "goal_window_days": {"default": 7, "title": "Goal Window Days", "type": "integer"}}, "required": ["segment_id", "goal_event"], "title": "CampaignPreviewIn", "type": "object"}, "CampaignPatch": {"properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "kind": {"anyOf": [{"$ref": "#/components/schemas/Kind"}, {"type": "null"}], "description": "outreach starts a conversation per contact; newsletter sends a series of issues rendered from block layouts: issue 1 first, then one issue per stale_first_follow_up_after_hours until the goal event or max_messages_per_contact; no AI writing, email_layout blocks."}, "segment_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Segment Id"}, "department_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "description": "The department the campaign speaks for: its address, display name, signature and Slack channel; list_departments for the ids. Omitted on create: the caller's own department, else the workspace default. null: send as the workspace from the channel address.", "title": "Department Id"}, "sender_name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Sender Name"}, "writing_mode": {"anyOf": [{"$ref": "#/components/schemas/WritingMode"}, {"type": "null"}]}, "ai_scope": {"anyOf": [{"$ref": "#/components/schemas/AiScope"}, {"type": "null"}]}, "research_missing": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Research Missing"}, "email_layout": {"anyOf": [{"$ref": "#/components/schemas/EmailLayout"}, {"type": "null"}], "description": "personal looks like a one-to-one email (first contact); designed wraps the first touch in the workspace header, product card and button (existing customers); blocks renders the templates' layout and is for newsletters only."}, "languages": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], "description": "Replaces the whole list.", "title": "Languages"}, "daily_cap": {"anyOf": [{"maximum": 10000, "minimum": 1, "type": "integer"}, {"type": "null"}], "title": "Daily Cap"}, "channel": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "email, the only value; SMS is a follow-up channel.", "title": "Channel"}, "goal_event": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Goal Event"}, "goal_window_days": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Goal Window Days"}, "brief": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "May be rewritten at any status; it takes effect on the next enrolment.", "title": "Brief"}, "todo_list": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], "title": "Todo List"}, "product_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Product Id"}, "budget": {"anyOf": [{"type": "number"}, {"pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", "type": "string"}, {"type": "null"}], "title": "Budget"}, "max_messages_per_contact": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Max Messages Per Contact"}, "stale_first_follow_up_after_hours": {"anyOf": [{"maximum": 720, "minimum": 0, "type": "integer"}, {"type": "null"}], "title": "Stale First Follow Up After Hours"}, "follow_up_channels": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], "description": "Replaces the whole list.", "title": "Follow Up Channels"}, "holdout_percent": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Holdout Percent"}, "wave_size": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Wave Size"}, "starts_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Starts At"}, "ends_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Ends At"}}, "title": "CampaignPatch", "type": "object"}, "CampaignGenerateIn": {"properties": {"count": {"default": 3, "description": "How many distinct templates the model writes.", "maximum": 6, "minimum": 1, "title": "Count", "type": "integer"}, "language": {"default": "en", "description": "Language code (ISO 639-1) the templates are written in, natively.", "title": "Language", "type": "string"}, "missing_languages": {"default": false, "description": "true writes every campaign language that has no active template yet, and ignores count and language.", "title": "Missing Languages", "type": "boolean"}}, "title": "CampaignGenerateIn", "type": "object"}, "CampaignTemplateIn": {"properties": {"name": {"title": "Name", "type": "string"}, "subject": {"default": "", "description": "Empty on SMS.", "title": "Subject", "type": "string"}, "body": {"default": "", "description": "Plain text; merge fields are limited to {{first_name}}, {{last_name}}, {{email}}, {{company}}, {{product.title}}, {{product.price}}, {{product.currency}}, {{product.url}}, {{sender.name}}, {{purchase.count}}, {{purchase.total}}, {{purchase.expected_by}}, {{purchase.last_at}}, {{purchase.first_at}}. Ignored on a newsletter, where it is derived from layout.", "title": "Body", "type": "string"}, "layout": {"default": [], "description": "Newsletter blocks in order, each {\"id\", \"type\", ...fields}; types: logo, heading, text, image, button, products, divider, spacer, html, card. logo {align, height 20-120, band true|false} is the header: the brand kit logo (else the workspace name), on a primary-colour band when band is true, heading {text, level 1|2}, text {markdown}, image {url, alt (required), link, width 80-520 or 0 for full width}, button {label, url}, heading, text, image, button and card take align (left, center, right; empty means left), products {product_ids: 1-3}, divider {}, spacer {height 16|32}, html {html: email HTML, sanitised to an allowlist; what is stripped comes back as a warning}, card {background_color #rrggbb, label (optional pill), dark: true inverts the text, blocks: [at least one heading, text, image, button, divider or spacer block, never a card], each with its own id unique across the layout; an error inside a card is numbered card.block, e.g. Block 3.2 (text) needs text; a card in the old shape (title, markdown, button_label, button_url) is read as a heading, a text and a button block inside it}. Every block also takes padding_top, padding_right, padding_bottom and padding_left (0-80 px; omitted means its type's default: 16, 40, 0, 40 for text, image and products, 24, 40, 0, 40 for logo, heading, button, divider and card, 0 for spacer and html, 12, 32, 0, 32 for any block inside a card; a logo with band true repeats its top padding underneath) and a cell background, #rrggbb or empty for none: background_color, except on a card, where it is cell_color because background_color is the card's own fill. Any block may carry hidden true|false (absent means false): a hidden block stays in the layout but is left out of the mail, its plain text, its links and the lint, and only its id is checked; a hidden card hides every block inside it. Heading text, text markdown and a card label may carry the outreach merge fields, e.g. {{ first_name|default:\"there\" }}; an unknown one is refused. URLs are http(s) or mailto. The footer is added on send. The arms of one language share one layout and differ by subject; body is derived from it.", "items": {"additionalProperties": true, "type": "object"}, "title": "Layout", "type": "array"}, "html": {"default": "", "description": "A whole email as HTML, stored as one html block so it can be converted to native blocks later with convert_campaign_template_html; newsletter only, and never together with a non-empty layout.", "title": "Html", "type": "string"}, "theme": {"additionalProperties": true, "default": {}, "description": "Newsletter theme overrides over the brand kit, every key optional: primary_color, accent_color, background_color, card_color, text_color, muted_color (#rrggbb), font (system, arial, georgia, verdana, trebuchet, inter, roboto, open_sans, lato, montserrat, poppins, nunito, playfair, merriweather), card_radius (0-24), button_shape (rounded, pill, square).", "title": "Theme", "type": "object"}, "recipe": {"additionalProperties": true, "default": {}, "description": "{angle, subject_style, tone, length_band, cta_type}; what this angle is, for the people reading it later.", "title": "Recipe", "type": "object"}, "language": {"default": "en", "description": "Language code (ISO 639-1) the template is written in.", "title": "Language", "type": "string"}, "step": {"default": 1, "description": "1 is the first send (on a newsletter its variants A-C, each with its own blocks, theme and subject); on a newsletter 2-10 are the follow-up emails, one per language and step. Outreach templates are always 1.", "maximum": 10, "minimum": 1, "title": "Step", "type": "integer"}, "variant": {"default": "", "description": "A, B or C on a newsletter's first email (step 1), each with its own blocks, theme and subject; empty on later emails and on outreach. Left empty on a newsletter's first email, the next free letter.", "pattern": "^[A-C]?$", "title": "Variant", "type": "string"}}, "required": ["name"], "title": "CampaignTemplateIn", "type": "object"}, "CampaignTemplatePatch": {"properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "subject": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Subject"}, "body": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Body"}, "layout": {"anyOf": [{"items": {"additionalProperties": true, "type": "object"}, "type": "array"}, {"type": "null"}], "description": "Newsletter blocks in order, each {\"id\", \"type\", ...fields}; types: logo, heading, text, image, button, products, divider, spacer, html, card. logo {align, height 20-120, band true|false} is the header: the brand kit logo (else the workspace name), on a primary-colour band when band is true, heading {text, level 1|2}, text {markdown}, image {url, alt (required), link, width 80-520 or 0 for full width}, button {label, url}, heading, text, image, button and card take align (left, center, right; empty means left), products {product_ids: 1-3}, divider {}, spacer {height 16|32}, html {html: email HTML, sanitised to an allowlist; what is stripped comes back as a warning}, card {background_color #rrggbb, label (optional pill), dark: true inverts the text, blocks: [at least one heading, text, image, button, divider or spacer block, never a card], each with its own id unique across the layout; an error inside a card is numbered card.block, e.g. Block 3.2 (text) needs text; a card in the old shape (title, markdown, button_label, button_url) is read as a heading, a text and a button block inside it}. Every block also takes padding_top, padding_right, padding_bottom and padding_left (0-80 px; omitted means its type's default: 16, 40, 0, 40 for text, image and products, 24, 40, 0, 40 for logo, heading, button, divider and card, 0 for spacer and html, 12, 32, 0, 32 for any block inside a card; a logo with band true repeats its top padding underneath) and a cell background, #rrggbb or empty for none: background_color, except on a card, where it is cell_color because background_color is the card's own fill. Any block may carry hidden true|false (absent means false): a hidden block stays in the layout but is left out of the mail, its plain text, its links and the lint, and only its id is checked; a hidden card hides every block inside it. Heading text, text markdown and a card label may carry the outreach merge fields, e.g. {{ first_name|default:\"there\" }}; an unknown one is refused. URLs are http(s) or mailto. The footer is added on send. The arms of one language share one layout and differ by subject; body is derived from it.", "title": "Layout"}, "html": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "A whole email as HTML, stored as one html block so it can be converted to native blocks later with convert_campaign_template_html; newsletter only, and never together with a non-empty layout.", "title": "Html"}, "theme": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "description": "Newsletter theme overrides over the brand kit, every key optional: primary_color, accent_color, background_color, card_color, text_color, muted_color (#rrggbb), font (system, arial, georgia, verdana, trebuchet, inter, roboto, open_sans, lato, montserrat, poppins, nunito, playfair, merriweather), card_radius (0-24), button_shape (rounded, pill, square).", "title": "Theme"}, "recipe": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "title": "Recipe"}, "language": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Language"}, "step": {"anyOf": [{"maximum": 10, "minimum": 1, "type": "integer"}, {"type": "null"}], "description": "1 is the first send (on a newsletter its variants A-C, each with its own blocks, theme and subject); on a newsletter 2-10 are the follow-up emails, one per language and step. Outreach templates are always 1.", "title": "Step"}, "variant": {"anyOf": [{"pattern": "^[A-C]?$", "type": "string"}, {"type": "null"}], "description": "A, B or C on a newsletter's first email (step 1), each with its own blocks, theme and subject; empty on later emails and on outreach.", "title": "Variant"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "false retires the arm: it stops being picked and keeps its history.", "title": "Is Active"}}, "title": "CampaignTemplatePatch", "type": "object"}, "NewsletterGenerateIn": {"properties": {"language": {"default": "en", "description": "Language code (ISO 639-1) the issue is written in.", "title": "Language", "type": "string"}, "step": {"default": 1, "description": "1 is the first send (on a newsletter its variants A-C, each with its own blocks, theme and subject); on a newsletter 2-10 are the follow-up emails, one per language and step. Outreach templates are always 1.", "maximum": 10, "minimum": 1, "title": "Step", "type": "integer"}}, "title": "NewsletterGenerateIn", "type": "object"}, "NewsletterVariantIn": {"properties": {"language": {"default": "en", "description": "Language code (ISO 639-1) of the first email to write another variant of.", "title": "Language", "type": "string"}}, "title": "NewsletterVariantIn", "type": "object"}, "NewsletterTranslateIn": {"properties": {"language": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Language code (ISO 639-1) of the ticked language to fill; empty for every ticked language that lacks an email.", "title": "Language"}}, "title": "NewsletterTranslateIn", "type": "object"}, "RenderedTemplateOut": {"properties": {"html": {"description": "The newsletter as a contact would get it, footer included.", "title": "Html", "type": "string"}, "errors": {"description": "The layout's block errors; empty when it can send.", "items": {"type": "string"}, "title": "Errors", "type": "array"}, "warnings": {"description": "Lint warnings, e.g. what sanitising stripped from an html block.", "items": {"type": "string"}, "title": "Warnings", "type": "array"}}, "required": ["html", "errors", "warnings"], "title": "RenderedTemplateOut", "type": "object"}, "CampaignImageOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "url": {"description": "The public address to put in an image block.", "title": "Url", "type": "string"}, "width": {"title": "Width", "type": "integer"}, "height": {"title": "Height", "type": "integer"}}, "required": ["id", "url", "width", "height"], "title": "CampaignImageOut", "type": "object"}, "CampaignEnrolmentOut": {"properties": {"enduser_id": {"format": "uuid", "title": "Enduser Id", "type": "string"}, "email": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Email"}, "template_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Template Id"}, "ticket_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Ticket Id"}, "holdout": {"title": "Holdout", "type": "boolean"}, "sent_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Sent At"}, "replied_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Replied At"}, "converted_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Converted At"}, "closed_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Closed At"}, "close_reason": {"title": "Close Reason", "type": "string"}, "language": {"default": "", "description": "The language this contact was written to.", "title": "Language", "type": "string"}, "has_draft": {"default": false, "description": "AI mode wrote this contact a draft; one not yet approved on an open enrolment waits in list_campaign_drafts.", "title": "Has Draft", "type": "boolean"}, "approved_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Approved At"}, "approved_by": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Email of the person who approved the draft.", "title": "Approved By"}}, "required": ["enduser_id", "email", "template_id", "ticket_id", "holdout", "sent_at", "replied_at", "converted_at", "closed_at", "close_reason", "approved_at"], "title": "CampaignEnrolmentOut", "type": "object"}, "PagedCampaignEnrolmentOut": {"properties": {"items": {"items": {"$ref": "#/components/schemas/CampaignEnrolmentOut"}, "title": "Items", "type": "array"}, "count": {"title": "Count", "type": "integer"}}, "required": ["items", "count"], "title": "PagedCampaignEnrolmentOut", "type": "object"}, "CampaignEstimateOut": {"properties": {"contacts": {"title": "Contacts", "type": "integer"}, "per_contact_writing": {"description": "What one AI-written email costs; 0 in templates mode.", "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", "title": "Per Contact Writing", "type": "string"}, "writing_total": {"pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", "title": "Writing Total", "type": "string"}, "research_contacts": {"description": "Companies among the contacts with no finished research profile.", "title": "Research Contacts", "type": "integer"}, "per_research": {"description": "What researching one company costs, quoted even while research is off.", "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", "title": "Per Research", "type": "string"}, "research_total": {"description": "Non-zero only in AI mode with research_missing.", "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", "title": "Research Total", "type": "string"}, "no_company_contacts": {"description": "Contacts without a company: they get what we already have.", "title": "No Company Contacts", "type": "integer"}, "total": {"description": "Writing plus research; SMS sending is not in it.", "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", "title": "Total", "type": "string"}, "currency": {"description": "The workspace currency every amount but the SMS ones is in.", "title": "Currency", "type": "string"}, "segments_per_contact": {"default": 0, "description": "SMS: the parts the longest active template costs; 0 for email.", "title": "Segments Per Contact", "type": "integer"}, "per_segment": {"default": "0", "description": "SMS: what one part costs.", "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", "title": "Per Segment", "type": "string"}, "sending_total": {"default": "0", "description": "SMS: parts x price over the contacts texted (holdout excluded); 0 for email, which sends free.", "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", "title": "Sending Total", "type": "string"}, "sending_currency": {"default": "", "description": "Currency of per_segment and sending_total: the workspace one, or EUR while no rate converts it.", "title": "Sending Currency", "type": "string"}}, "required": ["contacts", "per_contact_writing", "writing_total", "research_contacts", "per_research", "research_total", "no_company_contacts", "total", "currency"], "title": "CampaignEstimateOut", "type": "object"}, "CampaignDraftOut": {"properties": {"enrolment_id": {"format": "uuid", "title": "Enrolment Id", "type": "string"}, "enduser_id": {"format": "uuid", "title": "Enduser Id", "type": "string"}, "first_name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "First Name"}, "last_name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Last Name"}, "email": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Email"}, "company_name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Company Name"}, "language": {"title": "Language", "type": "string"}, "subject": {"title": "Subject", "type": "string"}, "body": {"title": "Body", "type": "string"}, "draft_facts": {"additionalProperties": true, "description": "Language and source, template, company, cited facts; fallback true means the template stood in.", "title": "Draft Facts", "type": "object"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}}, "required": ["enrolment_id", "enduser_id", "first_name", "last_name", "email", "company_name", "language", "subject", "body", "draft_facts", "created_at"], "title": "CampaignDraftOut", "type": "object"}, "CampaignDraftApproveIn": {"properties": {"subject": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Replaces the written subject before sending.", "title": "Subject"}, "body": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Replaces the written body before sending.", "title": "Body"}}, "title": "CampaignDraftApproveIn", "type": "object"}, "BrandKitOut": {"properties": {"logo_url": {"title": "Logo Url", "type": "string"}, "logo_on_color_url": {"title": "Logo On Color Url", "type": "string"}, "primary_color": {"title": "Primary Color", "type": "string"}, "accent_color": {"title": "Accent Color", "type": "string"}, "font_stack": {"title": "Font Stack", "type": "string"}, "legal_line": {"title": "Legal Line", "type": "string"}, "powered_by": {"title": "Powered By", "type": "boolean"}}, "required": ["logo_url", "logo_on_color_url", "primary_color", "accent_color", "font_stack", "legal_line", "powered_by"], "title": "BrandKitOut", "type": "object"}, "BrandKitIn": {"properties": {"logo_url": {"default": "", "description": "http(s) address of the logo at the top of every newsletter.", "title": "Logo Url", "type": "string"}, "logo_on_color_url": {"default": "", "description": "http(s) address of a white or single-colour logo for surfaces in the brand colour, such as the chat Home header; empty shows the workspace name there.", "title": "Logo On Color Url", "type": "string"}, "primary_color": {"default": "", "description": "#rrggbb; empty means the colour the widget had before the kit existed, else near-black.", "title": "Primary Color", "type": "string"}, "accent_color": {"default": "", "description": "#rrggbb; empty means the primary colour.", "title": "Accent Color", "type": "string"}, "font_stack": {"default": "", "description": "One of system, arial, georgia, verdana, trebuchet, inter, roboto, open_sans, lato, montserrat, poppins, nunito, playfair, merriweather; serif and rounded still mean georgia and trebuchet; empty means system.", "title": "Font Stack", "type": "string"}, "legal_line": {"default": "", "description": "One legal line under the footer address, which is the workspace address.", "title": "Legal Line", "type": "string"}, "powered_by": {"default": true, "description": "Show the \u201cPowered by Zellwo\u201d line under the chat widget and customer emails; on by default.", "title": "Powered By", "type": "boolean"}}, "title": "BrandKitIn", "type": "object"}, "NewsletterLayoutOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "description": {"title": "Description", "type": "string"}, "layout": {"items": {"additionalProperties": true, "type": "object"}, "title": "Layout", "type": "array"}, "theme": {"additionalProperties": true, "title": "Theme", "type": "object"}, "builtin_key": {"default": "", "description": "The key of a built-in layout, which is read-only; empty for one a person saved.", "title": "Builtin Key", "type": "string"}, "is_favorite": {"default": false, "description": "Starred on the Templates page: listed first and preferred when generating a newsletter.", "title": "Is Favorite", "type": "boolean"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}}, "required": ["id", "name", "description", "layout", "theme", "created_at", "updated_at"], "title": "NewsletterLayoutOut", "type": "object"}, "NewsletterLayoutIn": {"properties": {"name": {"maxLength": 80, "title": "Name", "type": "string"}, "description": {"default": "", "maxLength": 300, "title": "Description", "type": "string"}, "layout": {"default": [], "description": "Newsletter blocks in order, each {\"id\", \"type\", ...fields}; types: logo, heading, text, image, button, products, divider, spacer, html, card. logo {align, height 20-120, band true|false} is the header: the brand kit logo (else the workspace name), on a primary-colour band when band is true, heading {text, level 1|2}, text {markdown}, image {url, alt (required), link, width 80-520 or 0 for full width}, button {label, url}, heading, text, image, button and card take align (left, center, right; empty means left), products {product_ids: 1-3}, divider {}, spacer {height 16|32}, html {html: email HTML, sanitised to an allowlist; what is stripped comes back as a warning}, card {background_color #rrggbb, label (optional pill), dark: true inverts the text, blocks: [at least one heading, text, image, button, divider or spacer block, never a card], each with its own id unique across the layout; an error inside a card is numbered card.block, e.g. Block 3.2 (text) needs text; a card in the old shape (title, markdown, button_label, button_url) is read as a heading, a text and a button block inside it}. Every block also takes padding_top, padding_right, padding_bottom and padding_left (0-80 px; omitted means its type's default: 16, 40, 0, 40 for text, image and products, 24, 40, 0, 40 for logo, heading, button, divider and card, 0 for spacer and html, 12, 32, 0, 32 for any block inside a card; a logo with band true repeats its top padding underneath) and a cell background, #rrggbb or empty for none: background_color, except on a card, where it is cell_color because background_color is the card's own fill. Any block may carry hidden true|false (absent means false): a hidden block stays in the layout but is left out of the mail, its plain text, its links and the lint, and only its id is checked; a hidden card hides every block inside it. Heading text, text markdown and a card label may carry the outreach merge fields, e.g. {{ first_name|default:\"there\" }}; an unknown one is refused. URLs are http(s) or mailto. The footer is added on send. The arms of one language share one layout and differ by subject; body is derived from it.", "items": {"additionalProperties": true, "type": "object"}, "title": "Layout", "type": "array"}, "theme": {"additionalProperties": true, "default": {}, "description": "Newsletter theme overrides over the brand kit, every key optional: primary_color, accent_color, background_color, card_color, text_color, muted_color (#rrggbb), font (system, arial, georgia, verdana, trebuchet, inter, roboto, open_sans, lato, montserrat, poppins, nunito, playfair, merriweather), card_radius (0-24), button_shape (rounded, pill, square).", "title": "Theme", "type": "object"}}, "required": ["name"], "title": "NewsletterLayoutIn", "type": "object"}, "NewsletterLayoutReplaceIn": {"properties": {"layout": {"default": [], "description": "Newsletter blocks in order, each {\"id\", \"type\", ...fields}; types: logo, heading, text, image, button, products, divider, spacer, html, card. logo {align, height 20-120, band true|false} is the header: the brand kit logo (else the workspace name), on a primary-colour band when band is true, heading {text, level 1|2}, text {markdown}, image {url, alt (required), link, width 80-520 or 0 for full width}, button {label, url}, heading, text, image, button and card take align (left, center, right; empty means left), products {product_ids: 1-3}, divider {}, spacer {height 16|32}, html {html: email HTML, sanitised to an allowlist; what is stripped comes back as a warning}, card {background_color #rrggbb, label (optional pill), dark: true inverts the text, blocks: [at least one heading, text, image, button, divider or spacer block, never a card], each with its own id unique across the layout; an error inside a card is numbered card.block, e.g. Block 3.2 (text) needs text; a card in the old shape (title, markdown, button_label, button_url) is read as a heading, a text and a button block inside it}. Every block also takes padding_top, padding_right, padding_bottom and padding_left (0-80 px; omitted means its type's default: 16, 40, 0, 40 for text, image and products, 24, 40, 0, 40 for logo, heading, button, divider and card, 0 for spacer and html, 12, 32, 0, 32 for any block inside a card; a logo with band true repeats its top padding underneath) and a cell background, #rrggbb or empty for none: background_color, except on a card, where it is cell_color because background_color is the card's own fill. Any block may carry hidden true|false (absent means false): a hidden block stays in the layout but is left out of the mail, its plain text, its links and the lint, and only its id is checked; a hidden card hides every block inside it. Heading text, text markdown and a card label may carry the outreach merge fields, e.g. {{ first_name|default:\"there\" }}; an unknown one is refused. URLs are http(s) or mailto. The footer is added on send. The arms of one language share one layout and differ by subject; body is derived from it.", "items": {"additionalProperties": true, "type": "object"}, "title": "Layout", "type": "array"}, "theme": {"additionalProperties": true, "default": {}, "description": "Newsletter theme overrides over the brand kit, every key optional: primary_color, accent_color, background_color, card_color, text_color, muted_color (#rrggbb), font (system, arial, georgia, verdana, trebuchet, inter, roboto, open_sans, lato, montserrat, poppins, nunito, playfair, merriweather), card_radius (0-24), button_shape (rounded, pill, square).", "title": "Theme", "type": "object"}}, "title": "NewsletterLayoutReplaceIn", "type": "object"}, "NewsletterLayoutFavoriteIn": {"properties": {"favorite": {"description": "True stars the layout, false unstars it.", "title": "Favorite", "type": "boolean"}}, "required": ["favorite"], "title": "NewsletterLayoutFavoriteIn", "type": "object"}, "EventOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "enduser_id": {"format": "uuid", "title": "Enduser Id", "type": "string"}, "enduser_email": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Enduser Email"}, "event_type": {"title": "Event Type", "type": "string"}, "source": {"title": "Source", "type": "string"}, "note": {"title": "Note", "type": "string"}, "title": {"title": "Title", "type": "string"}, "url": {"title": "Url", "type": "string"}, "original_event_type": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Original Event Type"}, "properties": {"type": "object", "additionalProperties": true, "title": "Properties"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}}, "required": ["id", "enduser_id", "enduser_email", "event_type", "source", "note", "title", "url", "original_event_type", "properties", "created_at"], "title": "EventOut", "type": "object"}, "PagedEventOut": {"properties": {"items": {"items": {"$ref": "#/components/schemas/EventOut"}, "title": "Items", "type": "array"}, "count": {"title": "Count", "type": "integer"}}, "required": ["items", "count"], "title": "PagedEventOut", "type": "object"}, "LogEventIn": {"properties": {"enduser_id": {"format": "uuid", "title": "Enduser Id", "type": "string"}, "event_type": {"title": "Event Type", "type": "string"}, "note": {"default": "", "title": "Note", "type": "string"}, "properties": {"type": "object", "additionalProperties": true, "default": {}, "title": "Properties"}}, "required": ["enduser_id", "event_type"], "title": "LogEventIn", "type": "object"}, "EventTypeOut": {"properties": {"name": {"title": "Name", "type": "string"}, "description": {"title": "Description", "type": "string"}, "is_system": {"title": "Is System", "type": "boolean"}, "is_active": {"title": "Is Active", "type": "boolean"}, "seen_30d": {"default": 0, "title": "Seen 30D", "type": "integer"}, "rule_count": {"default": 0, "title": "Rule Count", "type": "integer"}}, "required": ["name", "description", "is_system", "is_active"], "title": "EventTypeOut", "type": "object"}, "EventTypeIn": {"properties": {"name": {"title": "Name", "type": "string"}, "description": {"default": "", "title": "Description", "type": "string"}}, "required": ["name"], "title": "EventTypeIn", "type": "object"}, "EventTypePatch": {"properties": {"description": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Description"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Active"}}, "title": "EventTypePatch", "type": "object"}, "EventRuleOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "description": {"title": "Description", "type": "string"}, "source_event_type": {"title": "Source Event Type", "type": "string"}, "match_field": {"title": "Match Field", "type": "string"}, "match_type": {"title": "Match Type", "type": "string"}, "match_key": {"title": "Match Key", "type": "string"}, "match_value": {"title": "Match Value", "type": "string"}, "target_event_type": {"title": "Target Event Type", "type": "string"}, "priority": {"title": "Priority", "type": "integer"}, "stop_processing": {"title": "Stop Processing", "type": "boolean"}, "is_active": {"title": "Is Active", "type": "boolean"}}, "required": ["id", "name", "description", "source_event_type", "match_field", "match_type", "match_key", "match_value", "target_event_type", "priority", "stop_processing", "is_active"], "title": "EventRuleOut", "type": "object"}, "EventRuleIn": {"properties": {"name": {"title": "Name", "type": "string"}, "target_event_type": {"title": "Target Event Type", "type": "string"}, "source_event_type": {"default": "*", "title": "Source Event Type", "type": "string"}, "match_field": {"default": "url", "enum": ["url", "referrer", "title", "event_type", "customer_information"], "title": "Match Field", "type": "string"}, "match_type": {"default": "contains", "enum": ["contains", "exact", "starts_with", "ends_with", "regex", "exists", "not_exists"], "title": "Match Type", "type": "string"}, "match_key": {"default": "", "title": "Match Key", "type": "string"}, "match_value": {"default": "", "title": "Match Value", "type": "string"}, "description": {"default": "", "title": "Description", "type": "string"}, "priority": {"default": 100, "title": "Priority", "type": "integer"}, "stop_processing": {"default": true, "title": "Stop Processing", "type": "boolean"}, "is_active": {"default": true, "title": "Is Active", "type": "boolean"}}, "required": ["name", "target_event_type"], "title": "EventRuleIn", "type": "object"}, "EventRulePatch": {"properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "target_event_type": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Target Event Type"}, "source_event_type": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Source Event Type"}, "match_field": {"anyOf": [{"enum": ["url", "referrer", "title", "event_type", "customer_information"], "type": "string"}, {"type": "null"}], "title": "Match Field"}, "match_type": {"anyOf": [{"enum": ["contains", "exact", "starts_with", "ends_with", "regex", "exists", "not_exists"], "type": "string"}, {"type": "null"}], "title": "Match Type"}, "match_key": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Match Key"}, "match_value": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Match Value"}, "description": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Description"}, "priority": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Priority"}, "stop_processing": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Stop Processing"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Active"}}, "title": "EventRulePatch", "type": "object"}, "EventRuleTestOut": {"properties": {"total_checked": {"title": "Total Checked", "type": "integer"}, "total_matches": {"title": "Total Matches", "type": "integer"}, "matches": {"items": {"additionalProperties": true, "type": "object"}, "title": "Matches", "type": "array"}}, "required": ["total_checked", "total_matches", "matches"], "title": "EventRuleTestOut", "type": "object"}, "StatusOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "stage": {"title": "Stage", "type": "string"}, "name": {"title": "Name", "type": "string"}, "substatus": {"title": "Substatus", "type": "string"}, "color": {"title": "Color", "type": "string"}, "order": {"title": "Order", "type": "integer"}, "is_default": {"title": "Is Default", "type": "boolean"}, "is_active": {"title": "Is Active", "type": "boolean"}, "entering_rules": {"default": [], "items": {"type": "string"}, "title": "Entering Rules", "type": "array"}, "leaving_rules": {"default": [], "items": {"type": "string"}, "title": "Leaving Rules", "type": "array"}}, "required": ["id", "stage", "name", "substatus", "color", "order", "is_default", "is_active"], "title": "StatusOut", "type": "object"}, "StatusIn": {"properties": {"stage": {"enum": ["cold", "interested", "not_interested", "consideration", "order_flow", "active_customer", "slipping_away", "churned", "lost", "stale", "follow_up", "not_a_buyer"], "title": "Stage", "type": "string"}, "name": {"title": "Name", "type": "string"}, "substatus": {"default": "", "title": "Substatus", "type": "string"}, "color": {"default": "#6B7280", "title": "Color", "type": "string"}, "order": {"default": 0, "title": "Order", "type": "integer"}, "is_default": {"default": false, "title": "Is Default", "type": "boolean"}, "is_active": {"default": true, "title": "Is Active", "type": "boolean"}}, "required": ["stage", "name"], "title": "StatusIn", "type": "object"}, "StatusPatch": {"properties": {"stage": {"anyOf": [{"enum": ["cold", "interested", "not_interested", "consideration", "order_flow", "active_customer", "slipping_away", "churned", "lost", "stale", "follow_up", "not_a_buyer"], "type": "string"}, {"type": "null"}], "title": "Stage"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "substatus": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Substatus"}, "color": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Color"}, "order": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Order"}, "is_default": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Default"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Active"}}, "title": "StatusPatch", "type": "object"}, "MigrateOut": {"properties": {"migrated_endusers": {"title": "Migrated Endusers", "type": "integer"}, "from_status": {"title": "From Status", "type": "string"}, "to_status": {"title": "To Status", "type": "string"}}, "required": ["migrated_endusers", "from_status", "to_status"], "title": "MigrateOut", "type": "object"}, "MigrateIn": {"properties": {"to_status_id": {"format": "uuid", "title": "To Status Id", "type": "string"}}, "required": ["to_status_id"], "title": "MigrateIn", "type": "object"}, "RuleOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "description": {"title": "Description", "type": "string"}, "mode": {"title": "Mode", "type": "string"}, "trigger_signal": {"title": "Trigger Signal", "type": "string"}, "from_stages": {"items": {"type": "string"}, "title": "From Stages", "type": "array"}, "from_statuses": {"items": {"type": "string"}, "title": "From Statuses", "type": "array"}, "to_status_id": {"format": "uuid", "title": "To Status Id", "type": "string"}, "to_status_name": {"title": "To Status Name", "type": "string"}, "priority": {"title": "Priority", "type": "integer"}, "time_anchor": {"title": "Time Anchor", "type": "string"}, "since_event": {"title": "Since Event", "type": "string"}, "unless_event": {"title": "Unless Event", "type": "string"}, "for_duration": {"title": "For Duration", "type": "string"}, "is_active": {"title": "Is Active", "type": "boolean"}, "conditions": {"additionalProperties": true, "title": "Conditions", "type": "object"}}, "required": ["id", "name", "description", "mode", "trigger_signal", "from_stages", "from_statuses", "to_status_id", "to_status_name", "priority", "time_anchor", "since_event", "unless_event", "for_duration", "is_active", "conditions"], "title": "RuleOut", "type": "object"}, "RuleIn": {"properties": {"mode": {"default": "event", "enum": ["event", "scheduled"], "title": "Mode", "type": "string"}, "to_status_id": {"format": "uuid", "title": "To Status Id", "type": "string"}, "trigger_signal": {"default": "", "title": "Trigger Signal", "type": "string"}, "from_stages": {"default": [], "items": {"enum": ["cold", "interested", "not_interested", "consideration", "order_flow", "active_customer", "slipping_away", "churned", "lost", "stale", "follow_up", "not_a_buyer"], "type": "string"}, "title": "From Stages", "type": "array"}, "from_statuses": {"default": [], "items": {"format": "uuid", "type": "string"}, "title": "From Statuses", "type": "array"}, "time_anchor": {"default": "", "enum": ["", "status", "event", "inactivity"], "title": "Time Anchor", "type": "string"}, "since_event": {"default": "", "title": "Since Event", "type": "string"}, "unless_event": {"default": "", "title": "Unless Event", "type": "string"}, "duration_amount": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Duration Amount"}, "duration_unit": {"default": "days", "enum": ["minutes", "hours", "days"], "title": "Duration Unit", "type": "string"}, "name": {"default": "", "title": "Name", "type": "string"}, "description": {"default": "", "title": "Description", "type": "string"}, "priority": {"default": 0, "title": "Priority", "type": "integer"}, "is_active": {"default": true, "title": "Is Active", "type": "boolean"}, "conditions": {"additionalProperties": true, "default": {}, "title": "Conditions", "type": "object"}}, "required": ["to_status_id"], "title": "RuleIn", "type": "object"}, "RulePatch": {"properties": {"mode": {"anyOf": [{"enum": ["event", "scheduled"], "type": "string"}, {"type": "null"}], "title": "Mode"}, "to_status_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "To Status Id"}, "trigger_signal": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Trigger Signal"}, "from_stages": {"anyOf": [{"items": {"enum": ["cold", "interested", "not_interested", "consideration", "order_flow", "active_customer", "slipping_away", "churned", "lost", "stale", "follow_up", "not_a_buyer"], "type": "string"}, "type": "array"}, {"type": "null"}], "title": "From Stages"}, "from_statuses": {"anyOf": [{"items": {"format": "uuid", "type": "string"}, "type": "array"}, {"type": "null"}], "title": "From Statuses"}, "time_anchor": {"anyOf": [{"enum": ["", "status", "event", "inactivity"], "type": "string"}, {"type": "null"}], "title": "Time Anchor"}, "since_event": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Since Event"}, "unless_event": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Unless Event"}, "duration_amount": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Duration Amount"}, "duration_unit": {"anyOf": [{"enum": ["minutes", "hours", "days"], "type": "string"}, {"type": "null"}], "title": "Duration Unit"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "description": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Description"}, "priority": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Priority"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Active"}, "conditions": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "title": "Conditions"}}, "title": "RulePatch", "type": "object"}, "RuleTestOut": {"properties": {"total_checked": {"title": "Total Checked", "type": "integer"}, "matching": {"items": {"additionalProperties": true, "type": "object"}, "title": "Matching", "type": "array"}}, "required": ["total_checked", "matching"], "title": "RuleTestOut", "type": "object"}, "RecipeOut": {"properties": {"name": {"title": "Name", "type": "string"}, "sentence": {"title": "Sentence", "type": "string"}, "description": {"title": "Description", "type": "string"}}, "required": ["name", "sentence", "description"], "title": "RecipeOut", "type": "object"}, "AdoptIn": {"properties": {"name": {"title": "Name", "type": "string"}}, "required": ["name"], "title": "AdoptIn", "type": "object"}, "SetupOut": {"properties": {"statuses": {"title": "Statuses", "type": "integer"}, "rules_created": {"title": "Rules Created", "type": "integer"}, "prompts_created": {"title": "Prompts Created", "type": "integer"}}, "required": ["statuses", "rules_created", "prompts_created"], "title": "SetupOut", "type": "object"}, "PromptOut": {"properties": {"id": {"title": "Id", "type": "integer"}, "prompt_type": {"title": "Prompt Type", "type": "string"}, "crm_status_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Crm Status Id"}, "crm_status_name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Crm Status Name"}, "department_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Department Id"}, "department_name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Department Name"}, "content": {"title": "Content", "type": "string"}, "todo_list": {"items": {"type": "string"}, "title": "Todo List", "type": "array"}, "is_active": {"title": "Is Active", "type": "boolean"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}}, "required": ["id", "prompt_type", "crm_status_id", "crm_status_name", "department_id", "department_name", "content", "todo_list", "is_active", "created_at", "updated_at"], "title": "PromptOut", "type": "object"}, "PromptIn": {"properties": {"prompt_type": {"enum": ["company_main", "eval_criteria", "crm_behavior", "profiling", "outreach", "analytics_notes", "department"], "title": "Prompt Type", "type": "string"}, "content": {"title": "Content", "type": "string"}, "crm_status_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Crm Status Id"}, "department_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "description": "For department and eval_criteria prompts: the department this prompt is for; leave empty on eval_criteria for the whole workspace.", "title": "Department Id"}, "todo_list": {"default": [], "items": {"type": "string"}, "title": "Todo List", "type": "array"}, "is_active": {"default": true, "title": "Is Active", "type": "boolean"}}, "required": ["prompt_type", "content"], "title": "PromptIn", "type": "object"}, "PromptPatch": {"properties": {"prompt_type": {"anyOf": [{"enum": ["company_main", "eval_criteria", "crm_behavior", "profiling", "outreach", "analytics_notes", "department"], "type": "string"}, {"type": "null"}], "title": "Prompt Type"}, "content": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Content"}, "crm_status_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Crm Status Id"}, "department_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "description": "For department and eval_criteria prompts: the department this prompt is for; leave empty on eval_criteria for the whole workspace.", "title": "Department Id"}, "todo_list": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], "title": "Todo List"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Active"}}, "title": "PromptPatch", "type": "object"}, "PromptPreviewOut": {"properties": {"assembled_prompt": {"title": "Assembled Prompt", "type": "string"}, "is_included": {"title": "Is Included", "type": "boolean"}, "exclusion_reason": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Exclusion Reason"}}, "required": ["assembled_prompt", "is_included", "exclusion_reason"], "title": "PromptPreviewOut", "type": "object"}, "PagedProductOut": {"properties": {"items": {"items": {"$ref": "#/components/schemas/ProductOut"}, "title": "Items", "type": "array"}, "count": {"title": "Count", "type": "integer"}}, "required": ["items", "count"], "title": "PagedProductOut", "type": "object"}, "ProductOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "title": {"title": "Title", "type": "string"}, "description": {"title": "Description", "type": "string"}, "price": {"anyOf": [{"pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", "type": "string"}, {"type": "null"}], "description": "null = price on request", "title": "Price"}, "currency": {"title": "Currency", "type": "string"}, "sku": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Sku"}, "url": {"title": "Url", "type": "string"}, "image_url": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Image Url"}, "brand": {"title": "Brand", "type": "string"}, "group_key": {"title": "Group Key", "type": "string"}, "availability": {"title": "Availability", "type": "string"}, "availability_date": {"anyOf": [{"format": "date", "type": "string"}, {"type": "null"}], "title": "Availability Date"}, "stock_quantity": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Stock Quantity"}, "sale_price": {"anyOf": [{"pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", "type": "string"}, {"type": "null"}], "title": "Sale Price"}, "sale_price_until": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Sale Price Until"}, "condition": {"title": "Condition", "type": "string"}, "gtin": {"title": "Gtin", "type": "string"}, "mpn": {"title": "Mpn", "type": "string"}, "category": {"title": "Category", "type": "string"}, "images": {"items": {"type": "string"}, "title": "Images", "type": "array"}, "attributes": {"additionalProperties": true, "title": "Attributes", "type": "object"}, "is_active": {"title": "Is Active", "type": "boolean"}, "source_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Source Id"}, "external_id": {"title": "External Id", "type": "string"}, "synced_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Synced At"}}, "required": ["id", "title", "description", "currency", "sku", "url", "image_url", "brand", "group_key", "availability", "availability_date", "stock_quantity", "sale_price", "sale_price_until", "condition", "gtin", "mpn", "category", "images", "attributes", "is_active", "source_id", "external_id", "synced_at"], "title": "ProductOut", "type": "object"}, "ProductIn": {"properties": {"title": {"title": "Title", "type": "string"}, "price": {"anyOf": [{"type": "number"}, {"pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", "type": "string"}, {"type": "null"}], "description": "Omit or null for price on request", "title": "Price"}, "description": {"default": "", "title": "Description", "type": "string"}, "currency": {"default": "EUR", "title": "Currency", "type": "string"}, "sku": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Sku"}, "url": {"default": "", "title": "Url", "type": "string"}, "image_url": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Image Url"}, "brand": {"default": "", "title": "Brand", "type": "string"}, "group_key": {"default": "", "description": "Variants of one product share it (Google's item_group_id).", "title": "Group Key", "type": "string"}, "availability": {"default": "in_stock", "description": "in_stock, out_of_stock, preorder or backorder.", "title": "Availability", "type": "string"}, "availability_date": {"anyOf": [{"format": "date", "type": "string"}, {"type": "null"}], "title": "Availability Date"}, "stock_quantity": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Stock Quantity"}, "sale_price": {"anyOf": [{"type": "number"}, {"pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", "type": "string"}, {"type": "null"}], "title": "Sale Price"}, "sale_price_until": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Sale Price Until"}, "condition": {"default": "new", "description": "new, refurbished or used.", "title": "Condition", "type": "string"}, "gtin": {"default": "", "title": "Gtin", "type": "string"}, "mpn": {"default": "", "title": "Mpn", "type": "string"}, "category": {"default": "", "description": "A path with \" > \", e.g. \"Tyres > Winter\".", "title": "Category", "type": "string"}, "images": {"default": [], "items": {"type": "string"}, "title": "Images", "type": "array"}, "attributes": {"additionalProperties": true, "default": {}, "description": "Workspace product properties by key, validated against the definitions with target product; an unknown key or a bad value is a 422.", "title": "Attributes", "type": "object"}, "is_active": {"default": true, "title": "Is Active", "type": "boolean"}}, "required": ["title"], "title": "ProductIn", "type": "object"}, "ProductPatch": {"properties": {"title": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Title"}, "price": {"anyOf": [{"type": "number"}, {"pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", "type": "string"}, {"type": "null"}], "title": "Price"}, "description": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Description"}, "currency": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Currency"}, "sku": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Sku"}, "url": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Url"}, "image_url": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Image Url"}, "brand": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Brand"}, "group_key": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Group Key"}, "availability": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Availability"}, "availability_date": {"anyOf": [{"format": "date", "type": "string"}, {"type": "null"}], "title": "Availability Date"}, "stock_quantity": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Stock Quantity"}, "sale_price": {"anyOf": [{"type": "number"}, {"pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", "type": "string"}, {"type": "null"}], "title": "Sale Price"}, "sale_price_until": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Sale Price Until"}, "condition": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Condition"}, "gtin": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Gtin"}, "mpn": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Mpn"}, "category": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Category"}, "images": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], "title": "Images"}, "attributes": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "description": "Workspace product properties by key, validated against the definitions with target product; an unknown key or a bad value is a 422. Merged into the stored attributes.", "title": "Attributes"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Active"}}, "title": "ProductPatch", "type": "object"}, "CatalogueSourceOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "type": {"title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "cadence": {"title": "Cadence", "type": "string"}, "next_due_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Next Due At"}, "is_active": {"title": "Is Active", "type": "boolean"}, "config": {"additionalProperties": true, "title": "Config", "type": "object"}, "has_credentials": {"title": "Has Credentials", "type": "boolean"}, "last_sync": {"anyOf": [{"$ref": "#/components/schemas/CatalogueSyncOut"}, {"type": "null"}]}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}}, "required": ["id", "type", "name", "cadence", "next_due_at", "is_active", "config", "has_credentials", "last_sync", "created_at"], "title": "CatalogueSourceOut", "type": "object"}, "CatalogueSyncOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "started_at": {"format": "date-time", "title": "Started At", "type": "string"}, "finished_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Finished At"}, "status": {"title": "Status", "type": "string"}, "total": {"title": "Total", "type": "integer"}, "created": {"title": "Created", "type": "integer"}, "updated": {"title": "Updated", "type": "integer"}, "unchanged": {"title": "Unchanged", "type": "integer"}, "deactivated": {"title": "Deactivated", "type": "integer"}, "failed": {"title": "Failed", "type": "integer"}, "errors": {"items": {"type": "string"}, "title": "Errors", "type": "array"}, "unmapped_keys": {"additionalProperties": {"type": "integer"}, "description": "Feed keys no product property defines yet, with how many rows carried each.", "title": "Unmapped Keys", "type": "object"}}, "required": ["id", "started_at", "finished_at", "status", "total", "created", "updated", "unchanged", "deactivated", "failed", "errors", "unmapped_keys"], "title": "CatalogueSyncOut", "type": "object"}, "PagedCatalogueSourceOut": {"properties": {"items": {"items": {"$ref": "#/components/schemas/CatalogueSourceOut"}, "title": "Items", "type": "array"}, "count": {"title": "Count", "type": "integer"}}, "required": ["items", "count"], "title": "PagedCatalogueSourceOut", "type": "object"}, "CatalogueSourceIn": {"properties": {"type": {"description": "One of google_feed, csv, json, shopify, woocommerce.", "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "cadence": {"default": "daily", "description": "One of hourly, six_hourly, daily, manual.", "title": "Cadence", "type": "string"}, "config": {"additionalProperties": true, "default": {}, "description": "Keys by type \u2014 google_feed: url, fields (optional), attributes (optional), templates (optional), defaults (optional), base_url (optional); csv: url, delimiter (optional), fields (optional), attributes (optional), templates (optional), defaults (optional), base_url (optional); json: url, root (optional), next (optional), max_pages (optional), fields (optional), attributes (optional), templates (optional), defaults (optional), base_url (optional); shopify: shop, api_version (optional); woocommerce: base_url. Shopify: an empty api_version means 2025-07; metafields (optional) lists `namespace.key` metafields to read as attributes. Feeds: fields is {product column: feed path} (a list of paths tries each, null drops the canonical guess), attributes is {property key: feed path}, templates fills a column from `{placeholders}`, defaults are the fallback values, base_url joins relative links; JSON: root is the dotted path of the record list, next the dotted path of the next-page URL (same host only), max_pages caps the walk. `path` is set only by a file upload.", "title": "Config", "type": "object"}, "credentials": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "description": "Write-only, never returned; `has_credentials` says whether any are stored. Keys by type \u2014 google_feed: username, password, token; csv: username, password, token; json: username, password, token; shopify: access_token; woocommerce: consumer_key, consumer_secret. A feed takes them only when protected: username and password (basic auth), or a token sent as `Authorization: Bearer <token>`.", "title": "Credentials"}, "is_active": {"default": true, "title": "Is Active", "type": "boolean"}}, "required": ["type", "name"], "title": "CatalogueSourceIn", "type": "object"}, "CatalogueSourcePatch": {"properties": {"type": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Type"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "cadence": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Cadence"}, "config": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "title": "Config"}, "credentials": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "description": "Write-only, never returned; `has_credentials` says whether any are stored. Keys by type \u2014 google_feed: username, password, token; csv: username, password, token; json: username, password, token; shopify: access_token; woocommerce: consumer_key, consumer_secret. A feed takes them only when protected: username and password (basic auth), or a token sent as `Authorization: Bearer <token>`. `null` clears them.", "title": "Credentials"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Active"}}, "title": "CatalogueSourcePatch", "type": "object"}, "AttributeProposalOut": {"properties": {"path": {"title": "Path", "type": "string"}, "key": {"title": "Key", "type": "string"}, "label": {"title": "Label", "type": "string"}, "type": {"title": "Type", "type": "string"}, "existing": {"description": "True when a product property with this key already exists (its label and type are shown).", "title": "Existing", "type": "boolean"}}, "required": ["path", "key", "label", "type", "existing"], "title": "AttributeProposalOut", "type": "object"}, "FeedPreviewOut": {"properties": {"format": {"description": "google_feed, csv or json: the `type` the source should get.", "title": "Format", "type": "string"}, "root": {"default": "", "description": "JSON: the dotted path of the record list (`results` for a DRF page, empty for a bare list).", "title": "Root", "type": "string"}, "next": {"default": "", "description": "JSON: the dotted path of the next-page URL when the envelope carries one.", "title": "Next", "type": "string"}, "delimiter": {"default": "", "description": "CSV: the delimiter sniffed on the header line.", "title": "Delimiter", "type": "string"}, "total_hint": {"anyOf": [{"type": "integer"}, {"type": "null"}], "description": "The record count when the feed or envelope says it; null when only the first records were read.", "title": "Total Hint"}, "records": {"description": "The first three raw records.", "items": {}, "title": "Records", "type": "array"}, "keys": {"items": {"$ref": "#/components/schemas/FieldInfoOut"}, "title": "Keys", "type": "array"}, "proposal": {"$ref": "#/components/schemas/MappingProposalOut"}}, "required": ["format", "records", "keys", "proposal"], "title": "FeedPreviewOut", "type": "object"}, "FieldInfoOut": {"properties": {"path": {"description": "The key, dotted one level into nested objects (`specs.rim`).", "title": "Path", "type": "string"}, "sample": {"title": "Sample"}, "kind": {"description": "text, number, boolean, list (of scalars) or object.", "title": "Kind", "type": "string"}, "fill_rate": {"description": "The share of previewed records that carry a value, 0-1.", "title": "Fill Rate", "type": "number"}}, "required": ["path", "sample", "kind", "fill_rate"], "title": "FieldInfoOut", "type": "object"}, "MappingProposalOut": {"properties": {"fields": {"additionalProperties": {"type": "string"}, "description": "Product column \u2192 feed key, matched by name; empty for a Google feed (its names are canonical).", "title": "Fields", "type": "object"}, "attributes": {"items": {"$ref": "#/components/schemas/AttributeProposalOut"}, "title": "Attributes", "type": "array"}, "ignored": {"description": "Keys with nothing to map: nested objects and keys empty in every previewed record.", "items": {"type": "string"}, "title": "Ignored", "type": "array"}}, "required": ["fields", "attributes", "ignored"], "title": "MappingProposalOut", "type": "object"}, "FeedPreviewIn": {"properties": {"url": {"description": "The feed address: a Google product feed (RSS, Atom or TSV), a CSV file or a JSON document / paged API.", "title": "Url", "type": "string"}, "username": {"default": "", "description": "Basic auth, as a Google Merchant feed is fetched; sent with `password`.", "title": "Username", "type": "string"}, "password": {"default": "", "title": "Password", "type": "string"}, "token": {"default": "", "description": "Sent as `Authorization: Bearer <token>` instead of basic auth; a value that starts with a scheme (`Token abc\u2026`) is sent verbatim.", "title": "Token", "type": "string"}}, "required": ["url"], "title": "FeedPreviewIn", "type": "object"}, "SyncAllOut": {"properties": {"queued": {"description": "How many sources were started: every active one whose last run is not still going.", "title": "Queued", "type": "integer"}}, "required": ["queued"], "title": "SyncAllOut", "type": "object"}, "QueuedOut": {"properties": {"queued": {"default": true, "title": "Queued", "type": "boolean"}, "sync_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Sync Id"}}, "title": "QueuedOut", "type": "object"}, "SourceCheckOut": {"properties": {"ok": {"default": true, "title": "Ok", "type": "boolean"}, "records": {"description": "Records seen: the first page of a platform (at most 50), or the feed count when known.", "title": "Records", "type": "integer"}, "sample": {"description": "Up to three raw records.", "items": {}, "title": "Sample", "type": "array"}}, "required": ["records", "sample"], "title": "SourceCheckOut", "type": "object"}, "PagedCatalogueSyncOut": {"properties": {"items": {"items": {"$ref": "#/components/schemas/CatalogueSyncOut"}, "title": "Items", "type": "array"}, "count": {"title": "Count", "type": "integer"}}, "required": ["items", "count"], "title": "PagedCatalogueSyncOut", "type": "object"}, "ArticleOut": {"properties": {"id": {"title": "Id", "type": "integer"}, "slug": {"title": "Slug", "type": "string"}, "category": {"title": "Category", "type": "string"}, "visible": {"title": "Visible", "type": "boolean"}, "public": {"title": "Public", "type": "boolean"}, "pinned": {"title": "Pinned", "type": "boolean"}, "expires_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Expires At"}, "external_id": {"title": "External Id", "type": "string"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}, "translations": {"items": {"$ref": "#/components/schemas/TranslationOut"}, "title": "Translations", "type": "array"}}, "required": ["id", "slug", "category", "visible", "public", "pinned", "expires_at", "external_id", "created_at", "updated_at", "translations"], "title": "ArticleOut", "type": "object"}, "PagedArticleOut": {"properties": {"items": {"items": {"$ref": "#/components/schemas/ArticleOut"}, "title": "Items", "type": "array"}, "count": {"title": "Count", "type": "integer"}}, "required": ["items", "count"], "title": "PagedArticleOut", "type": "object"}, "ExistingArticleOut": {"properties": {"detail": {"title": "Detail", "type": "string"}, "id": {"title": "Id", "type": "integer"}}, "required": ["detail", "id"], "title": "ExistingArticleOut", "type": "object"}, "ArticleIn": {"properties": {"slug": {"default": "", "description": "URL slug, unique. Empty: made from the first title.", "maxLength": 255, "title": "Slug", "type": "string"}, "category": {"default": "", "description": "A category's slug, such as shipping; empty means no category. A slug no category holds yet creates that category, named from it (returns-policy becomes Returns policy). service-update articles are always shown to the bot first until they expire.", "maxLength": 50, "title": "Category", "type": "string"}, "visible": {"default": false, "description": "The bot reads it. Leave false to keep a hidden draft.", "title": "Visible", "type": "boolean"}, "public": {"default": false, "description": "Your public help pages may publish it; a mirror lists articles both public and visible.", "title": "Public", "type": "boolean"}, "pinned": {"default": false, "title": "Pinned", "type": "boolean"}, "expires_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "description": "After this the bot stops reading it; use it for service updates.", "title": "Expires At"}, "external_id": {"default": "", "description": "Your own id for the article, so a seed can be re-run: a second create with the same one is a 409 carrying the existing id.", "maxLength": 100, "title": "External Id", "type": "string"}, "translations": {"items": {"$ref": "#/components/schemas/TranslationIn"}, "minItems": 1, "title": "Translations", "type": "array"}}, "required": ["translations"], "title": "ArticleIn", "type": "object"}, "TranslationIn": {"properties": {"language": {"description": "Any language of the world as a tag, an ISO 639 code or an English name, stored as its ISO 639-1 code (else 639-3): de-DE, deu and German become de, yue stays yue. The translation in that language is updated, else one is added; an unknown language is a 422.", "maxLength": 35, "title": "Language", "type": "string"}, "title": {"maxLength": 255, "minLength": 1, "title": "Title", "type": "string"}, "text": {"description": "The article body the bot reads and quotes.", "maxLength": 20000, "minLength": 1, "title": "Text", "type": "string"}}, "required": ["language", "title", "text"], "title": "TranslationIn", "type": "object"}, "PublicArticleOut": {"properties": {"id": {"title": "Id", "type": "integer"}, "slug": {"title": "Slug", "type": "string"}, "category": {"title": "Category", "type": "string"}, "language": {"description": "The translation shown: the visitor's language, else the default language, else the first written.", "title": "Language", "type": "string"}, "title": {"title": "Title", "type": "string"}, "text": {"title": "Text", "type": "string"}, "preview": {"description": "The first 90 characters of the text as plain text, the way the Help tab lists it.", "title": "Preview", "type": "string"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}}, "required": ["id", "slug", "category", "language", "title", "text", "preview", "updated_at"], "title": "PublicArticleOut", "type": "object"}, "ArticlePatch": {"properties": {"slug": {"anyOf": [{"maxLength": 255, "type": "string"}, {"type": "null"}], "title": "Slug"}, "category": {"anyOf": [{"maxLength": 50, "type": "string"}, {"type": "null"}], "description": "A category's slug, such as shipping; empty means no category. A slug no category holds yet creates that category, named from it (returns-policy becomes Returns policy). service-update articles are always shown to the bot first until they expire.", "title": "Category"}, "visible": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Visible"}, "public": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Public"}, "pinned": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Pinned"}, "expires_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Expires At"}, "external_id": {"anyOf": [{"maxLength": 100, "type": "string"}, {"type": "null"}], "title": "External Id"}, "translations": {"default": [], "description": "Upserted by language; languages not listed stay as they are.", "items": {"$ref": "#/components/schemas/TranslationIn"}, "title": "Translations", "type": "array"}}, "title": "ArticlePatch", "type": "object"}, "SearchResultOut": {"properties": {"article_id": {"title": "Article Id", "type": "integer"}, "title": {"title": "Title", "type": "string"}, "text": {"title": "Text", "type": "string"}, "score": {"anyOf": [{"type": "number"}, {"type": "null"}], "description": "1 minus the cosine distance; null for a service update (always listed first) or a keyword match.", "title": "Score"}}, "required": ["article_id", "title", "text", "score"], "title": "SearchResultOut", "type": "object"}, "SearchIn": {"properties": {"query": {"description": "The customer's question in a few words.", "maxLength": 500, "minLength": 1, "title": "Query", "type": "string"}, "language": {"default": "en", "description": "The customer's language, e.g. de-AT or de; the search tops up from the default language.", "maxLength": 35, "title": "Language", "type": "string"}}, "required": ["query"], "title": "SearchIn", "type": "object"}, "KnowledgeGapOut": {"properties": {"id": {"title": "Id", "type": "integer"}, "status": {"title": "Status", "type": "string"}, "question": {"title": "Question", "type": "string"}, "answer": {"title": "Answer", "type": "string"}, "answered_by": {"title": "Answered By", "type": "string"}, "draft": {"additionalProperties": true, "title": "Draft", "type": "object"}, "article_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Article Id"}, "task_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Task Id"}, "ticket_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Ticket Id"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}}, "required": ["id", "status", "question", "answer", "answered_by", "draft", "article_id", "task_id", "ticket_id", "created_at", "updated_at"], "title": "KnowledgeGapOut", "type": "object"}, "PagedKnowledgeGapOut": {"properties": {"items": {"items": {"$ref": "#/components/schemas/KnowledgeGapOut"}, "title": "Items", "type": "array"}, "count": {"title": "Count", "type": "integer"}}, "required": ["items", "count"], "title": "PagedKnowledgeGapOut", "type": "object"}, "KnowledgeGapPatch": {"properties": {"status": {"const": "dismissed", "description": "dismissed: nothing the knowledge base needs.", "title": "Status", "type": "string"}}, "required": ["status"], "title": "KnowledgeGapPatch", "type": "object"}, "KnowledgeGapDraftIn": {"properties": {"note": {"default": "", "description": "A note for the AI on the stored draft, e.g. \"accepted on every route, not only within Italy\"; the draft is redone with it and every earlier note. Empty: draft or retry without notes.", "maxLength": 2000, "title": "Note", "type": "string"}}, "title": "KnowledgeGapDraftIn", "type": "object"}, "KnowledgeGapApproveIn": {"properties": {"preview": {"default": false, "description": "update only: true holds each other language's carried text as a preview to apply or skip; false (default) writes each one as soon as it is carried over.", "title": "Preview", "type": "boolean"}}, "title": "KnowledgeGapApproveIn", "type": "object"}, "KnowledgeGapLanguagePatch": {"properties": {"status": {"description": "applied: write the previewed text into the article; skipped: leave that language's text as it is.", "enum": ["applied", "skipped"], "title": "Status", "type": "string"}}, "required": ["status"], "title": "KnowledgeGapLanguagePatch", "type": "object"}, "ChannelsOut": {"properties": {"smtp": {"anyOf": [{"$ref": "#/components/schemas/SmtpOut"}, {"type": "null"}]}, "imap": {"anyOf": [{"$ref": "#/components/schemas/ImapOut"}, {"type": "null"}]}, "sendgrid": {"anyOf": [{"$ref": "#/components/schemas/SendGridOut"}, {"type": "null"}]}, "selected_outbound_provider": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Selected Outbound Provider"}, "sendgrid_inbound": {"anyOf": [{"$ref": "#/components/schemas/SendGridInboundOut"}, {"type": "null"}]}, "sms": {"anyOf": [{"$ref": "#/components/schemas/SmsOut"}, {"type": "null"}]}, "widget_embed_key": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Widget Embed Key"}}, "required": ["smtp", "imap", "sendgrid", "selected_outbound_provider", "sendgrid_inbound", "sms", "widget_embed_key"], "title": "ChannelsOut", "type": "object"}, "ImapOut": {"properties": {"imap_host": {"title": "Imap Host", "type": "string"}, "imap_port": {"title": "Imap Port", "type": "integer"}, "imap_username": {"title": "Imap Username", "type": "string"}, "use_ssl": {"title": "Use Ssl", "type": "boolean"}, "mailboxes": {"items": {"type": "string"}, "title": "Mailboxes", "type": "array"}, "unseen_only": {"title": "Unseen Only", "type": "boolean"}, "after_poll": {"title": "After Poll", "type": "string"}, "archive_mailbox": {"title": "Archive Mailbox", "type": "string"}, "last_polled_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Last Polled At"}, "last_poll_error": {"title": "Last Poll Error", "type": "string"}, "department_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Department Id"}, "default_category_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Default Category Id"}, "bot_replies_to_email": {"title": "Bot Replies To Email", "type": "boolean"}, "trust_from_header": {"title": "Trust From Header", "type": "boolean"}, "save_attachments": {"title": "Save Attachments", "type": "boolean"}, "bot_reads_attachments": {"title": "Bot Reads Attachments", "type": "boolean"}, "department_addresses_only": {"title": "Department Addresses Only", "type": "boolean"}, "unthreaded_opens_new_ticket": {"title": "Unthreaded Opens New Ticket", "type": "boolean"}}, "required": ["imap_host", "imap_port", "imap_username", "use_ssl", "mailboxes", "unseen_only", "after_poll", "archive_mailbox", "last_polled_at", "last_poll_error", "department_id", "default_category_id", "bot_replies_to_email", "trust_from_header", "save_attachments", "bot_reads_attachments", "department_addresses_only", "unthreaded_opens_new_ticket"], "title": "ImapOut", "type": "object"}, "SendGridInboundOut": {"properties": {"inbound_address": {"title": "Inbound Address", "type": "string"}, "webhook_url": {"title": "Webhook Url", "type": "string"}, "has_public_key": {"title": "Has Public Key", "type": "boolean"}, "last_inbound_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Last Inbound At"}, "department_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Department Id"}, "default_category_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Default Category Id"}, "bot_replies_to_email": {"title": "Bot Replies To Email", "type": "boolean"}, "trust_from_header": {"title": "Trust From Header", "type": "boolean"}, "save_attachments": {"title": "Save Attachments", "type": "boolean"}, "bot_reads_attachments": {"title": "Bot Reads Attachments", "type": "boolean"}, "department_addresses_only": {"title": "Department Addresses Only", "type": "boolean"}, "unthreaded_opens_new_ticket": {"title": "Unthreaded Opens New Ticket", "type": "boolean"}}, "required": ["inbound_address", "webhook_url", "has_public_key", "last_inbound_at", "department_id", "default_category_id", "bot_replies_to_email", "trust_from_header", "save_attachments", "bot_reads_attachments", "department_addresses_only", "unthreaded_opens_new_ticket"], "title": "SendGridInboundOut", "type": "object"}, "SendGridOut": {"properties": {"email_address": {"title": "Email Address", "type": "string"}, "display_name": {"title": "Display Name", "type": "string"}, "api_key_last4": {"title": "Api Key Last4", "type": "string"}, "is_active": {"title": "Is Active", "type": "boolean"}, "is_selected_outbound": {"title": "Is Selected Outbound", "type": "boolean"}, "chat_reply_as_notification": {"title": "Chat Reply As Notification", "type": "boolean"}, "offer_chat_in_replies": {"title": "Offer Chat In Replies", "type": "boolean"}, "include_quoted_history": {"title": "Include Quoted History", "type": "boolean"}, "track_opens": {"title": "Track Opens", "type": "boolean"}, "signature_html": {"title": "Signature Html", "type": "string"}, "daily_limit": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Daily Limit"}, "events_webhook_url": {"title": "Events Webhook Url", "type": "string"}, "has_events_public_key": {"title": "Has Events Public Key", "type": "boolean"}}, "required": ["email_address", "display_name", "api_key_last4", "is_active", "is_selected_outbound", "chat_reply_as_notification", "offer_chat_in_replies", "include_quoted_history", "track_opens", "signature_html", "daily_limit", "events_webhook_url", "has_events_public_key"], "title": "SendGridOut", "type": "object"}, "SmsOut": {"properties": {"sender": {"title": "Sender", "type": "string"}, "daily_limit": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Daily Limit"}, "messages_sent_today": {"title": "Messages Sent Today", "type": "integer"}, "is_active": {"title": "Is Active", "type": "boolean"}}, "required": ["sender", "daily_limit", "messages_sent_today", "is_active"], "title": "SmsOut", "type": "object"}, "SmtpOut": {"properties": {"email_address": {"title": "Email Address", "type": "string"}, "display_name": {"title": "Display Name", "type": "string"}, "smtp_host": {"title": "Smtp Host", "type": "string"}, "smtp_port": {"title": "Smtp Port", "type": "integer"}, "smtp_username": {"title": "Smtp Username", "type": "string"}, "use_tls": {"title": "Use Tls", "type": "boolean"}, "is_active": {"title": "Is Active", "type": "boolean"}, "chat_reply_as_notification": {"title": "Chat Reply As Notification", "type": "boolean"}, "offer_chat_in_replies": {"title": "Offer Chat In Replies", "type": "boolean"}, "include_quoted_history": {"title": "Include Quoted History", "type": "boolean"}, "track_opens": {"title": "Track Opens", "type": "boolean"}, "signature_html": {"title": "Signature Html", "type": "string"}, "daily_limit": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Daily Limit"}}, "required": ["email_address", "display_name", "smtp_host", "smtp_port", "smtp_username", "use_tls", "is_active", "chat_reply_as_notification", "offer_chat_in_replies", "include_quoted_history", "track_opens", "signature_html", "daily_limit"], "title": "SmtpOut", "type": "object"}, "SmtpIn": {"properties": {"email_address": {"title": "Email Address", "type": "string"}, "smtp_host": {"title": "Smtp Host", "type": "string"}, "smtp_username": {"title": "Smtp Username", "type": "string"}, "smtp_password": {"default": "", "title": "Smtp Password", "type": "string"}, "display_name": {"default": "", "title": "Display Name", "type": "string"}, "smtp_port": {"default": 587, "title": "Smtp Port", "type": "integer"}, "use_tls": {"default": true, "title": "Use Tls", "type": "boolean"}}, "required": ["email_address", "smtp_host", "smtp_username"], "title": "SmtpIn", "type": "object"}, "DeletedOut": {"properties": {"deleted": {"title": "Deleted", "type": "boolean"}, "id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Id"}}, "required": ["deleted"], "title": "DeletedOut", "type": "object"}, "ConnectionTestOut": {"properties": {"success": {"title": "Success", "type": "boolean"}, "error": {"default": "", "title": "Error", "type": "string"}, "folders": {"default": [], "items": {"type": "string"}, "title": "Folders", "type": "array"}}, "required": ["success"], "title": "ConnectionTestOut", "type": "object"}, "ImapIn": {"properties": {"imap_host": {"title": "Imap Host", "type": "string"}, "imap_username": {"title": "Imap Username", "type": "string"}, "imap_password": {"default": "", "title": "Imap Password", "type": "string"}, "imap_port": {"default": 993, "title": "Imap Port", "type": "integer"}, "use_ssl": {"default": true, "title": "Use Ssl", "type": "boolean"}, "mailbox": {"default": "INBOX", "title": "Mailbox", "type": "string"}, "mailboxes": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], "title": "Mailboxes"}, "unseen_only": {"default": true, "title": "Unseen Only", "type": "boolean"}, "after_poll": {"default": "seen", "title": "After Poll", "type": "string"}, "archive_mailbox": {"default": "", "title": "Archive Mailbox", "type": "string"}, "department_addresses_only": {"default": false, "title": "Department Addresses Only", "type": "boolean"}}, "required": ["imap_host", "imap_username"], "title": "ImapIn", "type": "object"}, "ImapPollOut": {"properties": {"success": {"title": "Success", "type": "boolean"}, "error": {"default": "", "title": "Error", "type": "string"}, "processed": {"default": 0, "title": "Processed", "type": "integer"}, "new_tickets": {"default": 0, "title": "New Tickets", "type": "integer"}, "updated_tickets": {"default": 0, "title": "Updated Tickets", "type": "integer"}, "skipped": {"default": 0, "title": "Skipped", "type": "integer"}, "redirected": {"default": 0, "title": "Redirected", "type": "integer"}, "errors": {"default": [], "items": {"type": "string"}, "title": "Errors", "type": "array"}}, "required": ["success"], "title": "ImapPollOut", "type": "object"}, "OutboundSettingsIn": {"properties": {"chat_reply_as_notification": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Chat Reply As Notification"}, "offer_chat_in_replies": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Offer Chat In Replies"}, "include_quoted_history": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Include Quoted History"}, "track_opens": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Track Opens"}, "signature_html": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Signature Html"}, "daily_limit": {"anyOf": [{"minimum": 1, "type": "integer"}, {"type": "null"}], "title": "Daily Limit"}}, "title": "OutboundSettingsIn", "type": "object"}, "InboundSettingsIn": {"properties": {"provider": {"default": "custom_email", "title": "Provider", "type": "string"}, "department_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Department Id"}, "default_category_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Default Category Id"}, "bot_replies_to_email": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Bot Replies To Email"}, "trust_from_header": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Trust From Header"}, "save_attachments": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Save Attachments"}, "bot_reads_attachments": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Bot Reads Attachments"}, "department_addresses_only": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Department Addresses Only"}, "unthreaded_opens_new_ticket": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Unthreaded Opens New Ticket"}}, "title": "InboundSettingsIn", "type": "object"}, "SendGridInboundIn": {"properties": {"inbound_address": {"title": "Inbound Address", "type": "string"}, "public_key": {"default": "", "title": "Public Key", "type": "string"}, "department_addresses_only": {"default": false, "title": "Department Addresses Only", "type": "boolean"}}, "required": ["inbound_address"], "title": "SendGridInboundIn", "type": "object"}, "SendGridIn": {"properties": {"email_address": {"title": "Email Address", "type": "string"}, "sendgrid_api_key": {"default": "", "title": "Sendgrid Api Key", "type": "string"}, "display_name": {"default": "", "title": "Display Name", "type": "string"}}, "required": ["email_address"], "title": "SendGridIn", "type": "object"}, "SendGridEventsIn": {"properties": {"public_key": {"title": "Public Key", "type": "string"}}, "required": ["public_key"], "title": "SendGridEventsIn", "type": "object"}, "SmsIn": {"properties": {"sender": {"default": "", "title": "Sender", "type": "string"}, "daily_limit": {"anyOf": [{"minimum": 1, "type": "integer"}, {"type": "null"}], "title": "Daily Limit"}}, "title": "SmsIn", "type": "object"}, "SmsTestOut": {"properties": {"success": {"title": "Success", "type": "boolean"}, "error": {"default": "", "title": "Error", "type": "string"}, "folders": {"default": [], "items": {"type": "string"}, "title": "Folders", "type": "array"}, "state": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "State"}, "approved": {"default": false, "title": "Approved", "type": "boolean"}, "message": {"default": "", "title": "Message", "type": "string"}}, "required": ["success"], "title": "SmsTestOut", "type": "object"}, "SelectOutboundIn": {"properties": {"provider": {"title": "Provider", "type": "string"}}, "required": ["provider"], "title": "SelectOutboundIn", "type": "object"}, "ConnectorOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "slug": {"title": "Slug", "type": "string"}, "base_url": {"title": "Base Url", "type": "string"}, "host": {"title": "Host", "type": "string"}, "auth": {"title": "Auth", "type": "string"}, "header_name": {"title": "Header Name", "type": "string"}, "param_name": {"title": "Param Name", "type": "string"}, "has_credentials": {"title": "Has Credentials", "type": "boolean"}, "is_active": {"description": "Off until Test connection passes; a new base URL or secret switches it off again.", "title": "Is Active", "type": "boolean"}, "config_version": {"title": "Config Version", "type": "integer"}, "last_checked_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Last Checked At"}, "last_error": {"title": "Last Error", "type": "string"}, "fetch_count": {"title": "Fetch Count", "type": "integer"}, "action_count": {"title": "Action Count", "type": "integer"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}}, "required": ["id", "name", "slug", "base_url", "host", "auth", "header_name", "param_name", "has_credentials", "is_active", "config_version", "last_checked_at", "last_error", "fetch_count", "action_count", "created_at", "updated_at"], "title": "ConnectorOut", "type": "object"}, "PagedConnectorOut": {"properties": {"items": {"items": {"$ref": "#/components/schemas/ConnectorOut"}, "title": "Items", "type": "array"}, "count": {"title": "Count", "type": "integer"}}, "required": ["items", "count"], "title": "PagedConnectorOut", "type": "object"}, "ConnectorIn": {"properties": {"name": {"description": "Shown in the list and on thread lines; the slug (tool name prefix) is made from it once.", "maxLength": 80, "title": "Name", "type": "string"}, "base_url": {"description": "https only; every call must hit this host.", "title": "Base Url", "type": "string"}, "auth": {"default": "none", "description": "none, header (the secret sent in the header named header_name) or query (the secret sent as the query parameter named param_name).", "enum": ["none", "header", "query"], "title": "Auth", "type": "string"}, "header_name": {"default": "", "description": "With auth header, e.g. Authorization.", "maxLength": 80, "title": "Header Name", "type": "string"}, "param_name": {"default": "", "description": "With auth query, e.g. api_key.", "maxLength": 80, "title": "Param Name", "type": "string"}, "secret": {"default": "", "description": "Write-only: sent on every call, never returned. Leave it out to keep the stored one while the auth stays the same.", "title": "Secret", "type": "string"}}, "required": ["name", "base_url"], "title": "ConnectorIn", "type": "object"}, "ConnectorPatch": {"properties": {"name": {"anyOf": [{"maxLength": 80, "type": "string"}, {"type": "null"}], "title": "Name"}, "base_url": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Base Url"}, "auth": {"anyOf": [{"enum": ["none", "header", "query"], "type": "string"}, {"type": "null"}], "description": "none, header (the secret sent in the header named header_name) or query (the secret sent as the query parameter named param_name).", "title": "Auth"}, "header_name": {"anyOf": [{"maxLength": 80, "type": "string"}, {"type": "null"}], "title": "Header Name"}, "param_name": {"anyOf": [{"maxLength": 80, "type": "string"}, {"type": "null"}], "title": "Param Name"}, "secret": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Write-only: sent on every call, never returned. Leave it out to keep the stored one while the auth stays the same.", "title": "Secret"}}, "title": "ConnectorPatch", "type": "object"}, "ConnectionCheckOut": {"properties": {"level": {"enum": ["success", "warning", "danger"], "title": "Level", "type": "string"}, "message": {"title": "Message", "type": "string"}, "is_active": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "Whether the connector is on now; null for a check of typed values, which changes nothing.", "title": "Is Active"}}, "required": ["level", "message", "is_active"], "title": "ConnectionCheckOut", "type": "object"}, "ConnectionCheckIn": {"properties": {"name": {"description": "Shown in the list and on thread lines; the slug (tool name prefix) is made from it once.", "maxLength": 80, "title": "Name", "type": "string"}, "base_url": {"description": "https only; every call must hit this host.", "title": "Base Url", "type": "string"}, "auth": {"default": "none", "description": "none, header (the secret sent in the header named header_name) or query (the secret sent as the query parameter named param_name).", "enum": ["none", "header", "query"], "title": "Auth", "type": "string"}, "header_name": {"default": "", "description": "With auth header, e.g. Authorization.", "maxLength": 80, "title": "Header Name", "type": "string"}, "param_name": {"default": "", "description": "With auth query, e.g. api_key.", "maxLength": 80, "title": "Param Name", "type": "string"}, "secret": {"default": "", "description": "Write-only: sent on every call, never returned. Leave it out to keep the stored one while the auth stays the same.", "title": "Secret", "type": "string"}, "connector_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "description": "The connector being edited: a blank secret then uses its stored one, on the same host only.", "title": "Connector Id"}}, "required": ["name", "base_url"], "title": "ConnectionCheckIn", "type": "object"}, "CallOut": {"properties": {"id": {"title": "Id", "type": "integer"}, "tool": {"title": "Tool", "type": "string"}, "kind": {"title": "Kind", "type": "string"}, "principal": {"title": "Principal", "type": "string"}, "staff_email": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Staff Email"}, "ticket_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Ticket Id"}, "outcome": {"title": "Outcome", "type": "string"}, "status_code": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Status Code"}, "error_code": {"title": "Error Code", "type": "string"}, "message": {"description": "The error code in words.", "title": "Message", "type": "string"}, "error_detail": {"description": "An excerpt of the API's answer or the transport failure, secret blanked.", "title": "Error Detail", "type": "string"}, "request_path": {"description": "Path and query called, never the credential parameter.", "title": "Request Path", "type": "string"}, "duration_ms": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Duration Ms"}, "bytes": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Bytes"}, "is_test": {"title": "Is Test", "type": "boolean"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}}, "required": ["id", "tool", "kind", "principal", "staff_email", "ticket_id", "outcome", "status_code", "error_code", "message", "error_detail", "request_path", "duration_ms", "bytes", "is_test", "created_at"], "title": "CallOut", "type": "object"}, "PagedCallOut": {"properties": {"items": {"items": {"$ref": "#/components/schemas/CallOut"}, "title": "Items", "type": "array"}, "count": {"title": "Count", "type": "integer"}}, "required": ["items", "count"], "title": "PagedCallOut", "type": "object"}, "ToolSummaryOut": {"properties": {"name": {"title": "Name", "type": "string"}, "kind": {"title": "Kind", "type": "string"}, "method": {"title": "Method", "type": "string"}, "path": {"title": "Path", "type": "string"}, "status": {"description": "never tested, changed since the last passing test, or tested.", "enum": ["never", "changed", "tested"], "title": "Status", "type": "string"}, "bot_ready": {"description": "Tested in its current shape and When to use written; only then can a department have it above off.", "title": "Bot Ready", "type": "boolean"}}, "required": ["name", "kind", "method", "path", "status", "bot_ready"], "title": "ToolSummaryOut", "type": "object"}, "ToolOut": {"properties": {"name": {"title": "Name", "type": "string"}, "kind": {"title": "Kind", "type": "string"}, "method": {"title": "Method", "type": "string"}, "path": {"title": "Path", "type": "string"}, "status": {"description": "never tested, changed since the last passing test, or tested.", "enum": ["never", "changed", "tested"], "title": "Status", "type": "string"}, "bot_ready": {"description": "Tested in its current shape and When to use written; only then can a department have it above off.", "title": "Bot Ready", "type": "boolean"}, "description": {"description": "What the bot reads, compiled from docs.", "title": "Description", "type": "string"}, "requires": {"description": "Derived, read-only: verified when the tool fills a non-optional input from the contact (or, for an action, the ticket reference); it then runs only for a proven customer.", "enum": ["none", "verified"], "title": "Requires", "type": "string"}, "spec": {"additionalProperties": true, "description": "The stored spec, with test_values, example, example_raw and the tested stamp.", "title": "Spec", "type": "object"}, "last_answer": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "description": "The raw answer of the last passing Send (kept an hour) or the pasted example, with its time: pick fields from it. A file in it (base64) reads {\"$file\": name, \"bytes\": size, \"mime_type\": type}; set it as the tool's file with save_connector_tool.", "title": "Last Answer"}}, "required": ["name", "kind", "method", "path", "status", "bot_ready", "description", "requires", "spec", "last_answer"], "title": "ToolOut", "type": "object"}, "ToolDocsIn": {"properties": {"when": {"default": "", "description": "When the bot should call it and what to ask the customer first; required before any department can use it.", "maxLength": 600, "title": "When", "type": "string"}, "fields": {"additionalProperties": {"type": "string"}, "default": {}, "description": "A meaning per kept field path, at most 200 characters each.", "title": "Fields", "type": "object"}, "guidance": {"default": "", "description": "How to answer from the result.", "maxLength": 600, "title": "Guidance", "type": "string"}}, "title": "ToolDocsIn", "type": "object"}, "ToolSpecIn": {"properties": {"kind": {"description": "fetch reads, action changes something; fixed once stored.", "enum": ["fetch", "action"], "title": "Kind", "type": "string"}, "method": {"description": "An action never uses GET.", "enum": ["GET", "POST", "PUT", "PATCH", "DELETE"], "title": "Method", "type": "string"}, "path": {"description": "Starts with /, appended to the base URL; {input} placeholders are filled from inputs and must be required.", "title": "Path", "type": "string"}, "params": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "description": "The inputs the bot asks for: a flat JSON-schema object of string, number, integer or boolean properties (enum, pattern, minLength, maxLength, minimum, maximum, description).", "title": "Params"}, "bound": {"anyOf": [{"additionalProperties": {"type": "string"}, "type": "object"}, {"type": "null"}], "description": "Server-filled inputs: input name \u2192 ticket_uuid, external_reference, contact_email, contact_phone or contact_external_id.", "title": "Bound"}, "bound_prefix": {"anyOf": [{"additionalProperties": {"type": "string"}, "type": "object"}, {"type": "null"}], "description": "Per reference-filled input, a literal prefix stripped from the ticket reference (order:AX1 \u2192 AX1); {} clears.", "title": "Bound Prefix"}, "bound_optional": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], "description": "Bound inputs that may be empty: a contact-filled one (or the ticket reference on an Action) is sent as \"\" unless the customer is proven, and the call is never refused for it; [] clears.", "title": "Bound Optional"}, "query": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], "description": "Inputs sent as query parameters.", "title": "Query"}, "body": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "description": "A JSON template whose exact \"{input}\" strings are replaced; a GET sends none.", "title": "Body"}, "fields": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], "description": "Kept dotted paths of the answer (status, events[].date); the bot sees only these.", "title": "Fields"}, "untrusted": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], "description": "Kept string fields that hold free text (stripped of control characters).", "title": "Untrusted"}, "docs": {"anyOf": [{"$ref": "#/components/schemas/ToolDocsIn"}, {"type": "null"}]}, "test_values": {"anyOf": [{"additionalProperties": {"type": "string"}, "type": "object"}, {"type": "null"}], "description": "A test value per input, used by send_connector_tool.", "title": "Test Values"}, "per_ticket_cap": {"anyOf": [{"type": "integer"}, {"type": "null"}], "description": "Deprecated and ignored: the AI agent may make 10 connector calls in one reply, across all tools.", "title": "Per Ticket Cap"}, "prefetch": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "Load into every reply: before each AI reply the tool runs (or reuses its answer from the last 30 minutes on the ticket) and its result goes in the bot's context. Only for a Fetch with no bot inputs and no file, while a department has it on Always allow.", "title": "Prefetch"}, "file": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "description": "A Fetch that returns a file as base64: {\"content\": \"data.base64\", \"name\": \"data.filename\", \"types\": [\"pdf\"]} (dotted paths, types are attachment categories, pdf by default). The file is attached to the reply and never shown to the bot; the content path cannot be a kept field. {} clears.", "title": "File"}}, "required": ["kind", "method", "path"], "title": "ToolSpecIn", "type": "object"}, "PasteOut": {"properties": {"method": {"title": "Method", "type": "string"}, "path": {"title": "Path", "type": "string"}, "inputs": {"description": "One row per input read from the paste, with the pasted value as its test value.", "items": {"additionalProperties": true, "type": "object"}, "title": "Inputs", "type": "array"}, "body": {"additionalProperties": true, "description": "The body template when it is not a flat one.", "title": "Body", "type": "object"}, "warnings": {"items": {"type": "string"}, "title": "Warnings", "type": "array"}, "request": {"additionalProperties": true, "description": "The suggested method, path, params, query, body and test_values to put in save_connector_tool.", "title": "Request", "type": "object"}}, "required": ["method", "path", "inputs", "body", "warnings", "request"], "title": "PasteOut", "type": "object"}, "PasteIn": {"properties": {"paste": {"description": "A cURL command, a full URL on the base URL, or a JSON body; at most 64 KB.", "title": "Paste", "type": "string"}}, "required": ["paste"], "title": "PasteIn", "type": "object"}, "SendIn": {"properties": {"test_values": {"anyOf": [{"additionalProperties": {"type": "string"}, "type": "object"}, {"type": "null"}], "description": "Test values to send (stored on a pass); left out, the tool's stored test_values.", "title": "Test Values"}, "confirm": {"default": false, "description": "An action changes data in your system: true sends it for real, keyed test:<uuid4> so your API can dry-run it.", "title": "Confirm", "type": "boolean"}}, "title": "SendIn", "type": "object"}, "ExampleIn": {"properties": {"example": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"items": {}, "type": "array"}], "description": "The action's answer as your API would send it.", "title": "Example"}}, "required": ["example"], "title": "ExampleIn", "type": "object"}, "FieldsIn": {"properties": {"fields": {"description": "Kept dotted paths of the answer.", "items": {"type": "string"}, "title": "Fields", "type": "array"}, "untrusted": {"default": [], "description": "Kept string fields that hold free text.", "items": {"type": "string"}, "title": "Untrusted", "type": "array"}}, "required": ["fields"], "title": "FieldsIn", "type": "object"}, "AccessOut": {"properties": {"tools": {"items": {"$ref": "#/components/schemas/ToolSummaryOut"}, "title": "Tools", "type": "array"}, "departments": {"items": {"$ref": "#/components/schemas/DepartmentAccessOut"}, "title": "Departments", "type": "array"}}, "required": ["tools", "departments"], "title": "AccessOut", "type": "object"}, "DepartmentAccessOut": {"properties": {"id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "description": "The department's id; null: the Routing row, the bot's access while AI routing files a new conversation; listed only while routing is on.", "title": "Id"}, "name": {"description": "The department's name, or Routing.", "title": "Name", "type": "string"}, "access": {"additionalProperties": {"type": "string"}, "description": "Tool name \u2192 off, ask or allow.", "title": "Access", "type": "object"}}, "required": ["id", "name", "access"], "title": "DepartmentAccessOut", "type": "object"}, "AccessIn": {"properties": {"department_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "description": "The department to set; null sets the Routing row (404 while routing is off).", "title": "Department Id"}, "tool": {"title": "Tool", "type": "string"}, "mode": {"description": "ask: a colleague approves each call; allow: the bot calls it without approval (a Fetch or an Action).", "enum": ["off", "ask", "allow"], "title": "Mode", "type": "string"}}, "required": ["department_id", "tool", "mode"], "title": "AccessIn", "type": "object"}, "IngestEvent": {"additionalProperties": true, "description": "One analytics event as accepted by the ingestion API.\n\nUnknown top-level keys are allowed and merged into properties, so callers\nmay send flat payloads like {\"event\": \"purchase\", \"plan\": \"pro\"}. A plain\npydantic BaseModel rather than ninja's Schema: the Schema resolver extracts\nonly declared fields, which would silently drop the extras.", "properties": {"event": {"title": "Event", "type": "string"}, "email": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Raw address, not hashed; trimmed and lowercased before matching", "title": "Email"}, "phone": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Raw number, not hashed", "title": "Phone"}, "email_verified": {"default": true, "description": "False when the address is unproven, e.g. typed into a public form: it is stored as the contact's declared e-mail instead of the verified one. Honoured as true only from an integration token or the legacy signing secret; from any other token it is always false.", "title": "Email Verified", "type": "boolean"}, "phone_verified": {"default": true, "description": "False when the number is unproven, e.g. typed into a public form: it is stored as the contact's declared number instead of the verified one. Honoured as true only from an integration token or the legacy signing secret; from any other token it is always false.", "title": "Phone Verified", "type": "boolean"}, "external_id": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "The person's id in your own system", "title": "External Id"}, "session_id": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "The visitor session widget.js issued on your pages, read from the first-party cookie zellwo_session_id:<client_id>. Verified against your embed key: a value from anywhere else is discarded, and an event carrying no other identifier is then dropped after its 204. From an integration token, an event naming the person by external_id or a verified email also proves that widget session as them (chat shows their conversations and may run tools for them) until 8 hours pass without such an event; a logout event carrying session_id signs the session out.", "title": "Session Id"}, "first_name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "First Name"}, "last_name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Last Name"}, "company_name": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "The company this person belongs to, created and linked on ingest", "title": "Company Name"}, "company_external_id": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "The company's id in your own system; matched before name or domain, so renaming a company does not split it", "title": "Company External Id"}, "company_website": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Company website; its domain is the match key, and what links their colleagues automatically. Defaults to the domain of a business email address", "title": "Company Website"}, "company_vat_code": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "VAT number, kept on the company record", "title": "Company Vat Code"}, "company_address": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Postal address, kept on the company record", "title": "Company Address"}, "company_country": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "Two-letter country code, kept on the company record", "title": "Company Country"}, "is_business": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "description": "True for a business, false for a private person; stored on the contact as its typed business flag and wins over the one derived from a company link. Also read from properties.is_business when it is a JSON boolean.", "title": "Is Business"}, "url": {"default": "", "title": "Url", "type": "string"}, "referrer": {"default": "", "title": "Referrer", "type": "string"}, "title": {"default": "", "title": "Title", "type": "string"}, "attribution": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "description": "Campaign this visit came from: utm_source, utm_medium, utm_campaign, utm_term, utm_content, gclid, gbraid, wbraid, dclid, fbclid, msclkid, ttclid, li_fat_id, rdt_cid, epik, twclid, sccid, oppref, yclid, irclickid, ga, gcl_aw, gcl_gb, gcl_dc, fbp, fbc, ttp, uetmsclkid, rdt_uuid, scid. Overrides what would be parsed from url, so a later event still reports the campaign that brought the visitor in. Cookie names (_fbp, _ga, \u2026) are accepted as-is", "title": "Attribution"}, "ip": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "The visitor's IP address. Without it the event is attributed to your server's IP and its country", "title": "Ip"}, "user_agent": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "The visitor's User-Agent. Without it their browser, OS and device read as Unknown", "title": "User Agent"}, "timestamp": {"anyOf": [{"maximum": 4102444800000, "minimum": 0, "type": "integer"}, {"type": "null"}], "description": "JS-style milliseconds since epoch", "title": "Timestamp"}, "note": {"default": "", "description": "One sentence on what happened, in the customer's own terms. Shown with the event on the contact timeline and matchable from a CRM rule as metadata__note", "maxLength": 2000, "title": "Note", "type": "string"}, "source": {"anyOf": [{"enum": ["bot", "system"], "type": "string"}, {"type": "null"}], "description": "Who observed the event: 'system' for something your software recorded (a ticket opened, a conversation solved), 'bot' for what your AI agent concluded from a conversation. Honoured only for an MCP & API or OAuth token; on the widget signing secret it is ignored. Stored as an ordinary API event by default, and system events are excluded from activity counts and inactivity-anchored rules the same way Zellwo excludes its own", "title": "Source"}, "properties": {"type": "object", "additionalProperties": true, "title": "Properties"}}, "required": ["event"], "title": "IngestEvent", "type": "object"}, "SqlEventTypeOut": {"properties": {"name": {"title": "Name", "type": "string"}, "description": {"title": "Description", "type": "string"}}, "required": ["name", "description"], "title": "SqlEventTypeOut", "type": "object"}, "SqlSchemaOut": {"properties": {"dialect": {"title": "Dialect", "type": "string"}, "snapshot_built_at": {"format": "date-time", "title": "Snapshot Built At", "type": "string"}, "timezone": {"title": "Timezone", "type": "string"}, "utc_offset": {"title": "Utc Offset", "type": "string"}, "notes": {"title": "Notes", "type": "string"}, "event_types": {"items": {"$ref": "#/components/schemas/SqlEventTypeOut"}, "title": "Event Types", "type": "array"}, "tables": {"items": {"$ref": "#/components/schemas/SqlTableOut"}, "title": "Tables", "type": "array"}}, "required": ["dialect", "snapshot_built_at", "timezone", "utc_offset", "notes", "event_types", "tables"], "title": "SqlSchemaOut", "type": "object"}, "SqlTableOut": {"properties": {"name": {"title": "Name", "type": "string"}, "row_count": {"title": "Row Count", "type": "integer"}, "columns": {"additionalProperties": {"type": "string"}, "title": "Columns", "type": "object"}, "json_keys": {"additionalProperties": {"additionalProperties": {"type": "integer"}, "type": "object"}, "title": "Json Keys", "type": "object"}}, "required": ["name", "row_count", "columns", "json_keys"], "title": "SqlTableOut", "type": "object"}, "SelectOut": {"properties": {"columns": {"items": {"type": "string"}, "title": "Columns", "type": "array"}, "rows": {"items": {"items": {}, "type": "array"}, "title": "Rows", "type": "array"}, "row_count": {"title": "Row Count", "type": "integer"}, "truncated": {"title": "Truncated", "type": "boolean"}, "snapshot_built_at": {"format": "date-time", "title": "Snapshot Built At", "type": "string"}}, "required": ["columns", "rows", "row_count", "truncated", "snapshot_built_at"], "title": "SelectOut", "type": "object"}, "SelectIn": {"properties": {"sql": {"title": "Sql", "type": "string"}}, "required": ["sql"], "title": "SelectIn", "type": "object"}}, "securitySchemes": {"ApiAuth": {"type": "http", "scheme": "bearer"}, "IngestAuth": {"type": "http", "scheme": "bearer"}}}, "servers": []}