{"openapi":"3.0.0","paths":{"/v1/public/agents":{"get":{"description":"Lists agents available to the calling organization.","operationId":"PublicApiController_listAgents","parameters":[],"responses":{"200":{"description":""}},"security":[{"apiKey":[]}],"summary":"List agents","tags":["Agents"]}},"/v1/public/conversations":{"post":{"description":"Creates a conversation tied to a contact identified by `externalId`. The contact is upserted on each call.","operationId":"PublicApiController_createConversation","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateConversationDto"}}}},"responses":{"201":{"description":""}},"security":[{"apiKey":[]}],"summary":"Create conversation","tags":["Conversations"]},"get":{"description":"Paginated list of conversations in the organization. Combine `query` (free-text over contact name/subject) with structured filters (`status`, `channel`, `assignedToId`, `tagId`, `from`/`to`). Returns `data` + `meta.{total,page,limit,totalPages}`.","operationId":"PublicApiController_listConversations","parameters":[{"name":"limit","required":false,"in":"query","description":"Range 1–100","schema":{"example":25,"type":"number"}},{"name":"page","required":false,"in":"query","schema":{"example":1,"type":"number"}},{"name":"to","required":false,"in":"query","description":"ISO datetime (exclusive)","schema":{"type":"string"}},{"name":"from","required":false,"in":"query","description":"ISO datetime (inclusive)","schema":{"type":"string"}},{"name":"tagId","required":false,"in":"query","description":"Repeat to OR multiple tag ids","schema":{"type":"string"}},{"name":"assignedToId","required":false,"in":"query","schema":{"type":"string"}},{"name":"channel","required":false,"in":"query","schema":{"enum":["WEB_CHAT","WHATSAPP","EMAIL","INSTAGRAM","MESSENGER","TELEGRAM","API"],"type":"string"}},{"name":"status","required":false,"in":"query","schema":{"enum":["OPEN","PENDING","RESOLVED","CLOSED"],"type":"string"}},{"name":"query","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"apiKey":[]}],"summary":"List or search conversations","tags":["Conversations"]}},"/v1/public/conversations/{id}":{"get":{"operationId":"PublicApiController_getConversation","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"apiKey":[]}],"summary":"Get conversation","tags":["Conversations"]}},"/v1/public/conversations/{id}/close":{"post":{"operationId":"PublicApiController_closeConversation","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"apiKey":[]}],"summary":"Close conversation","tags":["Conversations"]}},"/v1/public/conversations/{id}/messages":{"get":{"description":"Returns messages of a conversation in cursor-paginated form.","operationId":"PublicApiController_listMessages","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Maximum number of messages to return (1–100).","schema":{"minimum":1,"maximum":100,"default":50,"example":50,"type":"number"}},{"name":"cursor","required":false,"in":"query","description":"Pagination cursor — pass `nextCursor` from the previous response.","schema":{"type":"string"}},{"name":"order","required":false,"in":"query","description":"Sort order. Default `asc` (oldest first).","schema":{"enum":["asc","desc"],"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"apiKey":[]}],"summary":"List messages","tags":["Messages"]},"post":{"description":"Sends a contact message and returns the AI reply (when AI is active on the conversation). Use the `/stream` variant for SSE streaming.","operationId":"PublicApiController_sendMessage","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendMessageDto"}}}},"responses":{"201":{"description":""}},"security":[{"apiKey":[]}],"summary":"Send message","tags":["Messages"]}},"/v1/public/conversations/{id}/messages/stream":{"post":{"description":"Sends a contact message and returns the AI reply via Server-Sent Events. Event types: `token`, `done`, `error`. Heartbeat every 15s as `: keepalive` comments.","operationId":"PublicStreamingController_streamMessage","parameters":[{"name":"id","required":true,"in":"path","description":"Conversation ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendMessageDto"}}}},"responses":{"200":{"description":"SSE stream with `token`, `done`, and (on failure) `error` events"},"400":{"description":"Missing or invalid `message`"},"404":{"description":"Conversation not found"},"429":{"description":"Concurrent stream limit (20 per API key) reached"}},"security":[{"apiKey":[]}],"summary":"Send message (streaming SSE)","tags":["Messages"]}},"/v1/public/webhooks":{"post":{"description":"Registers a webhook endpoint. The HMAC signing secret is returned ONCE in the response — store it; we never expose it again.","operationId":"PublicWebhooksController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookDto"}}}},"responses":{"201":{"description":"Webhook created. The signing secret is returned ONCE here."},"400":{"description":"Missing url or invalid event list"}},"security":[{"apiKey":[]}],"summary":"Create webhook","tags":["Webhooks"]},"get":{"operationId":"PublicWebhooksController_list","parameters":[],"responses":{"200":{"description":"Webhooks list returned"}},"security":[{"apiKey":[]}],"summary":"List webhooks","tags":["Webhooks"]}},"/v1/public/webhooks/{id}":{"get":{"operationId":"PublicWebhooksController_findOne","parameters":[{"name":"id","required":true,"in":"path","description":"Webhook ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Webhook returned"},"404":{"description":"Webhook not found"}},"security":[{"apiKey":[]}],"summary":"Get webhook by id","tags":["Webhooks"]},"patch":{"operationId":"PublicWebhooksController_update","parameters":[{"name":"id","required":true,"in":"path","description":"Webhook ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookDto"}}}},"responses":{"200":{"description":"Webhook updated"},"400":{"description":"Invalid event list"},"404":{"description":"Webhook not found"}},"security":[{"apiKey":[]}],"summary":"Update webhook","tags":["Webhooks"]},"delete":{"operationId":"PublicWebhooksController_remove","parameters":[{"name":"id","required":true,"in":"path","description":"Webhook ID","schema":{"type":"string"}}],"responses":{"204":{"description":"Webhook deleted"},"404":{"description":"Webhook not found"}},"security":[{"apiKey":[]}],"summary":"Delete webhook","tags":["Webhooks"]}},"/v1/public/webhooks/{id}/test":{"post":{"description":"Dispatches a `webhook.test` event to the configured URL using the current signing secret.","operationId":"PublicWebhooksController_test","parameters":[{"name":"id","required":true,"in":"path","description":"Webhook ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Test event dispatched"},"404":{"description":"Webhook not found"}},"security":[{"apiKey":[]}],"summary":"Send a test event","tags":["Webhooks"]}},"/v1/public/webhooks/{id}/rotate-secret":{"post":{"description":"Issues a new signing secret. The previous one stays valid for 24h, with both signatures emitted in `X-Cognizy-Signature` (`v1=<new>,v1=<old>`).","operationId":"PublicWebhooksController_rotateSecret","parameters":[{"name":"id","required":true,"in":"path","description":"Webhook ID","schema":{"type":"string"}}],"responses":{"200":{"description":"New signing secret returned (shown ONCE)"},"404":{"description":"Webhook not found"}},"security":[{"apiKey":[]}],"summary":"Rotate webhook secret","tags":["Webhooks"]}},"/v1/public/webhooks/{id}/deliveries":{"get":{"operationId":"PublicWebhooksController_listDeliveries","parameters":[{"name":"id","required":true,"in":"path","description":"Webhook ID","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Deliveries list returned"},"404":{"description":"Webhook not found"}},"security":[{"apiKey":[]}],"summary":"List webhook deliveries","tags":["Webhooks"]}},"/v1/public/webhooks/deliveries/{deliveryId}/replay":{"post":{"description":"Re-sends the payload of a previous delivery to the webhook URL.","operationId":"PublicWebhooksController_replay","parameters":[{"name":"deliveryId","required":true,"in":"path","description":"Webhook delivery ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Delivery replayed"},"404":{"description":"Delivery not found"}},"security":[{"apiKey":[]}],"summary":"Replay a webhook delivery","tags":["Webhooks"]}},"/v1/public/knowledge-base/search":{"get":{"description":"Returns the top matching chunks ranked by hybrid (semantic + keyword) score. Use this from external AI agents or MCP clients to ground answers in your KB.","operationId":"PublicKbController_search","parameters":[{"name":"knowledgeBaseId","required":false,"in":"query","description":"Restrict search to a single knowledge base. Omit to search across all.","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Number of results to return. Range 1–20, default 5.","schema":{"example":5,"type":"number"}},{"name":"query","required":true,"in":"query","schema":{"example":"how do I reset my password","type":"string"}}],"responses":{"200":{"description":""}},"security":[{"apiKey":[]}],"summary":"Semantic search across the knowledge base","tags":["Knowledge Base"]}},"/v1/public/knowledge-base":{"get":{"description":"Returns all knowledge bases in the organization, with document counts.","operationId":"PublicKbController_listKnowledgeBases","parameters":[],"responses":{"200":{"description":""}},"security":[{"apiKey":[]}],"summary":"List knowledge bases","tags":["Knowledge Base"]},"post":{"description":"Creates a new KB. Plan limits apply (RagService enforces this — returns 403 when the org is at its `knowledgeBases` quota).","operationId":"PublicKbController_createKnowledgeBase","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":200,"description":"Knowledge base name"},"description":{"type":"string","maxLength":2000,"description":"Optional description"}}}}}},"responses":{"201":{"description":"Knowledge base created"},"400":{"description":"Invalid `name` or `description`"},"403":{"description":"Plan quota for knowledge bases exceeded"}},"security":[{"apiKey":[]}],"summary":"Create a knowledge base","tags":["Knowledge Base"]}},"/v1/public/knowledge-base/{id}":{"get":{"description":"Returns one KB with metadata and document count.","operationId":"PublicKbController_getKnowledgeBase","parameters":[{"name":"id","required":true,"in":"path","description":"Knowledge base ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Knowledge base returned"},"404":{"description":"Knowledge base not found"}},"security":[{"apiKey":[]}],"summary":"Get a knowledge base","tags":["Knowledge Base"]}},"/v1/public/knowledge-base/{id}/documents":{"get":{"description":"Lists documents inside a KB with their titles, source URLs, use counts, and timestamps. Use this to avoid duplicating content before adding a new document.","operationId":"PublicKbController_listDocuments","parameters":[{"name":"id","required":true,"in":"path","description":"Knowledge base ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Document list returned"},"404":{"description":"Knowledge base not found"}},"security":[{"apiKey":[]}],"summary":"List documents in a knowledge base","tags":["Knowledge Base"]},"post":{"description":"Adds a new document, chunks it, generates embeddings, and stores them in pgvector. Same pipeline as the in-app upload. Returns the created document row (without chunks). Subsequent search_knowledge_base calls will surface this content immediately — RAG cache is invalidated automatically.","operationId":"PublicKbController_addDocument","parameters":[{"name":"id","required":true,"in":"path","description":"Knowledge base ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","content"],"properties":{"title":{"type":"string","maxLength":500,"description":"Document title (unique per KB)"},"content":{"type":"string","maxLength":200000,"description":"Document body (markdown/text)"},"sourceUrl":{"type":"string","maxLength":2000,"description":"Optional source URL"}}}}}},"responses":{"201":{"description":"Document created and embedded"},"400":{"description":"Invalid title/content/sourceUrl"},"404":{"description":"Knowledge base not found"}},"security":[{"apiKey":[]}],"summary":"Add a document to a knowledge base","tags":["Knowledge Base"]}},"/v1/public/knowledge-base/documents/{documentId}":{"get":{"description":"Returns the full document content. Useful to read existing material before suggesting an update.","operationId":"PublicKbController_getDocument","parameters":[{"name":"documentId","required":true,"in":"path","description":"Document ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Document returned"},"404":{"description":"Document not found"}},"security":[{"apiKey":[]}],"summary":"Get a document","tags":["Knowledge Base"]},"patch":{"description":"Partial update of a document. Provide any combination of `title`, `content`, `sourceUrl`. When `content` changes, the document is re-chunked and re-embedded automatically — useCount and feedback stats are preserved. Renaming into a title that already exists in the KB returns 409 (unique constraint).","operationId":"PublicKbController_updateDocument","parameters":[{"name":"documentId","required":true,"in":"path","description":"Document ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","maxLength":500},"content":{"type":"string","maxLength":200000},"sourceUrl":{"type":"string","maxLength":2000,"nullable":true}}}}}},"responses":{"200":{"description":"Document updated"},"400":{"description":"Invalid patch payload"},"404":{"description":"Document not found"},"409":{"description":"Another document in this KB already has that title"}},"security":[{"apiKey":[]}],"summary":"Update a document","tags":["Knowledge Base"]},"delete":{"description":"Removes a document and its chunks (cascade via Prisma). RAG cache is invalidated automatically. Idempotent — succeeds with 204 even if the document was already gone (P2025 swallowed).","operationId":"PublicKbController_deleteDocument","parameters":[{"name":"documentId","required":true,"in":"path","description":"Document ID","schema":{"type":"string"}}],"responses":{"204":{"description":"Document deleted (or was already gone — idempotent)"},"404":{"description":"Document not found in this organization"}},"security":[{"apiKey":[]}],"summary":"Delete a document","tags":["Knowledge Base"]}},"/v1/public/knowledge-base/{id}/documents/bulk":{"post":{"description":"Adds many documents in one request. Upsert-by-title: a doc whose title already exists in the KB is UPDATED with the new content; others are created. Returns counts of created/updated/skipped (skipped includes invalid items and intra-batch title duplicates). Use this for migrations, JSON imports, or batch material generated by an LLM. Cap: 200 items per call.","operationId":"PublicKbController_bulkAddDocuments","parameters":[{"name":"id","required":true,"in":"path","description":"Knowledge base ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["documents"],"properties":{"documents":{"type":"array","minItems":1,"maxItems":200,"items":{"type":"object","required":["title","content"],"properties":{"title":{"type":"string","maxLength":500},"content":{"type":"string","maxLength":200000},"sourceUrl":{"type":"string","maxLength":2000}}}}}}}}},"responses":{"200":{"description":"Bulk import result with created/updated/skipped counts"},"400":{"description":"Invalid documents payload"},"404":{"description":"Knowledge base not found"}},"security":[{"apiKey":[]}],"summary":"Bulk add or upsert documents","tags":["Knowledge Base"]}},"/v1/public/contacts":{"get":{"description":"Returns a paginated list of contacts in your organization. Use `query` for free-text search across name/email/phone.","operationId":"PublicContactsController_list","parameters":[{"name":"limit","required":false,"in":"query","description":"Range 1–100, default 25","schema":{"example":25,"type":"number"}},{"name":"page","required":false,"in":"query","schema":{"example":1,"type":"number"}},{"name":"hasPhone","required":false,"in":"query","schema":{"type":"boolean"}},{"name":"hasEmail","required":false,"in":"query","schema":{"type":"boolean"}},{"name":"tagId","required":false,"in":"query","description":"Filter to contacts that have this tag. Repeat to OR multiple.","schema":{"type":"string"}},{"name":"query","required":false,"in":"query","description":"Free-text search across name/email/phone","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"apiKey":[]}],"summary":"List or search contacts","tags":["Contacts"]}},"/v1/public/contacts/{id}":{"get":{"description":"Returns the full contact record including recent conversations, deals, appointments, tags, and custom fields.","operationId":"PublicContactsController_getById","parameters":[{"name":"id","required":true,"in":"path","description":"Contact ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Contact record returned"},"404":{"description":"Contact not found"}},"security":[{"apiKey":[]}],"summary":"Get a contact by id","tags":["Contacts"]}},"/v1/public/contacts/{id}/tags":{"post":{"description":"Attaches an existing tag to a contact. Idempotent — calling twice with the same pair is a no-op (no error). Same effect as the in-app dashboard tagging.","operationId":"PublicContactsController_addTag","parameters":[{"name":"id","required":true,"in":"path","description":"Contact ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["tagId"],"properties":{"tagId":{"type":"string","description":"Tag ID to attach to the contact"}}}}}},"responses":{"204":{"description":"Tag attached (or already attached — idempotent)"},"400":{"description":"Missing or invalid `tagId`"},"404":{"description":"Contact or tag not found"}},"security":[{"apiKey":[]}],"summary":"Add a tag to a contact","tags":["Contacts"]}},"/v1/public/contacts/{id}/tags/{tagId}":{"delete":{"description":"Detaches a tag from a contact. Returns 204 even if the tag wasn't attached (the postcondition holds regardless).","operationId":"PublicContactsController_removeTag","parameters":[{"name":"id","required":true,"in":"path","description":"Contact ID","schema":{"type":"string"}},{"name":"tagId","required":true,"in":"path","description":"Tag ID to detach","schema":{"type":"string"}}],"responses":{"204":{"description":"Tag detached (or was not attached — idempotent)"},"404":{"description":"Contact or tag not found"}},"security":[{"apiKey":[]}],"summary":"Remove a tag from a contact","tags":["Contacts"]}},"/v1/public/analytics":{"get":{"description":"Returns aggregated stats (totals, by-channel, by-status), recent activity, and a daily conversation count series for the requested window. Use this from MCP/external agents to answer questions like \"how was last week\" or \"what channel is busiest\".","operationId":"PublicAnalyticsController_getAnalytics","parameters":[{"name":"status","required":false,"in":"query","schema":{"enum":["OPEN","PENDING","RESOLVED","CLOSED"],"type":"string"}},{"name":"channel","required":false,"in":"query","schema":{"enum":["WEB_CHAT","WHATSAPP","EMAIL","INSTAGRAM","MESSENGER","TELEGRAM","API"],"type":"string"}},{"name":"to","required":false,"in":"query","description":"ISO datetime, exclusive","schema":{"type":"string"}},{"name":"from","required":false,"in":"query","description":"ISO datetime, inclusive","schema":{"type":"string"}},{"name":"period","required":false,"in":"query","description":"Preset window. Mutually exclusive with `from`/`to`. Default: 30d.","schema":{"enum":["today","7d","30d","90d","mtd","qtd","ytd"],"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"apiKey":[]}],"summary":"Get organization analytics","tags":["Analytics"]}},"/v1/public/task-boards":{"get":{"description":"Returns all task boards in the organization with their columns, members, and task count.","operationId":"PublicTasksController_listBoards","parameters":[],"responses":{"200":{"description":""}},"security":[{"apiKey":[]}],"summary":"List task boards","tags":["Tasks"]}},"/v1/public/task-boards/{id}":{"get":{"description":"Returns a single board with columns, tasks nested per column, labels, and members. Use list_tasks below to query tasks with filters and pagination instead of pulling the full board.","operationId":"PublicTasksController_getBoard","parameters":[{"name":"id","required":true,"in":"path","description":"Task board ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Task board returned"},"404":{"description":"Task board not found"}},"security":[{"apiKey":[]}],"summary":"Get a task board","tags":["Tasks"]}},"/v1/public/task-boards/{id}/tasks":{"get":{"description":"Paginated tasks within a board, with composable filters: column, assignee, label, overdue, completed.","operationId":"PublicTasksController_listTasks","parameters":[{"name":"id","required":true,"in":"path","description":"Task board ID","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Range 1–100","schema":{"example":25,"type":"number"}},{"name":"page","required":false,"in":"query","schema":{"example":1,"type":"number"}},{"name":"completed","required":false,"in":"query","description":"true = only completed; false = only uncompleted; omit for both","schema":{"type":"boolean"}},{"name":"overdue","required":false,"in":"query","description":"true = only tasks past their dueDate and not completed","schema":{"type":"boolean"}},{"name":"labelId","required":false,"in":"query","schema":{"type":"string"}},{"name":"assigneeId","required":false,"in":"query","schema":{"type":"string"}},{"name":"columnId","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"apiKey":[]}],"summary":"List tasks in a board","tags":["Tasks"]},"post":{"description":"Creates a new task in a specific column of a board. Most fields are optional; only `title` and `columnId` are required. Defaults to `aiGenerated: true` when called via the public API (overrideable in body) so the dashboard surfaces tasks that came from MCP/integrations distinctly from human-created ones. Returns the full task row with assignees, labels, counts.","operationId":"PublicTasksController_createTask","parameters":[{"name":"id","required":true,"in":"path","description":"Task board ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","columnId"],"properties":{"title":{"type":"string","maxLength":500},"columnId":{"type":"string","description":"Target column ID within this board"},"description":{"type":"string","maxLength":50000},"priority":{"type":"string","enum":["LOW","MEDIUM","HIGH","URGENT"]},"dueDate":{"type":"string","format":"date-time","description":"ISO datetime"},"estimatedMinutes":{"type":"integer","minimum":0,"maximum":100000},"assigneeIds":{"type":"array","items":{"type":"string"}},"labelIds":{"type":"array","items":{"type":"string"}},"conversationId":{"type":"string"},"contactId":{"type":"string"},"aiGenerated":{"type":"boolean","description":"Defaults to true via public API"},"aiReason":{"type":"string"},"parentId":{"type":"string","description":"Parent task ID (for subtasks)"}}}}}},"responses":{"201":{"description":"Task created"},"400":{"description":"Invalid task payload"},"404":{"description":"Board, column, assignee, or label not found"}},"security":[{"apiKey":[]}],"summary":"Create a task in a board","tags":["Tasks"]}},"/v1/public/tasks/{id}":{"get":{"description":"Returns the full task: description, checklist, subtasks, recent activities (50), assignees, labels, linked conversation/contact.","operationId":"PublicTasksController_getTask","parameters":[{"name":"id","required":true,"in":"path","description":"Task ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Task returned"},"404":{"description":"Task not found"}},"security":[{"apiKey":[]}],"summary":"Get a task by id","tags":["Tasks"]},"delete":{"description":"Removes a task entirely. Cascades to comments, checklist items, attachments, and subtasks. Idempotent — succeeds with 204 even if the task was already gone or never belonged to your org (cross-tenant ids are not revealed).","operationId":"PublicTasksController_deleteTask","parameters":[{"name":"id","required":true,"in":"path","description":"Task ID","schema":{"type":"string"}}],"responses":{"204":{"description":"Task deleted (or already gone — idempotent)"}},"security":[{"apiKey":[]}],"summary":"Delete a task","tags":["Tasks"]},"patch":{"description":"Partial update of task fields (title, description, priority, dueDate, estimatedMinutes). Pass `dueDate: null` to clear it. Other omitted fields are left untouched. Triggers PRIORITY_CHANGED / DUE_DATE_CHANGED / UPDATED activity entries as applicable.","operationId":"PublicTasksController_updateTask","parameters":[{"name":"id","required":true,"in":"path","description":"Task ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","maxLength":500},"description":{"type":"string","maxLength":50000},"priority":{"type":"string","enum":["LOW","MEDIUM","HIGH","URGENT"]},"dueDate":{"type":"string","format":"date-time","nullable":true},"estimatedMinutes":{"type":"integer","minimum":0,"maximum":100000,"nullable":true}}}}}},"responses":{"200":{"description":"Task updated"},"400":{"description":"Invalid patch payload"},"404":{"description":"Task not found"}},"security":[{"apiKey":[]}],"summary":"Update task fields","tags":["Tasks"]}},"/v1/public/tasks/{id}/comments":{"post":{"description":"Posts a comment authored by the user who created the API key (or the org owner as fallback). Triggers the same notifications and activity log entry as an in-app comment. Use this from MCP/external agents to surface findings on tasks (\"AI detected this ticket is at-risk\", \"Suggested fix: X\").","operationId":"PublicTasksController_addTaskComment","parameters":[{"name":"id","required":true,"in":"path","description":"Task ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["content"],"properties":{"content":{"type":"string","maxLength":10000,"description":"Comment body"},"parentId":{"type":"string","description":"Parent comment ID for threaded replies"}}}}}},"responses":{"201":{"description":"Comment created"},"400":{"description":"Invalid content or parentId"},"404":{"description":"Task not found"}},"security":[{"apiKey":[]}],"summary":"Add a comment to a task","tags":["Tasks"]}},"/v1/public/tasks/{id}/checklist":{"post":{"description":"Appends a new checklist item at the end of the task's checklist. Triggers a CHECKLIST_ITEM_ADDED activity entry.","operationId":"PublicTasksController_addChecklistItem","parameters":[{"name":"id","required":true,"in":"path","description":"Task ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string","maxLength":500,"description":"Checklist item title"}}}}}},"responses":{"201":{"description":"Checklist item created"},"400":{"description":"Invalid title"},"404":{"description":"Task not found"}},"security":[{"apiKey":[]}],"summary":"Add a checklist item to a task","tags":["Tasks"]}},"/v1/public/tasks/{id}/checklist/{itemId}/toggle":{"post":{"description":"Flips `completed` on/off. Adding `done: true` would let the caller force-set instead of toggle, but toggle is simpler and mirrors the in-app UI button. Triggers CHECKLIST_ITEM_COMPLETED activity when completing.","operationId":"PublicTasksController_toggleChecklistItem","parameters":[{"name":"id","required":true,"in":"path","description":"Task ID","schema":{"type":"string"}},{"name":"itemId","required":true,"in":"path","description":"Checklist item ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Checklist item toggled"},"404":{"description":"Task or checklist item not found"}},"security":[{"apiKey":[]}],"summary":"Toggle a checklist item completion","tags":["Tasks"]}},"/v1/public/tasks/{id}/checklist/{itemId}":{"delete":{"operationId":"PublicTasksController_deleteChecklistItem","parameters":[{"name":"id","required":true,"in":"path","description":"Task ID","schema":{"type":"string"}},{"name":"itemId","required":true,"in":"path","description":"Checklist item ID","schema":{"type":"string"}}],"responses":{"204":{"description":"Checklist item deleted"},"404":{"description":"Task or checklist item not found"}},"security":[{"apiKey":[]}],"summary":"Delete a checklist item","tags":["Tasks"]}},"/v1/public/tasks/{id}/labels/{labelId}":{"post":{"description":"Verifies the label belongs to the caller's org before attaching. Idempotent at the data layer via PK constraint — re-attach raises Prisma P2002 which we swallow.","operationId":"PublicTasksController_addLabel","parameters":[{"name":"id","required":true,"in":"path","description":"Task ID","schema":{"type":"string"}},{"name":"labelId","required":true,"in":"path","description":"Label ID to attach","schema":{"type":"string"}}],"responses":{"204":{"description":"Label attached (or already attached — idempotent)"},"404":{"description":"Task or label not found"}},"security":[{"apiKey":[]}],"summary":"Attach a label to a task","tags":["Tasks"]},"delete":{"operationId":"PublicTasksController_removeLabel","parameters":[{"name":"id","required":true,"in":"path","description":"Task ID","schema":{"type":"string"}},{"name":"labelId","required":true,"in":"path","description":"Label ID to detach","schema":{"type":"string"}}],"responses":{"204":{"description":"Label detached (or was not attached — idempotent)"},"404":{"description":"Task or label not found"}},"security":[{"apiKey":[]}],"summary":"Detach a label from a task","tags":["Tasks"]}},"/v1/public/tasks/{id}/move":{"post":{"description":"Move within the same board. The target column must belong to the task's current board (TaskService enforces this). Pass `position` as a 0-based index in the destination column. Triggers MOVED activity entry; auto-sets completedAt / archivedAt when the target column has those flags.","operationId":"PublicTasksController_moveTask","parameters":[{"name":"id","required":true,"in":"path","description":"Task ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["columnId","position"],"properties":{"columnId":{"type":"string","description":"Destination column ID (same board)"},"position":{"type":"integer","minimum":0,"description":"0-based index in the destination column"}}}}}},"responses":{"200":{"description":"Task moved"},"400":{"description":"Invalid columnId or position"},"404":{"description":"Task or column not found"}},"security":[{"apiKey":[]}],"summary":"Move a task to a different column / position","tags":["Tasks"]}},"/v1/public/tasks/{id}/assignees/{userId}":{"post":{"description":"Idempotent — re-assigning the same user is treated as success (TaskService raises BadRequestException for duplicates; we map that to 204). The user must be a member of the same org. Sends an in-app notification to the new assignee.","operationId":"PublicTasksController_assignTask","parameters":[{"name":"id","required":true,"in":"path","description":"Task ID","schema":{"type":"string"}},{"name":"userId","required":true,"in":"path","description":"User ID to assign (must be a member of the same org)","schema":{"type":"string"}}],"responses":{"204":{"description":"User assigned (or already assigned — idempotent)"},"404":{"description":"Task or user not found in organization"}},"security":[{"apiKey":[]}],"summary":"Assign a user to a task","tags":["Tasks"]},"delete":{"description":"Idempotent — succeeds even if the user wasn't assigned.","operationId":"PublicTasksController_unassignTask","parameters":[{"name":"id","required":true,"in":"path","description":"Task ID","schema":{"type":"string"}},{"name":"userId","required":true,"in":"path","description":"User ID to unassign","schema":{"type":"string"}}],"responses":{"204":{"description":"User unassigned (or was not assigned — idempotent)"},"404":{"description":"Task not found"}},"security":[{"apiKey":[]}],"summary":"Unassign a user from a task","tags":["Tasks"]}},"/v1/public/scheduling/booking-pages":{"get":{"description":"Returns booking pages configured in the organization. Each booking page represents a bookable service or appointment type with its slug, status, services, and attendants.","operationId":"PublicSchedulingController_listBookingPages","parameters":[],"responses":{"200":{"description":""}},"security":[{"apiKey":[]}],"summary":"List booking pages","tags":["Scheduling"]}},"/v1/public/scheduling/appointments":{"get":{"description":"Paginated list of appointments. Filter by booking page, attendant, contact, status, or date range. Returns `data` (with attendant/service/contact) + `meta.{total,page,limit,totalPages}`.","operationId":"PublicSchedulingController_listAppointments","parameters":[{"name":"limit","required":false,"in":"query","description":"Range 1–100","schema":{"example":25,"type":"number"}},{"name":"page","required":false,"in":"query","schema":{"example":1,"type":"number"}},{"name":"to","required":false,"in":"query","description":"ISO datetime (date-only also accepted)","schema":{"type":"string"}},{"name":"from","required":false,"in":"query","description":"ISO datetime, inclusive","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","schema":{"enum":["RESERVED","CONFIRMED","CANCELLED","NO_SHOW","COMPLETED"],"type":"string"}},{"name":"contactId","required":false,"in":"query","schema":{"type":"string"}},{"name":"attendantId","required":false,"in":"query","schema":{"type":"string"}},{"name":"bookingPageId","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"security":[{"apiKey":[]}],"summary":"List appointments","tags":["Scheduling"]},"post":{"description":"Queues a PENDING booking. An admin reviews attendant, service, contact, and time slot before the appointment is created and notifications fire.","operationId":"McpWriteController_bookAppointment","parameters":[],"responses":{"202":{"description":""}},"security":[{"apiKey":[]}],"summary":"Book an appointment (approval required)","tags":["MCP Approval"]}},"/v1/public/scheduling/appointments/{id}":{"get":{"description":"Returns the appointment with attendant, service, contact, and bookingPage included.","operationId":"PublicSchedulingController_getAppointment","parameters":[{"name":"id","required":true,"in":"path","description":"Appointment ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Appointment returned"},"404":{"description":"Appointment not found"}},"security":[{"apiKey":[]}],"summary":"Get an appointment","tags":["Scheduling"]}},"/v1/public/campaigns":{"get":{"description":"Paginated list of campaigns in the organization. Filter by status or channel. Returns `data` (with template name) + `meta.{total,page,limit,totalPages}`.","operationId":"PublicCampaignsController_list","parameters":[{"name":"limit","required":false,"in":"query","description":"Range 1–100","schema":{"example":25,"type":"number"}},{"name":"page","required":false,"in":"query","schema":{"example":1,"type":"number"}},{"name":"channel","required":false,"in":"query","schema":{"enum":["WEB_CHAT","WHATSAPP","EMAIL","INSTAGRAM","MESSENGER","API"],"type":"string"}},{"name":"status","required":false,"in":"query","schema":{"enum":["DRAFT","SCHEDULED","RUNNING","COMPLETED","CANCELLED"],"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"apiKey":[]}],"summary":"List campaigns","tags":["Campaigns"]}},"/v1/public/campaigns/{id}":{"get":{"description":"Returns the campaign with template, segment, handoff department, and all the denormalized counters (sent, replied, opened, etc).","operationId":"PublicCampaignsController_getById","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Campaign returned"},"404":{"description":"Campaign not found"}},"security":[{"apiKey":[]}],"summary":"Get a campaign","tags":["Campaigns"]}},"/v1/public/campaigns/{id}/analytics":{"get":{"description":"Returns totals (sent, failed, replied, qualified, booked, opted-out, email deliverability) plus a bucketed time series of conversations spawned by this campaign. Use for A/B test analysis and engagement over time.","operationId":"PublicCampaignsController_getAnalytics","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}},{"name":"granularity","required":false,"in":"query","description":"Default day","schema":{"enum":["hour","day","week"],"type":"string"}}],"responses":{"200":{"description":"Campaign analytics returned"},"404":{"description":"Campaign not found"}},"security":[{"apiKey":[]}],"summary":"Get campaign analytics","tags":["Campaigns"]}},"/v1/public/conversations/{id}/tags":{"post":{"description":"Idempotent: re-attaching the same (conversation, tag) pair is a no-op. Triggers TAG_ADDED automation rules — agents can use this to flag conversations for downstream workflows.","operationId":"PublicConversationsController_addTag","parameters":[{"name":"id","required":true,"in":"path","description":"Conversation ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["tagId"],"properties":{"tagId":{"type":"string","description":"Tag ID to attach to the conversation"}}}}}},"responses":{"204":{"description":"Tag attached (or already attached — idempotent)"},"400":{"description":"Missing or invalid `tagId`"},"404":{"description":"Conversation or tag not found"}},"security":[{"apiKey":[]}],"summary":"Attach a tag to a conversation","tags":["Conversations"]}},"/v1/public/conversations/{id}/tags/{tagId}":{"delete":{"operationId":"PublicConversationsController_removeTag","parameters":[{"name":"id","required":true,"in":"path","description":"Conversation ID","schema":{"type":"string"}},{"name":"tagId","required":true,"in":"path","description":"Tag ID to detach","schema":{"type":"string"}}],"responses":{"204":{"description":"Tag detached (or was not attached — idempotent)"},"404":{"description":"Conversation or tag not found"}},"security":[{"apiKey":[]}],"summary":"Detach a tag from a conversation","tags":["Conversations"]}},"/v1/public/conversations/{id}/assign":{"post":{"description":"Set the assigned agent for a conversation. Pass `userId: null` (or omit) to unassign. The user must be a member of the same org. Triggers SLA timers and assignment notifications same as in-app.","operationId":"PublicConversationsController_assign","parameters":[{"name":"id","required":true,"in":"path","description":"Conversation ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"userId":{"type":"string","nullable":true,"description":"User ID to assign. Pass null or omit to unassign."}}}}}},"responses":{"200":{"description":"Conversation assignment updated"},"400":{"description":"Invalid `userId`"},"404":{"description":"Conversation or user not found in organization"}},"security":[{"apiKey":[]}],"summary":"Assign or unassign a conversation","tags":["Conversations"]}},"/v1/public/whatsapp/messages":{"post":{"description":"Sends a WhatsApp message directly (text, media or approved template) without creating an inbox conversation. Returns 202 with the queued message id; the final status is tracked on the message and pushed via webhooks. Pass an Idempotency-Key header to safely retry without duplicating the send. Free-form text/media require an open 24h session window with the recipient; outside it, use an approved template.","operationId":"PublicWhatsappController_send","parameters":[{"name":"idempotency-key","required":true,"in":"header","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendMessageDto"}}}},"responses":{"202":{"description":"Message accepted and queued"}},"security":[{"apiKey":[]}],"summary":"Send a WhatsApp message","tags":["WhatsApp"]}},"/v1/public/whatsapp/messages/{id}":{"get":{"operationId":"PublicWhatsappController_status","parameters":[{"name":"id","required":true,"in":"path","description":"OutboundMessage id returned by POST","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"apiKey":[]}],"summary":"Get the status of a sent message","tags":["WhatsApp"]}},"/v1/public/pending-actions/{id}":{"get":{"operationId":"McpPendingActionsPublicController_getStatus","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["McpPendingActionsPublic"]},"delete":{"operationId":"McpPendingActionsPublicController_cancel","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"tags":["McpPendingActionsPublic"]}},"/v1/public/messages/send":{"post":{"description":"Creates a PENDING action — does NOT send the message immediately. An organization admin (OWNER or ADMIN) must approve in the dashboard. Returns the action id, preview (contact, channel, body excerpt), and expiresAt. Use GET /v1/public/pending-actions/:id to poll status.","operationId":"McpWriteController_sendMessage","parameters":[],"responses":{"202":{"description":""}},"security":[{"apiKey":[]}],"summary":"Send a message to a customer (approval required)","tags":["MCP Approval"]}},"/v1/public/scheduling/appointments/{id}/cancel":{"post":{"description":"Queues a PENDING cancellation. Once approved, the appointment is cancelled and the configured notification fires to the contact.","operationId":"McpWriteController_cancelAppointment","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"202":{"description":""}},"security":[{"apiKey":[]}],"summary":"Cancel an appointment (approval required)","tags":["MCP Approval"]}},"/v1/public/scheduling/appointments/{id}/reschedule":{"post":{"description":"Queues a PENDING reschedule. Once approved, the existing slot is cancelled and a new appointment is created at the requested time. The returned executionResult includes the NEW appointment id.","operationId":"McpWriteController_rescheduleAppointment","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"202":{"description":""}},"security":[{"apiKey":[]}],"summary":"Reschedule an appointment (approval required)","tags":["MCP Approval"]}},"/v1/public/campaigns/{id}/launch":{"post":{"description":"Queues a PENDING launch. Highest-risk write — touches many contacts. TTL is 1 hour (shorter than the default 24h) because campaign content is typically time-bound. Admin sees contact count, channel, and message preview before approving.","operationId":"McpWriteController_launchCampaign","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"202":{"description":""}},"security":[{"apiKey":[]}],"summary":"Launch a marketing campaign (approval required)","tags":["MCP Approval"]}}},"info":{"title":"Cognizy Public API","description":"REST + SSE API for client-built UIs (Professional plan and above).\n\n## Getting started\n\n1. Generate an API key in **Settings → Developers**. Keys are prefixed `cgz_live_` (production) or `cgz_test_` (sandbox).\n2. Send it on every request as a Bearer token:\n   ```\n   Authorization: Bearer cgz_live_xxx\n   ```\n3. Base URL: `https://<your-host>/api` (the `/api` prefix is already declared as the OpenAPI server, so paths in this doc are written without it).\n4. All responses are JSON (`Content-Type: application/json`) except SSE streams (`text/event-stream`).\n\n## Authentication\n\nEvery endpoint requires the `Authorization: Bearer <api-key>` header. The key resolves the tenant automatically — every resource you read or write is scoped to the organization that owns the key. You never pass `organizationId`.\n\n## Rate limits\n\nPer-key sliding-window limits are enforced by `ApiKeyRateLimitGuard`. On `429 Too Many Requests` the following headers describe the window:\n\n- `X-RateLimit-Limit` — requests allowed in the current window\n- `X-RateLimit-Remaining` — requests left\n- `X-RateLimit-Reset` — Unix timestamp when the window resets\n- `Retry-After` — seconds until you may retry\n\nPlans also have a monthly quota (`PublicApiQuotaGuard`); exceeding it returns `429` with `code: \"quota_exceeded\"`.\n\n## Pagination\n\nList endpoints use one of two styles:\n\n- **Page-based** (`?page=1&limit=25`) — for conversations, contacts, tasks, campaigns, KBs.\n- **Cursor-based** (`?cursor=<opaque>&limit=50&order=asc|desc`) — for messages and other time-ordered streams.\n\n`limit` is clamped to `1–100`. Responses include `data` plus either `{ page, limit, total }` or `{ nextCursor, hasMore }`.\n\n## Idempotency\n\nWrite endpoints (POST/PATCH) accept the `Idempotency-Key` header (any UUID / opaque string you choose). The first response is cached for 24h; replaying the same key returns the same response without re-executing. Use it for safe retries on network failure.\n\n## Streaming (SSE)\n\n`POST /v1/public/conversations/{id}/messages/stream` opens a Server-Sent Events connection. Each event is a delta of the agent's reply:\n\n```\nevent: delta\ndata: {\"content\":\"Hello\"}\n\nevent: done\ndata: {\"messageId\":\"msg_...\"}\n```\n\nConcurrent streams per key are capped (`StreamingConcurrencyService`); excess connections return `429`.\n\n## Webhooks\n\nSubscribe to events by registering a webhook (`POST /v1/public/webhooks`). Each delivery is signed with HMAC-SHA256 of the raw body using the webhook's secret:\n\n```\nX-Cognizy-Signature: sha256=<hex>\nX-Cognizy-Event: conversation.message.created\nX-Cognizy-Delivery: <delivery-id>\n```\n\nVerify by recomputing `HMAC_SHA256(secret, raw_body)` and comparing in constant time. Rotate the secret with `POST /v1/public/webhooks/{id}/rotate-secret`. Failed deliveries can be replayed via `POST /v1/public/webhooks/deliveries/{deliveryId}/replay`.\n\n## Errors\n\nErrors follow a consistent shape:\n\n```json\n{ \"statusCode\": 404, \"code\": \"not_found\", \"message\": \"Conversation not found\" }\n```\n\nCommon statuses: `400` validation, `401` missing/invalid key, `403` plan feature not enabled, `404` not found, `409` conflict (e.g. tag already attached), `422` business rule, `429` rate limit / quota, `5xx` server error.\n\n## SDK\n\nA first-party JavaScript SDK is available in `cognizy-sdk/` covering agents, conversations and messages. Other resources should be called via direct fetch until the SDK is expanded.","version":"1.0.0","contact":{}},"tags":[{"name":"Agents","description":"Read information about the organization's AI agent."},{"name":"Conversations","description":"Create and manage conversations, including tags and assignment."},{"name":"Messages","description":"Send and read messages, including SSE streaming."},{"name":"Contacts","description":"List contacts and manage their tags."},{"name":"Knowledge Base","description":"Manage knowledge bases and documents; run semantic search (RAG)."},{"name":"Tasks","description":"Manage task boards, tasks, checklists, labels and assignees."},{"name":"Campaigns","description":"Read campaigns and their analytics."},{"name":"Scheduling","description":"Read booking pages and appointments."},{"name":"Analytics","description":"Aggregated tenant metrics."},{"name":"Webhooks","description":"Manage webhook endpoints, deliveries and replays."}],"servers":[{"url":"/api"}],"components":{"securitySchemes":{"apiKey":{"scheme":"bearer","bearerFormat":"cgz_live_... or cgz_test_...","type":"http","description":"API key prefixed with `cgz_live_` (production) or `cgz_test_` (sandbox)."}},"schemas":{"CreateConversationDto":{"type":"object","properties":{"agentId":{"type":"string","description":"Agent identifier. Currently only `\"default\"` is supported (the organization's configured agent). Omit to use the default.","example":"default"},"contact":{"description":"Contact participating in the conversation. Upserted by `externalId`.","allOf":[{"$ref":"#/components/schemas/PublicContactDto"}]},"account":{"description":"External account this contact belongs to (B2B2C). When provided, conversations are grouped by this account in the inbox.","allOf":[{"$ref":"#/components/schemas/PublicAccountDto"}]},"subject":{"type":"string","maxLength":500,"description":"Optional subject/title for the conversation.","example":"Order #1234 — refund request"},"metadata":{"type":"object","description":"Free-form metadata attached to the conversation.","additionalProperties":true}},"required":["contact"]},"SendMessageDto":{"type":"object","properties":{"to":{"type":"string","description":"Recipient in E.164 (digits)","example":"5511999999999"},"type":{"type":"string","enum":["TEXT","MEDIA","TEMPLATE"]},"text":{"type":"string","description":"Body for type=TEXT","example":"Olá! Seu pedido saiu."},"media":{"$ref":"#/components/schemas/MediaDto"},"template":{"$ref":"#/components/schemas/TemplateDto"}},"required":["to","type"]},"CreateWebhookDto":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"Target URL that will receive the POST. Must be HTTPS in production.","example":"https://api.example.com/webhooks/cognizy"},"events":{"minItems":1,"description":"List of event names to subscribe to.","example":["message.created","conversation.closed"],"type":"array","items":{"type":"string"}},"description":{"type":"string","maxLength":500,"description":"Optional human-readable description for this webhook.","example":"Sync incoming messages with Salesforce"}},"required":["url","events"]},"UpdateWebhookDto":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string"}},"description":{"type":"string","maxLength":500},"isActive":{"type":"boolean","description":"Set `false` to pause delivery without deleting the webhook. Re-enabling resets the failure streak."}}},"PublicContactDto":{"type":"object","properties":{"externalId":{"type":"string","minLength":1,"maxLength":120,"description":"Stable identifier for the contact in your own system. Cognizy upserts the contact by this value scoped to your organization.","example":"user-42"},"name":{"type":"string","maxLength":200,"example":"Alice Silva"},"phone":{"type":"string","maxLength":40,"example":"+5511999999999"},"email":{"type":"string","format":"email","example":"alice@example.com"},"metadata":{"type":"object","description":"Free-form custom fields stored alongside the contact.","additionalProperties":true,"example":{"plan":"pro","signupSource":"mobile"}}},"required":["externalId"]},"PublicAccountDto":{"type":"object","properties":{"externalId":{"type":"string","minLength":1,"maxLength":120,"description":"Identifier of the customer/tenant in the host SaaS that the contact belongs to. Cognizy upserts the ExternalAccount by this value (scoped to your organization + source).","example":"imob-456"},"name":{"type":"string","maxLength":200,"description":"Display name of the account (e.g. the customer's company name). Shown as a badge in the inbox. Falls back to `externalId` if omitted.","example":"Imóveis Acme"},"source":{"type":"string","maxLength":60,"description":"Identifier of the host SaaS sending this account (e.g. \"mylar-pro\"). Defaults to \"api\" when omitted.","example":"mylar-pro"},"metadata":{"type":"object","description":"Free-form metadata about the account (plan, mrr, website, …).","additionalProperties":true,"example":{"plan":"pro","cidade":"São Paulo"}}},"required":["externalId"]},"MediaDto":{"type":"object","properties":{"url":{"type":"string","example":"https://example.com/file.pdf"},"type":{"type":"string","enum":["image","video","document","audio"]},"caption":{"type":"string"}},"required":["url","type"]},"TemplateDto":{"type":"object","properties":{"name":{"type":"string","description":"Approved Meta template name","example":"order_update"},"language":{"type":"string","example":"pt_BR"},"variables":{"description":"Positional body variables, in order","example":["Maria","#1234"],"type":"array","items":{"type":"string"}}},"required":["name"]}}}}