{"openapi":"3.0.3","info":{"title":"SmartChat Public API","version":"1.0.0","description":"Public REST API for SmartChat customers (external integrations such as\nZapier, Make and n8n). It is separate from the internal app API used by the\nSmartChat web app.\n\n## Base URL\nProduction: `https://api.smartchat.marketing/v1`.\n\n## Authentication\nSend an API key in the header `Authorization: Bearer sk_live_...`. Each key\nbelongs to exactly one account and carries scopes (for example\n`contacts:read`) that define which endpoints it may use. OAuth access tokens\n(`sc_at_...`) are only for the SmartChat MCP server and are rejected by `/v1`.\nUse `GET /me` to test a connection; `account.name` is a good connection label.\n\n## Scopes\n| Scope | Allows |\n|---|---|\n| `contacts:read` | Read contacts and tags |\n| `contacts:write` | Create, update and tag contacts |\n| `contacts:delete` | Permanently delete contacts |\n| `messages:write` | Send a WhatsApp free-text message or an approved template to a CONFIRMED contact, read send status |\n| `automations:read` | Read automations and their runs |\n| `automations:write` | Create, update, activate and trigger automations |\n| `automations:delete` | Delete automations |\n| `templates:read` | Read templates |\n| `templates:write` | Create and update templates |\n| `templates:submit` | Submit templates to Meta |\n| `templates:delete` | Delete templates |\n| `newsletters:read` | Read newsletters and delivery status |\n| `newsletters:write` | Create and update newsletter drafts (NO sending) |\n| `newsletters:schedule` | Schedule a newsletter or cancel its schedule |\n| `newsletters:delete` | Delete draft or scheduled newsletters |\n| `followups:read` | Read follow-up sequences |\n| `followups:write` | Activate/pause sequences, edit steps |\n| `settings:read` | Read settings |\n| `settings:write` | Change settings |\n| `inbox:read` | Read conversations, messages and the account-wide message list |\n| `webhooks:read` | Read automation webhook addresses (contain a secret) and webhook subscriptions |\n| `webhooks:write` | Create and delete webhook subscriptions (REST hooks) |\n| `team:read` | Read team members (read only) |\n| `stats:read` | Read statistics |\n| `media:read` | Read the media library (read only) |\n\nDeleting, submitting to Meta and scheduling newsletters are deliberately\nseparate from the matching `:write` scope, so an already issued key can\nnever gain more power through an update than it had when it was created.\n\n## Errors\nErrors are always JSON: `{ \"error\": { \"code\": string, \"key\": string, \"message\": string } }`.\n`message` is human-readable English text, `code` is a stable machine code\n(for example `bad_request`, `not_found`, `unauthorized`, `forbidden`,\n`rate_limited`, `account_blocked`, `contact_exists`, `contact_not_optin`,\n`limit_reached`, `idempotency_in_progress`, `idempotency_key_mismatch`), and\n`key` is a finer stable identifier. Clients must branch on `code`/`key`,\nnever on `message`.\n\nA blocked account is rejected on every endpoint with 403 `account_blocked`.\nIf the account status cannot be checked, the API answers 503\n`status_unavailable` instead of letting the request through.\n\n## Rate limit\n120 requests per minute per API key. Exceeding it returns 429\n`rate_limited` with a `Retry-After` header (seconds).\n\n## Pagination\nList endpoints that support it take `limit` and `offset` query parameters\nand return `limit`, `offset`, `has_more` (boolean) and `next_offset`\n(integer or null). Results are newest first with a stable sort:\n`created_at` descending, then `id`.\n\n## Timestamps\nAll timestamps are ISO 8601 strings in UTC.\n\n## Idempotency\nThe header `Idempotency-Key` (1-255 printable ASCII characters, no spaces)\nis accepted on `POST /messages`, `POST /contacts`, `POST /templates`,\n`POST /templates/{id}/submit`, `POST /automations/{id}/trigger` and\n`POST /newsletters/{id}/schedule`.\nThe same key with the same API key and account within 24 hours returns the\nstored first response (same status and body) with the response header\n`Idempotent-Replayed: true`, without executing again. Every final response\nis stored, including errors (4xx, 5xx), because an error such as 502\n`send_failed` does not guarantee that nothing was sent. Only 429 and 503\nare not stored. To deliberately retry after an error, use a new key.\nReplays require the same scope as the original request.\nThe same key with a different request body, path or query string returns 422\n`idempotency_key_mismatch`. The same key while the first request is still\nrunning returns 409 `idempotency_in_progress` (with `Retry-After: 1`). An\ninvalid key format returns 400 `idempotency_key_invalid`.\n\n## Deprecated field names\nSome older responses contain German field names. The English equivalents are\nnow returned alongside them (recursively, anywhere in `/v1` JSON responses).\nThe German names are DEPRECATED and will be removed in a future version.\nPairs: zustand->state, erstes_ereignis->first_event_at,\nletztes_ereignis->last_event_at, anzahl_ereignisse->event_count,\nletzter_fehler->last_error, art->kind, verbindung->connection,\nbestaetigung_gesendet->confirmation_sent, hinweis->notice,\nhinzugefuegt->added, verworfen->rejected, herkunft->origin, anzahl->count,\ngeprueft->checked, gedeckelt->capped, gesendet->sent, zugestellt->delivered,\ngelesen->read, gescheitert->failed, fortsetzbar->resumable, wartet->queued,\nuebersprungen->skipped, kann_fortsetzen->can_resume, grund->reason,\nlage->phase, weiter_ab->resumes_at, abbruch_grund->stop_reason,\ntageslimit->daily_limit, verbunden->connected, war_verbunden->was_connected.\nValue mapping for `state`: laeuft->running,\nwartet_auf_verbindung->waiting_for_first_event,\nnicht_verbunden->not_connected, intern->internal. Value mapping for `kind`:\nkalender->calendar, webhook->webhook, intern->internal. Values of `phase`\nare unchanged.\n","contact":{"name":"SmartChat","url":"https://smartchat.marketing"}},"servers":[{"url":"https://api.smartchat.marketing/v1","description":"Production"},{"url":"http://localhost:3010/v1","description":"Local development"}],"security":[{"ApiKeyAuth":[]}],"tags":[{"name":"Account"},{"name":"Contacts"},{"name":"Messages"},{"name":"Inbox"},{"name":"Automations"},{"name":"Webhooks","description":"Incoming webhook addresses of automations (read only)."},{"name":"Webhook subscriptions","description":"REST hooks: SmartChat sends events to your URL.\n\nDelivery: HTTP POST with a JSON body\n`{ \"id\": string, \"event\": string, \"created_at\": string, \"data\": object }`.\n`id` is a unique event id (e.g. `message.received:<uuid>`). `data` is the\nContact object for `contact.created`, the Message object (same as a\n`GET /messages` item) for `message.received`, and\n`{ id, contact_id, channel, created_at }` for `conversation.created`.\n\nHeaders: `Content-Type: application/json`,\n`User-Agent: SmartChat-Webhooks/1.0`, `X-SmartChat-Event`,\n`X-SmartChat-Delivery` (equals the body `id`), `X-SmartChat-Timestamp`\n(unix seconds), `X-SmartChat-Signature: v1=<hex>` where hex is\nHMAC-SHA256(secret, timestamp + \".\" + raw_body).\n\nReceivers should verify the signature with a constant-time compare, reject\ntimestamps older than 5 minutes, and deduplicate by `X-SmartChat-Delivery`.\nAny 2xx response counts as success. Events are detected within about 30\nseconds. Failed deliveries are retried with backoff after 1 min, 5 min,\n30 min, 2 h, 6 h and 12 h (7 attempts total), then dropped. HTTP 410 Gone\ndisables the subscription immediately. After 25 consecutive failed\nattempts the subscription is disabled automatically. Redirects are not\nfollowed. Timeout is 5 seconds.\n"},{"name":"Templates"},{"name":"Newsletters"},{"name":"Followups"},{"name":"Settings"},{"name":"Team"},{"name":"Stats"},{"name":"Media"},{"name":"Meta"}],"paths":{"/me":{"get":{"operationId":"getMe","tags":["Account"],"summary":"Get the authenticated account and key (test connection)","description":"No scope needed; any valid key works. Use it to test a connection.\n`account.name` is a good connection label. `key` is null when called\nthrough OAuth/MCP.\n","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"account":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"key":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"name":{"type":"string"},"prefix":{"type":"string"}}},"auth_type":{"type":"string","enum":["api_key","oauth"]},"scopes":{"type":"array","items":{"type":"string"}},"rate_limit":{"type":"object","properties":{"requests_per_minute":{"type":"integer","example":120}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/contacts":{"get":{"operationId":"listContacts","tags":["Contacts"],"summary":"List contacts","description":"Requires scope `contacts:read`. Non-deleted contacts of the account,\nnewest first (created_at descending, then id). With `q`: partial match in\nname, email or phone number, case-insensitive. A phone search may contain\nspaces and hyphens (\"+49 151 123\" finds \"+49151123...\").\n","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","maxLength":100},"description":"Search term (name, email or phone, partial match). Empty means no filter."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":100000,"default":0}},{"name":"created_after","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Only contacts created strictly after this time."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"contacts":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},"limit":{"type":"integer"},"offset":{"type":"integer"},"has_more":{"type":"boolean"},"next_offset":{"type":"integer","nullable":true},"q":{"type":"string","description":"The search term actually used (only when q was set)."}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"post":{"operationId":"createContact","tags":["Contacts"],"summary":"Create (or upsert) a contact","description":"Requires scope `contacts:write`. At least `email` or `phone` is required.\nNew contacts start with `optin_whatsapp=none` / `optin_email=none`; a\ndouble opt-in cannot be triggered through this API. The contact must go\nthrough the regular sign-up flow before `/messages` can reach it.\n\nUpsert (body `upsert: true` or query `upsert=true`): if a contact with the\nsame email or phone exists, it is updated (name/email/phone) and 200 is\nreturned with `created: false`. Changing an email or phone resets the\nopt-in for that channel to `none`; the opt-in can never be raised via the\nAPI. Without upsert, a duplicate returns 409 `contact_exists` (key\n`kontakt_email_doppelt`, `kontakt_nummer_doppelt` or `kontakt_doppelt`,\nplus `contact_id`). If the email matches one contact and the phone\nanother, 409 `contact_conflict` is returned with `contact_ids`.\n403 `limit_reached` when the plan's contact limit is reached.\n","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"},{"name":"upsert","in":"query","required":false,"schema":{"type":"boolean","default":false}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":120},"email":{"type":"string","format":"email"},"phone":{"type":"string","description":"International format, e.g. +49170..."},"acquisition":{"type":"string","enum":["marketing","automation","service"],"default":"marketing"},"upsert":{"type":"boolean","default":false}},"example":{"name":"Max Sample","phone":"+491701234567","acquisition":"marketing"}}}}},"responses":{"200":{"description":"Existing contact updated (upsert)","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"created":{"type":"boolean","enum":[false]},"updated":{"type":"boolean"},"contact":{"$ref":"#/components/schemas/Contact"}}}}}},"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"created":{"type":"boolean","enum":[true]},"contact":{"$ref":"#/components/schemas/Contact"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Contact already exists (`contact_exists`), matches two contacts (`contact_conflict`), or idempotency request in progress.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactConflictError"}}}},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/contacts/tags":{"get":{"operationId":"listContactTags","tags":["Contacts"],"summary":"All assigned tags with counts","description":"Requires scope `contacts:read`. If the same name comes from several\norigins, the stronger origin wins for display (system > ki > manuell).\n`capped` = true means the limit of 1000 checked contacts was reached and\nthe count is incomplete.\n","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"type":"array","items":{"$ref":"#/components/schemas/TagCount"}},"checked":{"type":"integer"},"capped":{"type":"boolean"},"geprueft":{"type":"integer","deprecated":true,"description":"Deprecated German alias of checked."},"gedeckelt":{"type":"boolean","deprecated":true,"description":"Deprecated German alias of capped."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/contacts/{id}":{"get":{"operationId":"getContact","tags":["Contacts"],"summary":"Get a contact","description":"Requires scope `contacts:read`.","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"contact":{"$ref":"#/components/schemas/Contact"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"patch":{"operationId":"updateContact","tags":["Contacts"],"summary":"Update a contact","description":"Requires scope `contacts:write`. Only the given fields are changed.\n\nIMPORTANT: if `email` or `phone` changes, the opt-in for EXACTLY THAT\nchannel falls back to `none`. Consent cannot be moved to another address\nor number; through this route an opt-in can only fall, never rise.\n\nThe contact must still have at least an email or a phone number. A\nduplicate email or phone returns 409 `contact_exists`.\n","parameters":[{"$ref":"#/components/parameters/Id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":120,"nullable":true},"email":{"type":"string","format":"email","nullable":true},"phone":{"type":"string","nullable":true,"description":"International format, e.g. +49170..."}},"example":{"name":"Maxi Sample","phone":"+491709999999"}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"contact":{"$ref":"#/components/schemas/Contact"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Another contact already has this email or phone (`contact_exists`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactConflictError"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"delete":{"operationId":"deleteContact","tags":["Contacts"],"summary":"Permanently delete a contact","description":"Requires scope `contacts:delete`; `contacts:write` is NOT enough.\n\nDeletes the same scope as the SmartChat app: pending send jobs,\nconversations and messages, personal automation runs, then the contact\nitself. Events and consent records are ANONYMIZED instead of deleted (the\nstatistics stay consistent, and the proof THAT consent existed remains).\nIf the end customer requested deletion, a deletion confirmation is sent\nto them afterwards (Art. 19 GDPR).\n","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"removed":{"type":"object","additionalProperties":{"type":"integer"},"description":"Number of removed rows per table."},"confirmation_sent":{"type":"boolean"},"bestaetigung_gesendet":{"type":"boolean","deprecated":true,"description":"Deprecated German alias of confirmation_sent."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/contacts/{id}/tags":{"post":{"operationId":"addContactTags","tags":["Contacts"],"summary":"Add tags","description":"Requires scope `contacts:write`. Either `tag` (single) or `tags` (list).\nAdded tags have the origin `manuell` (manual).\n\nSystem tags (e.g. \"hat gekauft\", \"marketing\", \"herkunft: ...\") are\nrejected with 400: they describe what actually happened and must not be\nclaimed by hand. At most 30 tags per contact.\n","parameters":[{"$ref":"#/components/parameters/Id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tag":{"type":"string","maxLength":40},"tags":{"type":"array","items":{"type":"string","maxLength":40}}},"example":{"tags":["regular","newsletter"]}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"tags":{"type":"array","items":{"$ref":"#/components/schemas/ContactTag"}},"added":{"type":"array","items":{"type":"string"}},"rejected":{"type":"array","items":{"type":"string"}},"hinzugefuegt":{"type":"array","items":{"type":"string"},"deprecated":true,"description":"Deprecated German alias of added."},"verworfen":{"type":"array","items":{"type":"string"},"deprecated":true,"description":"Deprecated German alias of rejected."}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/contacts/{id}/tags/{tag}":{"delete":{"operationId":"removeContactTag","tags":["Contacts"],"summary":"Remove one tag","description":"Requires scope `contacts:write`. System tags cannot be removed (400);\nthey are a record, not a label.\n","parameters":[{"$ref":"#/components/parameters/Id"},{"name":"tag","in":"path","required":true,"schema":{"type":"string"},"description":"URL-encoded tag name."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"tags":{"type":"array","items":{"$ref":"#/components/schemas/ContactTag"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/inbox/conversations":{"get":{"operationId":"listConversations","tags":["Inbox"],"summary":"List inbox conversations","description":"Requires scope `inbox:read`. Newest first. `automated_only` marks\nconversations that consist only of automated campaign messages\n(broadcast/follow-up/automation/opt-in), i.e. without a real customer\nreaction.\n\nThere is deliberately NO reply endpoint. To write from an integration,\nuse `POST /messages`, where the opt-in requirement is visible and checked.\n","parameters":[{"name":"channel","in":"query","required":false,"schema":{"type":"string","enum":["whatsapp","email"]}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"conversations":{"type":"array","items":{"$ref":"#/components/schemas/Conversation"}},"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer","description":"Total count"},"has_more":{"type":"boolean"},"next_offset":{"type":"integer","nullable":true,"description":"Offset for the next page","null if there is none":null}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/inbox/conversations/{id}/messages":{"get":{"operationId":"listConversationMessages","tags":["Inbox"],"summary":"Read the history of a conversation","description":"Requires scope `inbox:read`. Returns the NEWEST `limit` messages (default\n25, max 100), oldest first within the page. `offset` pages backwards from\nthe newest end. `automated` marks automated campaign messages, `ai` a\nreply written by the SmartChat AI.\n","parameters":[{"$ref":"#/components/parameters/Id"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"messages":{"type":"array","items":{"$ref":"#/components/schemas/InboxMessage"}},"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer","description":"Total count"},"has_more":{"type":"boolean"},"next_offset":{"type":"integer","nullable":true,"description":"Offset for the next page","null if there is none":null}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/messages":{"get":{"operationId":"listMessages","tags":["Messages"],"summary":"List messages account-wide (polling trigger \"new inbound message\")","description":"Requires scope `inbox:read`. Newest first, stable sort (created_at\ndescending, then id).\n","parameters":[{"name":"direction","in":"query","required":false,"schema":{"type":"string","enum":["inbound","outbound","in","out"],"default":"inbound"},"description":"\"in\"/\"out\" are accepted as aliases of inbound/outbound."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":100000,"default":0}},{"name":"created_after","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Only messages created strictly after this time."},{"name":"conversation_id","in":"query","required":false,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"messages":{"type":"array","items":{"$ref":"#/components/schemas/Message"}},"limit":{"type":"integer"},"offset":{"type":"integer"},"has_more":{"type":"boolean"},"next_offset":{"type":"integer","nullable":true}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"post":{"operationId":"sendWhatsappMessage","tags":["Messages"],"summary":"Send a WhatsApp free-text message or an approved template to a confirmed contact","description":"Requires scope `messages:write`. The contact must already have\n`optin_whatsapp=confirmed` (double opt-in); this route neither creates nor\nbypasses an opt-in. Send EITHER `text` OR `template`, never both.\n\n**Free text (`text`)** is sent immediately (201). WhatsApp rejects free\ntext outside the 24-hour service window (the send then fails with\n`send_failed`).\n\n**Template (`template`)** works at any time, also outside the 24-hour\nwindow. Only the account's own WhatsApp templates with Meta status\n`approved` can be sent (`GET /templates` shows `sendable` and\n`variables`); opt-in confirmation and system templates cannot. Template\nvariables are filled from the contact (`name` = first name with a\nlanguage-specific fallback, `full_name`, `email`, `phone`, custom fields)\nand from `template.variables`. `name`, `full_name`, `email`, `phone` and\na positional `{{1}}` used as greeting always come from the contact and\ncannot be passed. Every other template variable must have a value; unknown\nvariables, empty values, values over 1024 characters, values with line\nbreaks, tabs, control characters or four spaces in a row, and values that\ncontain a placeholder are rejected with 422 `invalid_variables` (the\nmessage names the variables). If the final text would still contain a\nplaceholder, nothing is sent.\n\nA template message is checked immediately (contact, opt-in, opt-out list,\ntemplate status, variables, account lock, monthly quota, connected\nnumber) and then QUEUED (202 with `send_id`). SmartChat sends it within\nseconds through exactly the same path as every other message of the app\n(opt-out and opt-in are checked again right before sending, monthly quota\nis reserved atomically). Follow the result with `GET /sends/{id}`. Messages\ncost no credits; each sent message counts against the monthly message\nquota like any other message.\n\nLinks in the text: http(s) addresses get UTM parameters\n(`utm_source=smartchat`, `utm_medium=whatsapp`, `utm_campaign=api`) and are\nsent as a short link `<app-host>/public/t/c/<id>` so clicks are counted.\nThe short link redirects immediately. Unchanged are SmartChat's own\naddresses, addresses with their own `utm_source`, signed addresses\n(signature, token or expiry parameters also get no UTM) and texts that\nwould become too long.\n","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["contact_id"],"properties":{"contact_id":{"type":"string","format":"uuid"},"text":{"type":"string","maxLength":4000,"description":"Free text. Not together with template."},"template":{"type":"object","description":"Approved template to send. Not together with text.","required":["id"],"properties":{"id":{"type":"string","format":"uuid","description":"Template id from GET /templates."},"variables":{"type":"object","maxProperties":20,"additionalProperties":{"type":"string","maxLength":1024},"description":"Values for template variables that do not come from the contact."}}}}},"examples":{"text":{"value":{"contact_id":"b2c1e6b0-1111-4a2b-9c3d-000000000000","text":"Hello! Your appointment is confirmed."}},"template":{"value":{"contact_id":"b2c1e6b0-1111-4a2b-9c3d-000000000000","template":{"id":"7f3c2a10-2222-4b3c-8d4e-000000000000","variables":{"date":"12 October"}}}}}}}},"responses":{"201":{"description":"Free text sent","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"message_id":{"type":"string","nullable":true,"description":"Provider message id (WhatsApp), if available."}}}}}},"202":{"description":"Template message queued","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"status":{"type":"string","enum":["queued"]},"send_id":{"type":"string","format":"uuid","description":"Use with GET /sends/{id}."},"contact_id":{"type":"string","format":"uuid"},"template":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"text":{"type":"string","description":"Final text as the contact will see it."}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Missing scope, account blocked, sending paused (`subscription_locked`) or monthly quota used up (`limit_reached`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Contact not opted in (`contact_not_optin`), contact opted out (`contact_opted_out`), no phone number, no WhatsApp number connected (`wa_not_connected`), or idempotency request in progress.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Template not approved (`template_not_approved`), not a WhatsApp template (`template_not_whatsapp`), template type not sendable (`template_not_sendable`), invalid or missing variables (`invalid_variables`), or idempotency key reused with a different request (`idempotency_key_mismatch`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"Sending via the provider (WhatsApp) failed (`send_failed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/sends/{id}":{"get":{"operationId":"getSend","tags":["Messages"],"summary":"Status of a queued template message","description":"Requires scope `messages:write`. Only template sends created with\n`POST /messages` (`template`) of this account; everything else is 404.\n","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"send":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["queued","sent","delivered","read","failed","skipped"]},"contact_id":{"type":"string","format":"uuid"},"template_id":{"type":"string","format":"uuid"},"created_at":{"type":"string","format":"date-time"},"sent_at":{"type":"string","format":"date-time","nullable":true},"message_id":{"type":"string","nullable":true,"description":"Provider message id (WhatsApp) once sent."},"reason":{"type":"string","description":"Only for failed/skipped: why it was not sent."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/automations":{"get":{"operationId":"listAutomations","tags":["Automations"],"summary":"List automations","description":"Requires scope `automations:read`.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"automations":{"type":"array","items":{"$ref":"#/components/schemas/AutomationSummary"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"post":{"operationId":"createAutomation","tags":["Automations"],"summary":"Create an automation","description":"Requires scope `automations:write`.\n\nThe same deterministic safety rules apply as in the SmartChat app: an\nautomation with a mailing-list/newsletter intent always gets a double\nopt-in step prepended, and a follow-up sequence disguised as an\nautomation (several messages >= 24 h apart) is reduced to the first,\nimmediate message. So this route never creates an automation that sends\nmarketing to contacts without consent.\n\nTriggers with a webhook (`generic_webhook`, `purchase`) automatically get\nan address; it is available at `GET /webhooks/{id}` (scope `webhooks:read`).\nTrigger type values are fixed identifiers: `datum` = date, `termin` =\nappointment, `inaktivitaet` = inactivity.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","trigger_type","actions"],"properties":{"name":{"type":"string","maxLength":120},"trigger_type":{"$ref":"#/components/schemas/TriggerType"},"trigger_config":{"type":"object","additionalProperties":true},"actions":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/AutomationAction"}},"source":{"type":"string","enum":["generic","manual","shopify"],"default":"generic"},"contact_fields":{"type":"array","items":{"type":"string"},"description":"Fields the form/tool sends. Always contains email OR phone."},"status":{"type":"string","enum":["active","paused","draft"],"default":"active"}},"example":{"name":"New order","trigger_type":"generic_webhook","actions":[{"type":"tag","tag":"customer"}]}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"automation":{"$ref":"#/components/schemas/Automation"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/automations/{id}":{"get":{"operationId":"getAutomation","tags":["Automations"],"summary":"Get an automation","description":"Requires scope `automations:read`. The secret hook token is NOT part of\nthe response; only `has_hook` says whether there is an address. The\naddress itself is available at `GET /webhooks/{id}`.\n","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"automation":{"$ref":"#/components/schemas/Automation"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"patch":{"operationId":"updateAutomation","tags":["Automations"],"summary":"Update an automation (including activate/pause)","description":"Requires scope `automations:write`. `status: \"active\"` activates,\n`\"paused\"`/`\"draft\"` pauses.\n\nTwo behaviors to know: `trigger_config` is MERGED, not replaced (a\npartial update does not silently remove the calendar address). And when\npausing, already queued pending messages of this automation are stopped;\notherwise a delayed action would still go out despite the pause.\n","parameters":[{"$ref":"#/components/parameters/Id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":120},"status":{"type":"string","enum":["active","paused","draft"]},"trigger_type":{"$ref":"#/components/schemas/TriggerType"},"trigger_config":{"type":"object","additionalProperties":true},"actions":{"type":"array","items":{"$ref":"#/components/schemas/AutomationAction"}}},"example":{"status":"active"}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"automation":{"$ref":"#/components/schemas/Automation"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"delete":{"operationId":"deleteAutomation","tags":["Automations"],"summary":"Delete an automation","description":"Requires scope `automations:delete`; `automations:write` is NOT enough.\nPending messages of this automation are stopped first, so nothing goes\nout in its name after deletion.\n","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/automations/{id}/runs":{"get":{"operationId":"listAutomationRuns","tags":["Automations"],"summary":"Runs of an automation","description":"Requires scope `automations:read`. Newest first.","parameters":[{"$ref":"#/components/parameters/Id"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"runs":{"type":"array","items":{"$ref":"#/components/schemas/AutomationRun"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/automations/{id}/trigger":{"post":{"operationId":"triggerAutomation","tags":["Automations"],"summary":"Trigger an automation with a payload","description":"Requires scope `automations:write`. Runs the same action chain as a\nregular trigger (webhook/internal event). There is no test mode; results\nare real (messages are sent, etc.).\n","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"payload":{"type":"object","additionalProperties":true,"description":"Free-form event object passed to the automation."}}}}}},"responses":{"201":{"description":"Run started","content":{"application/json":{"schema":{"type":"object","properties":{"run":{"$ref":"#/components/schemas/AutomationRun"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/webhooks":{"get":{"operationId":"listWebhooks","tags":["Webhooks"],"summary":"List incoming webhook addresses of automations","description":"Requires scope `webhooks:read`.\n\nAn incoming webhook is the inbound address of an automation with a\nwebhook trigger. There is no separate resource: CREATE means\n`POST /automations` with `trigger_type: generic_webhook` (or `purchase`),\nDELETE means deleting the automation. For outgoing event notifications\nsee `/webhook-subscriptions`.\n\nSeparate scope because the address contains the SECRET hook token: whoever\nhas it can trigger the automation without any API key, even after the key\nwas revoked. Rotating the token is deliberately not offered here.\n","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"webhooks":{"type":"array","items":{"$ref":"#/components/schemas/WebhookSummary"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/webhooks/{id}":{"get":{"operationId":"getWebhook","tags":["Webhooks"],"summary":"Setup package of an incoming webhook (address, embed code, state)","description":"Requires scope `webhooks:read`. `{id}` is the automation id. If the\ntrigger needs no address at all (e.g. `keyword`, `datum`), the API\nanswers 400.\n\n`connection.state` answers the question where most setups fail:\n`waiting_for_first_event` (address ready, nothing received yet),\n`running` (at least one real event received), `not_connected` (calendar\nwithout address), `internal` (trigger needs no connection).\n","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDetail"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/webhook-subscriptions":{"get":{"operationId":"listWebhookSubscriptions","tags":["Webhook subscriptions"],"summary":"List webhook subscriptions","description":"Requires scope `webhooks:read`.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"subscriptions":{"type":"array","items":{"$ref":"#/components/schemas/WebhookSubscription"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"post":{"operationId":"createWebhookSubscription","tags":["Webhook subscriptions"],"summary":"Subscribe a URL to an event (REST hook)","description":"Requires scope `webhooks:write` plus the read scope of the event:\n`contact.created` needs `contacts:read`; `message.received` and\n`conversation.created` need `inbox:read`. Only available with an API key\n(not via OAuth/MCP).\n\nThe URL must use https, be at most 2000 characters and resolve to a\npublic IP address. At most 10 active subscriptions per account (409\n`webhook_limit_reached`). The signing secret (`whsec_...`) is returned\nONLY in this response. See the tag description for delivery details.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","event"],"properties":{"url":{"type":"string","format":"uri","maxLength":2000},"event":{"$ref":"#/components/schemas/WebhookEvent"}},"example":{"url":"https://hooks.example.com/smartchat","event":"message.received"}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"subscription":{"$ref":"#/components/schemas/WebhookSubscription"},"secret":{"type":"string","example":"whsec_..."}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/webhook-subscriptions/{id}":{"delete":{"operationId":"deleteWebhookSubscription","tags":["Webhook subscriptions"],"summary":"Delete a webhook subscription","description":"Requires scope `webhooks:write`. 404 if not found in this account.","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/templates":{"get":{"operationId":"listTemplates","tags":["Templates"],"summary":"List templates","description":"Requires scope `templates:read`. Each item also has `variables` (the\nplaceholders of the approved version, in order), `settable_variables`\n(the ones that may be passed in `POST /messages` `template.variables`;\nthe rest comes from the contact), `sendable` (can be sent with\n`POST /messages` right now) and `language`.\n","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"templates":{"type":"array","items":{"$ref":"#/components/schemas/Template"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"post":{"operationId":"createTemplate","tags":["Templates"],"summary":"Create a template","description":"Requires scope `templates:write`. The template is created as a DRAFT; it\nis only submitted deliberately via `POST /templates/{id}/submit` (separate\nscope `templates:submit`), so an unchecked template never burns a scarce\nMeta submission slot.\n\n`purpose` controls the Meta category: `optin_confirm` and `utility` are\nsent as UTILITY, `marketing` as MARKETING. Opt-in templates ALWAYS get\nthe fixed, language-bound confirm button; a custom button label would\nbreak the YES detection in the webhook and thus the double opt-in, and is\nrejected.\n\n`language` matters more than it seems: at Meta a template is identified by\n(name, language). If omitted, the account language applies.\n\nCheck messages (`issues`, `rejection_reason_text`) are English.\n","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","content"],"properties":{"name":{"type":"string","maxLength":120},"content":{"type":"string","maxLength":20000},"channel":{"type":"string","enum":["whatsapp","email"],"default":"whatsapp"},"subject":{"type":"string","description":"Only for channel=email."},"purpose":{"type":"string","enum":["marketing","utility","optin_confirm"],"default":"marketing"},"language":{"type":"string","description":"Meta language code, e.g. de, en_US."},"buttons":{"type":"array","maxItems":1,"items":{"$ref":"#/components/schemas/TemplateButton"}}},"example":{"name":"Welcome","content":"Hi {{1}}, great to have you here!","purpose":"marketing","language":"en_US"}}}}},"responses":{"201":{"description":"Created (draft)","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"template":{"$ref":"#/components/schemas/Template"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/templates/{id}":{"get":{"operationId":"getTemplate","tags":["Templates"],"summary":"Get a template","description":"Requires scope `templates:read`. `state` is the state\n(draft/pending/approved/rejected/paused/disabled), `submittable` says\nwhether submitting makes sense right now, `issues` lists the reasons if not.\n","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"template":{"$ref":"#/components/schemas/Template"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"patch":{"operationId":"updateTemplate","tags":["Templates"],"summary":"Update a template","description":"Requires scope `templates:write`.\n\nANY content change to a WhatsApp template (text, button, language, name,\nheader image) resets `meta_status` to `null`. This is the WABA rule: the\nchanged version must not be sent under the old approval. After a change\nthe template must be submitted again.\n","parameters":[{"$ref":"#/components/parameters/Id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":120},"content":{"type":"string","maxLength":20000},"subject":{"type":"string"},"language":{"type":"string"},"buttons":{"type":"array","maxItems":1,"items":{"$ref":"#/components/schemas/TemplateButton"}},"header_media":{"nullable":true,"description":"Header image of the template; null removes it.","type":"object","additionalProperties":true}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"template":{"$ref":"#/components/schemas/Template"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"delete":{"operationId":"deleteTemplate","tags":["Templates"],"summary":"Delete a template","description":"Requires scope `templates:delete`. If the template is still attached to a\nfollow-up step or an automation, the API answers 409 instead of letting a\nlater send fail. A template known to Meta is also removed there; if that\nfails, the reason is in `notice` - locally it is deleted anyway.\n","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"notice":{"type":"string","nullable":true},"hinweis":{"type":"string","nullable":true,"deprecated":true,"description":"Deprecated German alias of notice."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Template is still attached to a follow-up step or an automation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/templates/{id}/submit":{"post":{"operationId":"submitTemplate","tags":["Templates"],"summary":"Submit a template to Meta","description":"Requires scope `templates:submit`; `templates:write` is NOT enough.\nSeparate scope because something goes OUTSIDE here: Meta strictly caps\nsubmissions per WABA, and rejected templates affect the quality rating of\nthe number.\n\nThe same deterministic check as in the SmartChat app runs first; if the\ntemplate fails, it is NOT submitted at all (400 with the reasons).\nWithout a connected WhatsApp number: also 400.\n\nIf the template is already at Meta (`approved`/`pending`), 200 is\nreturned with `already: true` and a plain-text notice instead of a false\nerror. If the submission did not reach Meta, the answer is 502 with the\nreason, never a sugar-coated `ok`.\n","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"Submitted (or already at Meta)","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"template":{"$ref":"#/components/schemas/Template"},"already":{"type":"boolean"},"notice":{"type":"string","nullable":true},"hinweis":{"type":"string","nullable":true,"deprecated":true,"description":"Deprecated German alias of notice."}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"The submission did not reach Meta (reason in the error message).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/newsletters":{"get":{"operationId":"listNewsletters","tags":["Newsletters"],"summary":"List newsletters","description":"Requires scope `newsletters:read`.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"newsletters":{"type":"array","items":{"$ref":"#/components/schemas/Newsletter"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"post":{"operationId":"createNewsletter","tags":["Newsletters"],"summary":"Create a newsletter draft (NEVER sent)","description":"Requires scope `newsletters:write`. ALWAYS creates only a draft\n(`status=draft`). There is deliberately NO \"send now\" in this API; an\nimmediate mass send to all contacts remains a click in the SmartChat app.\nScheduling is possible via `POST /newsletters/{id}/schedule` with the\nSEPARATE scope `newsletters:schedule`; the sending itself then runs\nthrough the usual safeguards (Meta template approval, opt-in per\nrecipient, credit and subscription gate). WhatsApp only (`channel` must\nbe omitted or `whatsapp`).\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["subject","content"],"properties":{"name":{"type":"string","maxLength":200},"subject":{"type":"string","maxLength":200},"content":{"type":"string","maxLength":20000},"channel":{"type":"string","enum":["whatsapp"]}},"example":{"subject":"New this week","content":"Hello! This week we have..."}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"newsletter":{"$ref":"#/components/schemas/Newsletter"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/newsletters/{id}":{"get":{"operationId":"getNewsletter","tags":["Newsletters"],"summary":"Get a newsletter","description":"Requires scope `newsletters:read`.","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"newsletter":{"$ref":"#/components/schemas/Newsletter"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"patch":{"operationId":"updateNewsletter","tags":["Newsletters"],"summary":"Update a newsletter (button and recipient filter)","description":"Requires scope `newsletters:write`. At least one of `cta` or `segment`\nmust be in the body.\n\n`segment` filters recipients by tags and is checked against the tags\nthat ACTUALLY exist; otherwise a typo would silently mean \"nobody\", which\nwould only show after sending. `segment: null` means \"all confirmed\ncontacts\" again. `cta: null` removes the button.\n\nNewsletters that are already sent or currently sending are locked (400).\n","parameters":[{"$ref":"#/components/parameters/Id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"cta":{"nullable":true,"type":"object","properties":{"text":{"type":"string","maxLength":200},"url":{"type":"string","maxLength":2000}}},"segment":{"nullable":true,"type":"object","additionalProperties":true,"description":"Recipient filter by tags."}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"newsletter":{"$ref":"#/components/schemas/Newsletter"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"delete":{"operationId":"deleteNewsletter","tags":["Newsletters"],"summary":"Delete a newsletter (draft or scheduled only)","description":"Requires scope `newsletters:delete`. Sending (`sending`) and sent (`sent`)\nnewsletters cannot be deleted; a sent newsletter is the record of what\nwent out to contacts (400). Already queued send jobs are removed first.\n","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"removed_jobs":{"type":"integer"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/newsletters/{id}/schedule":{"post":{"operationId":"scheduleNewsletter","tags":["Newsletters"],"summary":"Schedule a newsletter","description":"Requires scope `newsletters:schedule`; `newsletters:write` is NOT enough.\nReason: `newsletters:write` has always explicitly meant \"may create\ndrafts, can never send\". If scheduling fell under it, a long-issued key\ncould trigger a real mass send after an update without the customer ever\ngranting that.\n\nThis route ONLY sets a time. The sending itself then runs through the\nusual path and all safeguards: credit gate, opt-in per recipient, Meta\ntemplate approval, subscription/payment lock.\n\nAllowed is `draft -> scheduled` and rescheduling. The time must be in the\nfuture and at most one year ahead.\n","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["scheduled_at"],"properties":{"scheduled_at":{"type":"string","format":"date-time","description":"ISO time with zone, e.g. 2026-09-10T09:00:00+02:00."}},"example":{"scheduled_at":"2026-09-10T09:00:00+02:00"}}}}},"responses":{"200":{"description":"Scheduled","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"scheduled_at":{"type":"string","format":"date-time","description":"Always stored as UTC."}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/newsletters/{id}/cancel":{"post":{"operationId":"cancelNewsletterSchedule","tags":["Newsletters"],"summary":"Cancel a schedule","description":"Requires scope `newsletters:schedule`. Only possible while the newsletter\nis `scheduled`; it then returns to `draft`.\n","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Schedule cancelled","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"status":{"type":"string","enum":["draft"]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/newsletters/{id}/status":{"get":{"operationId":"getNewsletterStatus","tags":["Newsletters"],"summary":"Delivery status of a newsletter","description":"Requires scope `newsletters:read`. Read-only access to the same counts as\nin the SmartChat app (send jobs per status); changes nothing.\n","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["draft","scheduled","sending","sent"]},"state":{"$ref":"#/components/schemas/NewsletterDeliveryState"},"zustand":{"allOf":[{"$ref":"#/components/schemas/NewsletterDeliveryState"}],"deprecated":true,"description":"Deprecated German alias of state."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/followups":{"get":{"operationId":"listFollowups","tags":["Followups"],"summary":"List follow-up sequences","description":"Requires scope `followups:read`. Changes need `followups:write`.\n\nCREATING a new step is deliberately not offered: the internal path\ngenerates the text with AI and does not bill this call. Through a public\ninterface with 120 requests/minute that would be an unpaid lever on model\ncosts. Text and delay of EXISTING steps can be fully changed.\n","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"followups":{"type":"array","items":{"$ref":"#/components/schemas/FollowupSequence"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/followups/{id}":{"get":{"operationId":"getFollowup","tags":["Followups"],"summary":"Get a follow-up sequence","description":"Requires scope `followups:read`.","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"followup":{"$ref":"#/components/schemas/FollowupSequence"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"patch":{"operationId":"updateFollowupSequence","tags":["Followups"],"summary":"Activate, pause or rename a sequence","description":"Requires scope `followups:write`.\n\n`status: \"active\"` activates the sequence AND schedules already confirmed\ncontacts for its steps (future times only). Without this, existing\ncontacts would never receive newly activated steps.\n","parameters":[{"$ref":"#/components/parameters/Id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["active","paused","draft"]},"name":{"type":"string"}},"example":{"status":"active"}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/followups/steps/{id}":{"patch":{"operationId":"updateFollowupStep","tags":["Followups"],"summary":"Update a follow-up step (text, subject, delay)","description":"Requires scope `followups:write`. Delay as `delay_minutes`,\n`delay_hours` or `delay_days`.\n\nTwo rules apply: the MINIMUM GAP to the other steps of the same sequence\n(otherwise a contact would get two messages shortly after each other), and\nshifting already queued jobs by the difference. `verschobene_auftraege`\n(shifted jobs) says how many pending messages were moved. Never into the\npast: a shortened delay sends from now at the earliest, not\nretroactively as a burst.\n","parameters":[{"$ref":"#/components/parameters/Id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"content":{"type":"string"},"subject":{"type":"string"},"delay_minutes":{"type":"integer","minimum":0},"delay_hours":{"type":"number","minimum":0},"delay_days":{"type":"number","minimum":0}},"example":{"content":"A quick reminder about your offer.","delay_days":3}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"verschobene_auftraege":{"type":"integer","description":"Number of pending jobs that were shifted (field name kept as is)."}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/settings/profile":{"get":{"operationId":"getSettingsProfile","tags":["Settings"],"summary":"Read profile (company name, UI language)","description":"Requires scope `settings:read`.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"ui_language":{"type":"string","enum":["en","de","it","fr","es"]}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"patch":{"operationId":"updateSettingsProfile","tags":["Settings"],"summary":"Update profile","description":"Requires scope `settings:write`. At least one field must be set.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":120},"ui_language":{"type":"string","enum":["en","de","it","fr","es"]}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/settings/brand":{"get":{"operationId":"getSettingsBrand","tags":["Settings"],"summary":"Read brand settings (color, tone, logo, test number)","description":"Requires scope `settings:read`.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"brand":{"$ref":"#/components/schemas/Brand"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"patch":{"operationId":"updateSettingsBrand","tags":["Settings"],"summary":"Update brand settings","description":"Requires scope `settings:write`. A `color` set here counts as chosen by\nthe customer; a later AI build will not overwrite it. `test_phone` is the\nnumber for newsletter test sends (international format; an empty string\nremoves it).\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"color":{"type":"string","pattern":"^#[0-9a-fA-F]{3,8}$"},"tonality":{"type":"string","maxLength":300},"logo_url":{"type":"string","maxLength":500},"test_phone":{"type":"string"}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/settings/legal":{"get":{"operationId":"getSettingsLegal","tags":["Settings"],"summary":"Read imprint/privacy policy URLs","description":"Requires scope `settings:read`.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LegalState"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"patch":{"operationId":"updateSettingsLegal","tags":["Settings"],"summary":"Set imprint/privacy policy URLs","description":"Requires scope `settings:write`. These are the CUSTOMER's URLs; for the\nWhatsApp opt-in the customer is the data controller. URLs on SmartChat's\nown domains are therefore rejected with 400.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"imprint_url":{"type":"string","nullable":true},"privacy_url":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LegalState"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/settings/followup-timing":{"get":{"operationId":"getSettingsFollowupTiming","tags":["Settings"],"summary":"Read follow-up send times","description":"Requires scope `settings:read`.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FollowupTiming"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"patch":{"operationId":"updateSettingsFollowupTiming","tags":["Settings"],"summary":"Update follow-up send times","description":"Requires scope `settings:write`. `send_time` in the format HH:MM,\n`timezone` as an IANA name (e.g. Europe/Berlin). `null` means \"no fixed\ntime\"; timing then stays exact to the second from confirmation. Steps of\nother accounts in `steps` are silently skipped.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"send_time":{"type":"string","nullable":true,"pattern":"^[0-9]{2}:[0-9]{2}$"},"timezone":{"type":"string","nullable":true},"steps":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"send_time":{"type":"string","nullable":true}}}}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/settings/coexistence":{"get":{"operationId":"getSettingsCoexistence","tags":["Settings"],"summary":"Use the WhatsApp Business app at the same time - read state","description":"Requires scope `settings:read`. `available` is only true when a WhatsApp\nnumber is connected.\n","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean"},"available":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"patch":{"operationId":"updateSettingsCoexistence","tags":["Settings"],"summary":"Use the WhatsApp Business app at the same time - toggle","description":"Requires scope `settings:write`. Enabling requires a connected WhatsApp\nnumber (otherwise 400).\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["enabled"],"properties":{"enabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"enabled":{"type":"boolean"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/settings/channels":{"get":{"operationId":"getSettingsChannels","tags":["Settings"],"summary":"Read channel state (read only)","description":"Requires scope `settings:read`. The question an integration should ask\nbefore sending: is a number connected, and is sending currently stopped\n(Meta block, invalid access)? While `versand_moeglich` (sending possible)\nis false, NOTHING goes out - no newsletters, follow-ups or automations.\n\nConnecting and disconnecting a WhatsApp number is deliberately not\noffered: it is a Meta OAuth flow with an access token, and disconnecting\nremoves template bindings including the queue.\n","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelState"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/team":{"get":{"operationId":"listTeam","tags":["Team"],"summary":"List team members (read only)","description":"Requires scope `team:read`. No inviting/removing via this API; account and\npermission changes remain exclusive to the SmartChat app.\n","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"members":{"type":"array","items":{"$ref":"#/components/schemas/TeamMember"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/stats":{"get":{"operationId":"getStats","tags":["Stats"],"summary":"Statistics summary","description":"Requires scope `stats:read`. The same aggregation as the statistics page in the SmartChat app.","parameters":[{"name":"range","in":"query","required":false,"schema":{"type":"string","enum":["7d","30d","90d"],"default":"30d"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Stats"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/media":{"get":{"operationId":"listMedia","tags":["Media"],"summary":"List the media library (read only)","description":"Requires scope `media:read`. No upload via this API: the internal upload\nlogic (file signature check, SVG sanitizing, EXIF removal, WhatsApp\ncompression, duplicate detection) is tied to the internal handler.\n","parameters":[{"name":"kind","in":"query","required":false,"schema":{"type":"string","enum":["image","video"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"media":{"type":"array","items":{"$ref":"#/components/schemas/MediaAsset"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}}},"/openapi.json":{"get":{"operationId":"getOpenApi","tags":["Meta"],"summary":"This API description as JSON","description":"Public, no authentication. `/openapi.yaml` returns the same document (JSON is valid YAML).","security":[],"responses":{"200":{"description":"OpenAPI 3.0.3 document","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}}},"x-webhooks":{"contact.created":{"post":{"summary":"A contact was created","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/WebhookEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Contact"}}}]}}}},"responses":{"200":{"description":"Any 2xx acknowledges the delivery."}}}},"message.received":{"post":{"summary":"An inbound message was received","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/WebhookEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Message"}}}]}}}},"responses":{"200":{"description":"Any 2xx acknowledges the delivery."}}}},"conversation.created":{"post":{"summary":"A conversation was created","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/WebhookEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ConversationCreatedData"}}}]}}}},"responses":{"200":{"description":"Any 2xx acknowledges the delivery."}}}}},"components":{"securitySchemes":{"ApiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"sk_live_ API key","description":"API key in the format `sk_live_...`, sent as `Authorization: Bearer sk_live_...`.\nEach key belongs to one account and carries scopes (see the API\ndescription). OAuth tokens (`sc_at_...`) are rejected by `/v1`.\n"}},"parameters":{"Id":{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[\\x21-\\x7E]+$"},"description":"Optional. 1-255 printable ASCII characters, no spaces. Same key + same API\nkey + same account within 24 hours returns the stored first response\n(also errors, except 429/503) with header `Idempotent-Replayed: true`,\nwithout executing again. Different body, path or query -> 422 `idempotency_key_mismatch`; first request\nstill running -> 409 `idempotency_in_progress` (Retry-After: 1); invalid\nformat -> 400 `idempotency_key_invalid`.\n"}},"schemas":{"Contact":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","nullable":true},"email":{"type":"string","description":"Empty string \"\" if none."},"phone":{"type":"string","description":"E.164; empty string \"\" if none."},"optin_whatsapp":{"$ref":"#/components/schemas/OptinState"},"optin_email":{"$ref":"#/components/schemas/OptinState"},"tags":{"type":"array","items":{"type":"string"}},"language":{"type":"string","nullable":true},"source":{"type":"string"},"created_at":{"type":"string","format":"date-time","description":"There is no updated_at on contacts."}}},"OptinState":{"type":"string","enum":["none","pending","confirmed","opted_out"]},"ContactTag":{"type":"object","description":"A tag WITH origin; system-assigned tags cannot be changed by hand.","properties":{"name":{"type":"string"},"origin":{"$ref":"#/components/schemas/TagOrigin"},"herkunft":{"type":"string","deprecated":true,"description":"Deprecated German alias of origin."}}},"TagCount":{"type":"object","properties":{"name":{"type":"string"},"origin":{"$ref":"#/components/schemas/TagOrigin"},"count":{"type":"integer"},"herkunft":{"type":"string","deprecated":true,"description":"Deprecated German alias of origin."},"anzahl":{"type":"integer","deprecated":true,"description":"Deprecated German alias of count."}}},"TagOrigin":{"type":"string","enum":["system","manuell","ki"],"description":"Stable identifiers: system, manuell (manual), ki (AI)."},"Conversation":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"channel":{"type":"string","enum":["whatsapp","email"]},"origin":{"type":"string","nullable":true,"description":"Where the contact came from (landing page, widget, import, ...)."},"origin_at":{"type":"string","format":"date-time","nullable":true},"automated_only":{"type":"boolean","description":"Only automated campaign messages, no real customer reaction."},"contact":{"type":"object","properties":{"id":{"type":"string","format":"uuid","nullable":true},"name":{"type":"string","nullable":true},"phone":{"type":"string","nullable":true},"email":{"type":"string","nullable":true}}},"last_message":{"type":"string"},"last_message_at":{"type":"string","format":"date-time","nullable":true},"unread_count":{"type":"integer"},"window_expires_at":{"type":"string","format":"date-time","nullable":true,"description":"End of the open WhatsApp 24-hour window."}}},"ConversationCreatedData":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"contact_id":{"type":"string","format":"uuid","nullable":true},"channel":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}},"InboxMessage":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"direction":{"type":"string","enum":["in","out"]},"text":{"type":"string"},"subject":{"type":"string","nullable":true},"media_url":{"type":"string","nullable":true},"media_type":{"type":"string","nullable":true},"media_mime":{"type":"string","nullable":true},"media_name":{"type":"string","nullable":true},"automated":{"type":"boolean","description":"Automated campaign message."},"ai":{"type":"boolean","description":"Written by the SmartChat AI."},"kind":{"type":"string","nullable":true},"reply_to_message_id":{"type":"string","nullable":true},"reply_preview":{"type":"object","nullable":true,"properties":{"text":{"type":"string"},"direction":{"type":"string"}}},"forwarded":{"type":"boolean"},"frequently_forwarded":{"type":"boolean"},"status":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}},"Message":{"type":"object","description":"Item of GET /messages; also the `data` of the message.received webhook event.","properties":{"id":{"type":"string","format":"uuid"},"conversation_id":{"type":"string","format":"uuid"},"contact_id":{"type":"string","format":"uuid","nullable":true},"channel":{"type":"string","description":"whatsapp, email, ..."},"direction":{"type":"string","enum":["inbound","outbound"]},"type":{"type":"string","description":"text, or a media type: image, video, audio, document, sticker, ..."},"text":{"type":"string"},"media_url":{"type":"string","nullable":true},"media_mime":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"TriggerType":{"type":"string","enum":["purchase","signup","keyword","datum","termin","inaktivitaet","generic_webhook"],"description":"Stable identifiers: datum = date, termin = appointment, inaktivitaet = inactivity."},"AutomationSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"trigger_type":{"$ref":"#/components/schemas/TriggerType"},"status":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}},"AutomationAction":{"type":"object","description":"One action of the automation. `message` sends a WhatsApp message (it\nautomatically gets its own template to be submitted to Meta; without it,\nit would never be delivered to contacts without an open 24-hour window),\n`tag` sets a tag, `sequence` starts an EXISTING follow-up sequence,\n`webhook` calls an external URL.\n","properties":{"type":{"type":"string","enum":["message","tag","sequence","webhook"]},"text":{"type":"string","description":"Only for type=message."},"channel":{"type":"string","enum":["whatsapp"]},"delay_minutes":{"type":"integer","minimum":0},"tag":{"type":"string","description":"Only for type=tag."},"sequence_id":{"type":"string","format":"uuid","description":"Only for type=sequence."},"url":{"type":"string","description":"Only for type=webhook."},"condition":{"type":"object","nullable":true,"description":"If/then condition on a field of the event.","properties":{"field":{"type":"string"},"op":{"type":"string","enum":["eq","contains","gt"]},"value":{}}}},"additionalProperties":true},"Automation":{"type":"object","description":"Full automation. The secret hook_token is NEVER included, only has_hook.","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"status":{"type":"string","enum":["active","paused","draft"]},"trigger_type":{"$ref":"#/components/schemas/TriggerType"},"trigger_config":{"type":"object","additionalProperties":true},"actions":{"type":"array","items":{"$ref":"#/components/schemas/AutomationAction"}},"source":{"type":"string","enum":["generic","manual","shopify"]},"has_hook":{"type":"boolean"},"last_triggered_at":{"type":"string","format":"date-time","nullable":true},"trigger_count":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"AutomationRun":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["running","done","failed"]},"error":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"WebhookState":{"type":"string","enum":["running","waiting_for_first_event","not_connected","internal"]},"WebhookSummary":{"type":"object","properties":{"automation_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"trigger_type":{"type":"string"},"status":{"type":"string"},"webhook_url":{"type":"string","nullable":true,"description":"CONTAINS THE SECRET TOKEN - treat it like a password."},"state":{"$ref":"#/components/schemas/WebhookState"},"first_event_at":{"type":"string","format":"date-time","nullable":true},"last_event_at":{"type":"string","format":"date-time","nullable":true},"event_count":{"type":"integer"},"zustand":{"type":"string","enum":["laeuft","wartet_auf_verbindung","nicht_verbunden","intern"],"deprecated":true,"description":"Deprecated German alias of state."},"erstes_ereignis":{"type":"string","format":"date-time","nullable":true,"deprecated":true,"description":"Deprecated German alias of first_event_at."},"letztes_ereignis":{"type":"string","format":"date-time","nullable":true,"deprecated":true,"description":"Deprecated German alias of last_event_at."},"anzahl_ereignisse":{"type":"integer","deprecated":true,"description":"Deprecated German alias of event_count."}}},"WebhookConnection":{"type":"object","additionalProperties":true,"description":"Connection state (state, first/last event, last error).","properties":{"state":{"$ref":"#/components/schemas/WebhookState"},"kind":{"type":"string","enum":["calendar","webhook","internal"]},"first_event_at":{"type":"string","format":"date-time","nullable":true},"last_event_at":{"type":"string","format":"date-time","nullable":true},"event_count":{"type":"integer"},"last_error":{"type":"string","nullable":true},"zustand":{"type":"string","deprecated":true,"description":"Deprecated German alias of state."},"art":{"type":"string","deprecated":true,"description":"Deprecated German alias of kind."},"erstes_ereignis":{"type":"string","nullable":true,"deprecated":true,"description":"Deprecated German alias of first_event_at."},"letztes_ereignis":{"type":"string","nullable":true,"deprecated":true,"description":"Deprecated German alias of last_event_at."},"anzahl_ereignisse":{"type":"integer","deprecated":true,"description":"Deprecated German alias of event_count."},"letzter_fehler":{"type":"string","nullable":true,"deprecated":true,"description":"Deprecated German alias of last_error."}}},"WebhookDetail":{"type":"object","properties":{"automation_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"webhook_url":{"type":"string","description":"CONTAINS THE SECRET TOKEN."},"secret":{"type":"string","description":"The same token on its own - never embed it in customer websites."},"snippet":{"type":"string","description":"Loader URL with the PUBLIC website key."},"snippet_code":{"type":"string","description":"Ready-to-paste embed block for the website."},"curl":{"type":"string"},"instructions":{"type":"string"},"setup_steps":{"type":"array","items":{"type":"string"}},"recommended":{"type":"string","nullable":true},"tool":{"type":"string","nullable":true},"connection":{"$ref":"#/components/schemas/WebhookConnection"},"verbindung":{"allOf":[{"$ref":"#/components/schemas/WebhookConnection"}],"deprecated":true,"description":"Deprecated German alias of connection."}}},"WebhookEvent":{"type":"string","enum":["contact.created","message.received","conversation.created"]},"WebhookSubscription":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"event":{"$ref":"#/components/schemas/WebhookEvent"},"status":{"type":"string","enum":["active","disabled"]},"disabled_reason":{"type":"string","nullable":true,"enum":["too_many_failures","gone","api_key_revoked","scope_removed",null]},"failure_count":{"type":"integer","description":"Consecutive failed attempts."},"last_success_at":{"type":"string","format":"date-time","nullable":true},"last_failure_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"WebhookEnvelope":{"type":"object","properties":{"id":{"type":"string","description":"Unique event id, e.g. \"message.received:<uuid>\". Equals X-SmartChat-Delivery."},"event":{"$ref":"#/components/schemas/WebhookEvent"},"created_at":{"type":"string","format":"date-time"},"data":{"type":"object"}}},"TemplateButton":{"type":"object","properties":{"type":{"type":"string","enum":["QUICK_REPLY","URL"]},"text":{"type":"string","maxLength":25},"url":{"type":"string","description":"Only for type=URL."}}},"Brand":{"type":"object","properties":{"color":{"type":"string"},"tonality":{"type":"string"},"logo_url":{"type":"string"},"test_phone":{"type":"string","description":"Number for newsletter test sends (E.164)."}}},"LegalState":{"type":"object","properties":{"ok":{"type":"boolean"},"legal":{"type":"object","properties":{"imprint_url":{"type":"string"},"privacy_url":{"type":"string"}}},"missing":{"type":"object","properties":{"imprint":{"type":"boolean"},"privacy":{"type":"boolean"}}},"incomplete":{"type":"boolean"},"pages":{"type":"object","additionalProperties":true,"description":"Is a copy of the CONTENT of both pages stored with us (date and character count)?"}}},"FollowupTiming":{"type":"object","properties":{"send_time":{"type":"string","nullable":true,"description":"HH:MM or null (exact to the second from confirmation)."},"timezone":{"type":"string","nullable":true},"default_timezone":{"type":"string"},"steps":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"position":{"type":"integer"},"channel":{"type":"string"},"delay_minutes":{"type":"integer"},"topic":{"type":"string","nullable":true},"send_time":{"type":"string","nullable":true}}}}}},"ChannelState":{"type":"object","properties":{"whatsapp":{"type":"object","properties":{"status":{"type":"string"},"display_number":{"type":"string","nullable":true},"connected":{"type":"boolean"},"was_connected":{"type":"boolean"},"verbunden":{"type":"boolean","deprecated":true,"description":"Deprecated German alias of connected."},"war_verbunden":{"type":"boolean","deprecated":true,"description":"Deprecated German alias of was_connected."}}},"versand_moeglich":{"type":"boolean","description":"Sending possible. false = NOTHING goes out (no number or sending stopped)."},"vorlagen_gesamt":{"type":"integer","description":"Total number of templates."},"vorlagen_offen":{"type":"integer","description":"Number of templates not yet approved."},"versand_gestoppt":{"type":"boolean","description":"Sending is stopped."},"versand_gestoppt_grund":{"type":"string","nullable":true,"description":"Reason sending is stopped."}}},"Template":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"channel":{"type":"string","enum":["whatsapp","email"]},"purpose":{"type":"string","enum":["marketing","utility","optin_confirm"]},"subject":{"type":"string"},"content":{"type":"string"},"language":{"type":"string","nullable":true,"description":"Meta language code; (name, language) identifies the template at Meta."},"buttons":{"type":"array","items":{"$ref":"#/components/schemas/TemplateButton"}},"header_media":{"type":"object","nullable":true,"additionalProperties":true},"state":{"type":"string","enum":["draft","pending","approved","rejected","paused","disabled","ready"],"description":"Template state; 'ready' only for email templates (no Meta approval needed)."},"submittable":{"type":"boolean","description":"Can it be submitted right now?"},"issues":{"type":"array","items":{"type":"string"},"description":"Why it CANNOT be submitted (empty if nothing is in the way)."},"rejection_reason":{"type":"string","nullable":true},"variables":{"type":"array","items":{"type":"string"},"description":"Only in GET /templates: placeholders of the version that can be sent now."},"settable_variables":{"type":"array","items":{"type":"string"},"description":"Only in GET /templates: variables that may be passed in POST /messages template.variables."},"sendable":{"type":"boolean","description":"Only in GET /templates: approved by Meta and sendable with POST /messages."},"meta_status":{"type":"string","nullable":true,"enum":["pending","approved","rejected","paused","disabled",null]}}},"Newsletter":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"subject":{"type":"string"},"channel":{"type":"string","enum":["whatsapp"]},"status":{"type":"string","enum":["draft","scheduled","sending","sent"]},"content":{"type":"string"},"scheduled_at":{"type":"string","format":"date-time","nullable":true},"sent_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"NewsletterDeliveryState":{"type":"object","additionalProperties":true,"description":"Send job counts per status.","properties":{"sent":{"type":"integer","description":"Actually sent (sent + delivered + read)."},"delivered":{"type":"integer","description":"Reported delivered by WhatsApp (delivered + read)."},"read":{"type":"integer","description":"Reported read by WhatsApp."},"failed":{"type":"integer","description":"Finally failed (all reasons)."},"resumable":{"type":"integer","description":"Failed for an account-wide reason; only these can be resumed."},"queued":{"type":"integer","description":"Still in the queue (queued + sending)."},"skipped":{"type":"integer","description":"Skipped (e.g. no opt-in); not an error, but not sent."},"can_resume":{"type":"boolean"},"reason":{"type":"string","nullable":true,"description":"Most common account-wide reason, otherwise null."},"phase":{"type":"string","nullable":true,"description":"Delivery phase (value unchanged)."},"resumes_at":{"type":"string","format":"date-time","nullable":true},"stop_reason":{"type":"string","nullable":true},"daily_limit":{"type":"integer","nullable":true},"gesendet":{"type":"integer","deprecated":true,"description":"Deprecated German alias of sent."},"zugestellt":{"type":"integer","deprecated":true,"description":"Deprecated German alias of delivered."},"gelesen":{"type":"integer","deprecated":true,"description":"Deprecated German alias of read."},"gescheitert":{"type":"integer","deprecated":true,"description":"Deprecated German alias of failed."},"fortsetzbar":{"type":"integer","deprecated":true,"description":"Deprecated German alias of resumable."},"wartet":{"type":"integer","deprecated":true,"description":"Deprecated German alias of queued."},"uebersprungen":{"type":"integer","deprecated":true,"description":"Deprecated German alias of skipped."},"kann_fortsetzen":{"type":"boolean","deprecated":true,"description":"Deprecated German alias of can_resume."},"grund":{"type":"string","nullable":true,"deprecated":true,"description":"Deprecated German alias of reason."},"lage":{"type":"string","nullable":true,"deprecated":true,"description":"Deprecated German alias of phase."},"weiter_ab":{"type":"string","nullable":true,"deprecated":true,"description":"Deprecated German alias of resumes_at."},"abbruch_grund":{"type":"string","nullable":true,"deprecated":true,"description":"Deprecated German alias of stop_reason."},"tageslimit":{"type":"integer","nullable":true,"deprecated":true,"description":"Deprecated German alias of daily_limit."}}},"FollowupStep":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"position":{"type":"integer"},"channel":{"type":"string","enum":["whatsapp"]},"delay_minutes":{"type":"integer"},"delay_hours":{"type":"number"},"subject":{"type":"string","nullable":true},"content":{"type":"string"},"send_time":{"type":"string","nullable":true,"description":"Fixed time HH:MM, otherwise null."}}},"FollowupSequence":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"trigger":{"type":"string"},"status":{"type":"string","enum":["draft","active","paused"]},"steps":{"type":"array","items":{"$ref":"#/components/schemas/FollowupStep"}}}},"TeamMember":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string"},"role":{"type":"string","enum":["owner","admin","member"]},"status":{"type":"string"}}},"Stats":{"type":"object","properties":{"kpis":{"type":"object","properties":{"sent":{"type":"integer"},"delivered":{"type":"integer"},"open_rate":{"type":"number"},"click_rate":{"type":"number"},"clicked":{"type":"integer","nullable":true,"description":"Actual number of recipients with a click; null if clicks are not measurable in the period."},"clicks_measurable":{"type":"boolean","description":"false while no message carried a tracking link; then click_rate 0 is not a measurement."},"contacts_total":{"type":"integer"},"contacts_new":{"type":"integer"},"unread_inbound":{"type":"integer"}}},"timeline":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"sent":{"type":"integer"},"delivered":{"type":"integer"}}}},"status_donut":{"type":"object","properties":{"delivered":{"type":"integer"},"read":{"type":"integer"},"pending":{"type":"integer"},"failed":{"type":"integer"}}},"top_sends":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"channel":{"type":"string"},"sent":{"type":"integer"},"delivered":{"type":"integer"},"opened":{"type":"integer"}}}}}},"MediaAsset":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string","enum":["image","video","audio","pdf","document"]},"usage":{"type":"string"},"url":{"type":"string"},"mime":{"type":"string"},"size_bytes":{"type":"integer"},"original_name":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","key","message"],"properties":{"code":{"type":"string","description":"Stable machine code, e.g. not_found, rate_limited."},"key":{"type":"string","description":"Finer stable identifier for programmatic handling."},"message":{"type":"string","description":"Human-readable English text. Do not branch on it."}}}}},"ContactConflictError":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["contact_exists","contact_conflict","idempotency_in_progress"]},"key":{"type":"string","description":"kontakt_email_doppelt, kontakt_nummer_doppelt or kontakt_doppelt for contact_exists."},"message":{"type":"string"},"contact_id":{"type":"string","format":"uuid","description":"Existing contact (contact_exists)."},"contact_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Both matching contacts (contact_conflict)."}}}}}},"responses":{"BadRequest":{"description":"Invalid request (`bad_request`, `idempotency_key_invalid`, ...)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Missing or invalid API key (`unauthorized`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The key lacks the required scope (`forbidden`, key `apikey_scope_fehlt`),\nthe account is blocked (`account_blocked`), or a plan limit is reached\n(`limit_reached`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Not found (or belongs to another account)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Conflict":{"description":"The current state does not allow the action (e.g. `webhook_limit_reached`, `idempotency_in_progress`)","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Set to 1 for `idempotency_in_progress`."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnprocessableEntity":{"description":"Idempotency key reused with a different request body or path (`idempotency_key_mismatch`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests for this key (120 per minute), code `rate_limited`","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the next allowed request."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ServiceUnavailable":{"description":"The account status could not be checked (`status_unavailable`). The API\ndeliberately does NOT let the request through (fail-closed); retry in a minute.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}