{"openapi":"3.1.0","info":{"title":"WizChat Management API","version":"1.0.0","description":"Programmatic control plane for WizChat chatbots. Authenticate with a `wpk_live_…` Management API key or a Firebase ID token. All endpoints are scoped — see the `x-scope` extension on each operation."},"servers":[{"url":"https://www.wizchat.com","description":"Production"}],"security":[{"bearerAuth":[]}],"paths":{"/api/v1/chatbots":{"get":{"summary":"List chatbots","operationId":"listChatbots","tags":["Chatbots"],"description":"Returns the chatbots you own, created on behalf of a team owner, were granted admin access to, or that are shared with a team you own or administer. Required scope: `chatbots:read`. Chatbots shared with a team where you are a plain member, and chatbots where you have only viewer/editor (non-admin) access, are not listed here — access those directly by id via `GET` / `PATCH /api/v1/chatbots/{chatbotId}`.","responses":{"200":{"description":"List of chatbots owned by or created by the authenticated user.","content":{"application/json":{"schema":{"type":"object","required":["chatbots"],"properties":{"chatbots":{"type":"array","items":{"$ref":"#/components/schemas/PublicChatbot"}}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a chatbot","operationId":"createChatbot","tags":["Chatbots"],"description":"Creates a new chatbot with `status: \"draft\"` and `deployment.status: \"pending\"`. Creation does not provision any infrastructure or start a deployment. **The first deploy must be done from the WizChat dashboard**, which provisions the underlying infrastructure; the v1 `/deploy` endpoint only re-deploys a chatbot that has already been deployed once. Restricted API keys (those locked to specific chatbot IDs) cannot create chatbots. Plan limits are enforced transactionally. **Note:** newly created chatbots have no `aiConfig` (no LLM model). Before triggering a deployment, use `POST /api/v1/chatbots/{id}/apply` to set `spec.core.aiConfig.llmModel` and related fields. Required scope: `chatbots:write`.","requestBody":{"required":true,"description":"Chatbot creation payload.","content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Display name for the chatbot (required).","example":"Support Assistant"},"description":{"type":"string","description":"Optional description of the chatbot."},"dataRegion":{"type":"string","enum":["EU","US"],"description":"Data residency region. Locked at creation time — cannot be changed after deployment. Defaults to \"US\".","example":"US"},"requireAuth":{"type":"boolean","description":"Whether the chatbot requires user authentication to access. Defaults to false."},"logoUrl":{"type":"string","description":"URL of the chatbot logo image."}},"additionalProperties":false}}}},"responses":{"201":{"description":"Chatbot created. `chatbot.status` is `\"draft\"`; `deployment.status` is `\"pending\"`. First deploy must use the dashboard.","content":{"application/json":{"schema":{"type":"object","required":["chatbot"],"properties":{"chatbot":{"$ref":"#/components/schemas/PublicChatbot"}},"additionalProperties":false}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/documents":{"get":{"summary":"List documents","operationId":"listDocuments","tags":["Documents"],"description":"Returns all documents owned by the authenticated user. Required scope: `documents:read`. Optional `?chatbotId=` filter (restricted keys are scoped to their allowed chatbots automatically). Lists only documents you own, even when `chatbotId` is supplied; a chatbot's full knowledge base is not enumerated here.","parameters":[{"name":"chatbotId","in":"query","required":false,"description":"Filter documents to a specific chatbot.","schema":{"type":"string"}}],"responses":{"200":{"description":"List of documents.","content":{"application/json":{"schema":{"type":"object","required":["documents"],"properties":{"documents":{"type":"array","items":{"$ref":"#/components/schemas/PublicDocument"}}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/documents/{documentId}":{"get":{"summary":"Get a document","operationId":"getDocument","tags":["Documents"],"description":"Returns a single document by ID (IDOR-guarded). Required scope: `documents:read`.","parameters":[{"name":"documentId","in":"path","required":true,"description":"The document ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Document found.","content":{"application/json":{"schema":{"type":"object","required":["document"],"properties":{"document":{"$ref":"#/components/schemas/PublicDocument"}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a document","operationId":"deleteDocument","tags":["Documents"],"description":"Delete a document and all its data (embedding vectors, storage, metadata). Required scope: `documents:write`. `success` is `false` when any cleanup step produced a warning (honest semantics). `warnings` is always present (empty array on clean delete).","parameters":[{"name":"documentId","in":"path","required":true,"description":"The document ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Document deleted.","content":{"application/json":{"schema":{"type":"object","required":["success","warnings"],"properties":{"success":{"type":"boolean","description":"False when any cleanup step produced a warning."},"warnings":{"type":"array","items":{"type":"string"},"description":"Empty on clean delete."}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/documents/{documentId}/privacy":{"put":{"summary":"Update document privacy","operationId":"updateDocumentPrivacy","tags":["Documents"],"description":"Update the privacy/access level of a document. Required scope: `documents:write`.","parameters":[{"name":"documentId","in":"path","required":true,"description":"The document ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Set `viewAccessLevel` to the desired access level.","content":{"application/json":{"schema":{"type":"object","required":["viewAccessLevel"],"properties":{"viewAccessLevel":{"type":"string","enum":["owner-only","authenticated-users","anyone-with-link","permanent-public"],"description":"Access level (canonical 4-value field): owner-only = private (only the document owner); authenticated-users = signed-in chatbot users; anyone-with-link = shareable link; permanent-public = public download token. The deprecated isPublic boolean is not accepted here — use viewAccessLevel."}},"additionalProperties":false}}}},"responses":{"200":{"description":"Privacy updated.","content":{"application/json":{"schema":{"type":"object","required":["document"],"properties":{"document":{"$ref":"#/components/schemas/PublicDocument"}},"additionalProperties":false}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/documents/{documentId}/authorized-emails":{"put":{"summary":"Replace authorized email list","operationId":"putDocumentAuthorizedEmails","tags":["Documents"],"description":"Replace the authorized email list for a document. Required scope: `documents:write`.","parameters":[{"name":"documentId","in":"path","required":true,"description":"The document ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["authorizedEmails"],"properties":{"authorizedEmails":{"type":"array","items":{"type":"string","description":"Email address."},"description":"Complete replacement list of individually authorised email addresses. Pass an empty array to open the document to all chatbot users."},"authorizedGroups":{"type":"array","items":{"type":"string","description":"Group ID."},"description":"Optional group IDs whose members are expanded and merged into the document's authorized-viewer set. Email membership is resolved at write time."}},"additionalProperties":false}}}},"responses":{"200":{"description":"Authorized emails updated.","content":{"application/json":{"schema":{"type":"object","required":["document"],"properties":{"document":{"$ref":"#/components/schemas/PublicDocument"}},"additionalProperties":false}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/videos":{"get":{"summary":"List videos","operationId":"listVideos","tags":["Videos"],"description":"Returns all videos owned by the authenticated user. Required scope: `videos:read`. Optional `?chatbotId=` filter (restricted keys are scoped to their allowed chatbots automatically). Lists only videos you own, even when `chatbotId` is supplied; a chatbot's full knowledge base is not enumerated here.","parameters":[{"name":"chatbotId","in":"query","required":false,"description":"Filter videos to a specific chatbot.","schema":{"type":"string"}}],"responses":{"200":{"description":"List of videos.","content":{"application/json":{"schema":{"type":"object","required":["videos"],"properties":{"videos":{"type":"array","items":{"$ref":"#/components/schemas/PublicVideo"}}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/videos/{videoId}":{"get":{"summary":"Get a video","operationId":"getVideo","tags":["Videos"],"description":"Returns a single video by ID (IDOR-guarded). Required scope: `videos:read`.","parameters":[{"name":"videoId","in":"path","required":true,"description":"The video ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Video found.","content":{"application/json":{"schema":{"type":"object","required":["video"],"properties":{"video":{"$ref":"#/components/schemas/PublicVideo"}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a video","operationId":"deleteVideo","tags":["Videos"],"description":"Delete a video and all its data (embedding vectors, transcript, storage, metadata). Required scope: `videos:write`. `success` is `false` when any cleanup step produced a warning (honest semantics). `warnings` is present when non-empty.","parameters":[{"name":"videoId","in":"path","required":true,"description":"The video ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Video deleted.","content":{"application/json":{"schema":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"False when any cleanup step produced a warning."},"cleanupResults":{"type":"object","description":"Per-step cleanup outcome.","additionalProperties":true},"warnings":{"type":"array","items":{"type":"string"},"description":"Present when non-empty."}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/scopes":{"get":{"summary":"Get knowledge scopes","operationId":"getChatbotScopes","tags":["Scopes"],"description":"Returns the knowledge-scope configuration for a chatbot. Required scope: `chatbots:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Scopes configuration.","content":{"application/json":{"schema":{"type":"object","required":["scopes"],"properties":{"scopes":{"type":"array","items":{"$ref":"#/components/schemas/PublicScope"}},"uiTree":{"type":"array","items":{"type":"object","additionalProperties":true}},"slotWorkflows":{"type":"array","items":{"type":"object","additionalProperties":true}},"defaultSlotId":{"type":"string"},"defaultScopeLabel":{"type":"string"},"showDefaultScopeInSelector":{"type":"boolean"}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update knowledge scopes","operationId":"patchChatbotScopes","tags":["Scopes"],"description":"Partial update of the knowledge-scope configuration. Write set matches GET read set exactly: `scopes`, `uiTree`, `slotWorkflows`, `defaultSlotId`, `defaultScopeLabel`, `showDefaultScopeInSelector`. Unknown fields are ignored. Legacy scope fields (knowledgeScopes, defaultScopeId, scopeRelationships, scopeCanvasLayout, scopeWorkflows, activeScopeWorkflowId) are not managed via this endpoint. Required scope: `chatbots:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Scope configuration fields to update. Write set == read set.","content":{"application/json":{"schema":{"type":"object","properties":{"scopes":{"type":"array","items":{"type":"object","additionalProperties":true}},"uiTree":{"type":"array","items":{"type":"object","additionalProperties":true}},"slotWorkflows":{"type":"array","items":{"type":"object","additionalProperties":true}},"defaultSlotId":{"type":"string"},"defaultScopeLabel":{"type":"string"},"showDefaultScopeInSelector":{"type":"boolean"}},"additionalProperties":false}}}},"responses":{"200":{"description":"Scopes updated. Returns refreshed scopes payload.","content":{"application/json":{"schema":{"type":"object","required":["scopes"],"properties":{"scopes":{"type":"array","items":{"$ref":"#/components/schemas/PublicScope"}},"uiTree":{"type":"array","items":{"type":"object","additionalProperties":true}},"slotWorkflows":{"type":"array","items":{"type":"object","additionalProperties":true}},"defaultSlotId":{"type":"string"},"defaultScopeLabel":{"type":"string"},"showDefaultScopeInSelector":{"type":"boolean"}},"additionalProperties":false}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a knowledge scope","operationId":"createChatbotScope","tags":["Scopes"],"description":"Create a single knowledge scope. The scope `id` is derived by slugifying `id` (if given) else `label` (lowercase; `[^a-z0-9_-]` → `-`). Returns 409 if a scope with that id already exists. Unlike PATCH /scopes (which replaces the whole array), this updates one scope in place, so `costLimit`, `authorizedEmails`, and `authorizedDomains` are preserved. Required scope: `chatbots:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"New scope fields. `label` is required; `id` is optional (derived from label if absent).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScopeCreateInput"}}}},"responses":{"201":{"description":"Scope created.","content":{"application/json":{"schema":{"type":"object","required":["scope"],"properties":{"scope":{"$ref":"#/components/schemas/PublicScope"}},"additionalProperties":false}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict — a scope with that id already exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/scopes/{scopeId}":{"get":{"summary":"Get a knowledge scope","operationId":"getChatbotScope","tags":["Scopes"],"description":"Returns a single knowledge scope by id. Billing detail (costLimit) is stripped and PII rosters (emails/domains) are collapsed to counts. Required scope: `chatbots:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"scopeId","in":"path","required":true,"description":"The scope id (namespace slug).","schema":{"type":"string"}}],"responses":{"200":{"description":"Scope found.","content":{"application/json":{"schema":{"type":"object","required":["scope"],"properties":{"scope":{"$ref":"#/components/schemas/PublicScope"}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a knowledge scope","operationId":"patchChatbotScope","tags":["Scopes"],"description":"Partial update of ONE knowledge scope. Only the supplied fields change; absent fields (including `costLimit`, `authorizedEmails`, `authorizedDomains`) are preserved server-side. `null` on an optional field clears it; `[]` clears an array; `costLimit` is replaced atomically. `id` is immutable (ignored). Required scope: `chatbots:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"scopeId","in":"path","required":true,"description":"The scope id (namespace slug).","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Scope fields to update. All optional; `id` is immutable. Clearable optional fields accept `null` to delete them.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScopeUpdateInput"}}}},"responses":{"200":{"description":"Scope updated.","content":{"application/json":{"schema":{"type":"object","required":["scope"],"properties":{"scope":{"$ref":"#/components/schemas/PublicScope"}},"additionalProperties":false}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a knowledge scope","operationId":"deleteChatbotScope","tags":["Scopes"],"description":"Remove one knowledge scope from a chatbot. Also unassigns the scope from the knowledge-base tree and any workflows that referenced it. Returns 409 if the scope still has documents/vectors — reassign or delete them in the dashboard first (no destructive document/vector cascade). Required scope: `chatbots:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"scopeId","in":"path","required":true,"description":"The scope id (namespace slug).","schema":{"type":"string"}}],"responses":{"200":{"description":"Scope deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict — the scope still has documents/vectors.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/deployment":{"get":{"summary":"Get deployment status","operationId":"getChatbotDeployment","tags":["Deployment"],"description":"Returns the deployment status for a chatbot. Internal hosting and credential identifiers are never included in the response. Required scope: `chatbots:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Deployment status.","content":{"application/json":{"schema":{"type":"object","properties":{"deployment":{"$ref":"#/components/schemas/PublicDeployment"}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/deploy":{"post":{"summary":"Trigger a redeploy","operationId":"deployChatbot","tags":["Deployment"],"description":"Re-runs the production build with the chatbot's CURRENT build-time environment. Runtime config (model, behavior, appearance, prompts, etc.) is read live and does NOT require a deploy. This endpoint does NOT re-sync build-time settings (e.g. chatbot name, requireAuth, region); if you changed a build-time field, use the dashboard redeploy. Long-running operation (up to 5 min). The response is returned immediately with status `deploying`; poll GET /deployment for the final status. If a build exceeds ~60s the status may remain `deploying`; the already-deploying guard clears after ~10 minutes. Required scope: `deploy`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Deployment triggered.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"status":{"type":"string","enum":["deploying"]},"deployment":{"$ref":"#/components/schemas/PublicDeployment"}},"additionalProperties":false}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/domains":{"get":{"summary":"List authorized domains","operationId":"getChatbotDomains","tags":["Domains"],"description":"Returns the authorized login domains and custom domain status for a chatbot. Internal IDs and auth tokens are stripped. Required scope: `domains:read`. Requires admin-level access (`canManageAccess`); callers with only editor access will receive 404 here.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Domain status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicDomainStatus"}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Authorize a custom domain","operationId":"authorizeChatbotDomain","tags":["Domains"],"description":"Adds a custom domain to the chatbot's authorized login domain list. Requires a paid plan tier that supports custom domains. On success returns the updated domain status. On failure (the domain authorization call failed) returns 400 with `PublicDomainStatus` (status: `pending_manual`) or a standard `Error` envelope for validation/config failures. Required scope: `domains:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"The custom domain to authorize.","content":{"application/json":{"schema":{"type":"object","required":["domain"],"properties":{"domain":{"type":"string","description":"Custom domain to authorize (e.g. chat.acme.com). Schemes and trailing slashes are stripped automatically.","example":"chat.acme.com"}},"additionalProperties":false}}}},"responses":{"200":{"description":"Domain authorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicDomainStatus"}}}},"400":{"description":"Bad Request — either a validation/config error (Error envelope) or a soft-failure where domain authorization failed but manual DNS setup is possible (PublicDomainStatus with status: pending_manual).","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/PublicDomainStatus"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/domains/{domain}":{"delete":{"summary":"Deauthorize a custom domain","operationId":"deauthorizeChatbotDomain","tags":["Domains"],"description":"Removes a custom domain from the chatbot's authorized login domain list. Required scope: `domains:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"domain","in":"path","required":true,"description":"Domain to deauthorize (URL-encoded if it contains dots).","schema":{"type":"string"},"example":"chat.acme.com"}],"responses":{"200":{"description":"Domain deauthorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicDomainStatus"}}}},"400":{"description":"Bad Request — either a validation/config error (Error envelope) or a soft-failure where the domain removal failed (PublicDomainStatus with status: removal_failed, possibly including a warning field).","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/PublicDomainStatus"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/domains/{domain}/verify":{"post":{"summary":"Verify domain DNS","operationId":"verifyChatbotDomain","tags":["Domains"],"description":"Triggers a DNS verification check for the given domain. Returns `verified: true` when DNS is correctly configured; `verified: false` with a pending message when DNS is not yet propagated. Required scope: `domains:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"domain","in":"path","required":true,"description":"Domain to verify (URL-encoded if it contains dots).","schema":{"type":"string"},"example":"chat.acme.com"}],"responses":{"200":{"description":"Verification result.","content":{"application/json":{"schema":{"type":"object","properties":{"verified":{"type":"boolean","description":"True when DNS is verified."},"domain":{"type":"string","description":"The normalized domain."},"message":{"type":"string","description":"Human-readable result message."}},"additionalProperties":false}}}},"400":{"description":"Bad Request — either a validation/config error (Error envelope) or a soft-failure (e.g. domain_not_configured, no active deployment) where the response may include the verification result shape ({verified, domain, message}) or a standard Error envelope.","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"verified":{"type":"boolean"},"domain":{"type":"string"},"message":{"type":"string"}},"additionalProperties":false},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/mcp-servers":{"get":{"summary":"List MCP servers","operationId":"listMcpServers","tags":["MCP Servers"],"description":"Returns all MCP servers configured for a chatbot. Credentials, headers, env vars, and access-key hashes are stripped. Email/group rosters are collapsed to counts. OAuth flows, PostgreSQL scan, and metrics are dashboard-only (not yet in v1 API). Required scope: `mcp:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"List of MCP servers.","content":{"application/json":{"schema":{"type":"object","properties":{"mcpEnabled":{"type":"boolean","description":"Whether MCP is enabled for this chatbot."},"servers":{"type":"array","items":{"$ref":"#/components/schemas/PublicMcpServer"}}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create an MCP server","operationId":"createMcpServer","tags":["MCP Servers"],"description":"Create a new MCP server configuration for a chatbot. Stdio transport is not supported via the management API (use HTTP transport). Tool discovery is attempted automatically for HTTP servers. OAuth flows, PostgreSQL scan, and metrics are dashboard-only (deferred). Required scope: `mcp:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"MCP server configuration.","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Server display name."},"description":{"type":"string","description":"Server description."},"type":{"type":"string","description":"Server type (e.g., atlassian, postgresql, generic)."},"transport":{"type":"string","enum":["http"],"description":"Transport protocol. Only http is supported via the v1 API."},"url":{"type":"string","description":"Server URL (required for http transport)."},"credentialSource":{"type":"string","enum":["wizchat","user","oauth"],"description":"Where credentials come from."},"enabled":{"type":"boolean","description":"Whether this server is active on creation. Defaults to true."},"modelOverride":{"type":"string","description":"Gateway model ID to override for this server (validated against catalog)."},"reasoningEffort":{"type":"string","enum":["none","minimal","low","medium","high","default"],"description":"Per-MCP thinking level applied to the override model at runtime; null/absent = inherit. Normalized per provider at runtime."},"executionMode":{"type":"string","enum":["remote_http","return_script"],"description":"Tool call execution mode."},"allowUiResources":{"type":"boolean","description":"Allow this server to return MCP UI resources."},"allowLlmCompletion":{"type":"boolean","description":"Allow in-server LLM completion (requires allowUiResources)."}},"additionalProperties":true}}}},"responses":{"201":{"description":"MCP server created.","content":{"application/json":{"schema":{"type":"object","properties":{"server":{"$ref":"#/components/schemas/PublicMcpServer"},"toolModelWarnings":{"type":"array","items":{"type":"string"},"description":"Non-fatal warnings about model-backed tool declarations."}},"additionalProperties":false}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/mcp-servers/{serverId}":{"get":{"summary":"Get an MCP server","operationId":"getMcpServer","tags":["MCP Servers"],"description":"Returns a single MCP server by ID. Credentials, headers, and access-key hashes are stripped. Required scope: `mcp:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"serverId","in":"path","required":true,"description":"MCP server ID (mcp_…).","schema":{"type":"string"}}],"responses":{"200":{"description":"MCP server found.","content":{"application/json":{"schema":{"type":"object","properties":{"server":{"$ref":"#/components/schemas/PublicMcpServer"}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update an MCP server","operationId":"patchMcpServer","tags":["MCP Servers"],"description":"Partial update of an MCP server configuration. Supports: enabled toggle, modelOverride, credential update, tool re-discovery, accessKeyAction (generate/clear), executionMode, allowUiResources, allowLlmCompletion. OAuth flows, PostgreSQL scan, and metrics are dashboard-only (deferred). Required scope: `mcp:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"serverId","in":"path","required":true,"description":"MCP server ID (mcp_…).","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Fields to update. Only config fields are accepted; access-control and identity fields are immutable here.","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Updated server display name."},"description":{"type":"string","description":"Updated server description."},"enabled":{"type":"boolean","description":"Enable or disable this server."},"modelOverride":{"type":"string","description":"Updated gateway model override (validated against catalog)."},"reasoningEffort":{"type":"string","enum":["none","minimal","low","medium","high","default"],"description":"Per-MCP thinking level applied to the override model at runtime; null = inherit. Normalized per provider at runtime."},"url":{"type":"string","description":"Updated server URL (HTTPS required; SSRF-validated)."},"headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Updated request headers."},"httpProtocol":{"type":"string","description":"HTTP protocol variant."},"executionMode":{"type":"string","enum":["remote_http","return_script"],"description":"Tool call execution mode."},"localScriptFormat":{"type":"object","additionalProperties":true,"description":"Local script format config (return_script mode)."},"instructions":{"type":"string","description":"Updated server instructions."},"credentials":{"type":"object","additionalProperties":true,"description":"Updated user-provided credentials (only accepted when credentialSource=user; encrypted at rest)."},"credentialSource":{"type":"string","enum":["wizchat","user","oauth"],"description":"Updated credential source."},"type":{"type":"string","description":"Updated server type."},"centralizedCredentials":{"type":"array","items":{"type":"string"},"description":"Updated centralized credential env-var names."},"toolNamePrefix":{"type":"string","description":"Updated tool name prefix."},"allowUiResources":{"type":"boolean","description":"Allow MCP UI resources."},"allowLlmCompletion":{"type":"boolean","description":"Allow in-server LLM completion (requires allowUiResources)."},"schemaNames":{"type":"string","description":"Updated PostgreSQL schema names (merged into credentials)."},"accessKeyAction":{"type":"string","enum":["generate","clear"],"description":"Generate a new access key or clear the existing one. Processed transactionally; mutually exclusive with config field changes in the same request."},"discoverTools":{"type":"boolean","description":"Re-run tool discovery against the server URL (SSRF-validated)."}},"additionalProperties":false}}}},"responses":{"200":{"description":"MCP server updated.","content":{"application/json":{"schema":{"type":"object","properties":{"server":{"$ref":"#/components/schemas/PublicMcpServer"},"accessKey":{"type":"string","description":"One-time plaintext access key (only present when accessKeyAction=generate)."},"toolModelWarnings":{"type":"array","items":{"type":"string"},"description":"Non-fatal warnings about model-backed tool declarations."}},"additionalProperties":false}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete an MCP server","operationId":"deleteMcpServer","tags":["MCP Servers"],"description":"Remove an MCP server from a chatbot. For PostgreSQL servers, the associated schema graph is also deleted (non-fatal on error). Required scope: `mcp:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"serverId","in":"path","required":true,"description":"MCP server ID (mcp_…).","schema":{"type":"string"}}],"responses":{"200":{"description":"MCP server deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/mcp-servers/{serverId}/access":{"get":{"summary":"Get MCP server access control","operationId":"getMcpServerAccess","tags":["MCP Servers"],"description":"Returns the access-control settings for an MCP server: authorizedEmails, authorizedGroups, isPublic, accessKeyHint. The access-key hash is never returned. Required scope: `mcp:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"serverId","in":"path","required":true,"description":"MCP server ID (mcp_…).","schema":{"type":"string"}}],"responses":{"200":{"description":"Access control settings.","content":{"application/json":{"schema":{"type":"object","properties":{"isPublic":{"type":"boolean","description":"Whether the server is publicly accessible (default true for legacy servers)."},"accessKeyHint":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"Truncated display-only hint of the access key, or null when no key is set."},"authorizedEmails":{"type":"array","items":{"type":"string"},"description":"Email addresses authorised to access this server."},"authorizedGroups":{"type":"array","items":{"type":"string"},"description":"Group IDs authorised to access this server."}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"summary":"Update MCP server access control","operationId":"putMcpServerAccess","tags":["MCP Servers"],"description":"Replace the access-control settings for an MCP server. Supports: isPublic, authorizedEmails, authorizedGroups, and accessKeyAction. When accessKeyAction=generate a one-time plaintext accessKey is returned. Required scope: `mcp:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"serverId","in":"path","required":true,"description":"MCP server ID (mcp_…).","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Access control fields to update.","content":{"application/json":{"schema":{"type":"object","properties":{"isPublic":{"type":"boolean","description":"Set server visibility."},"authorizedEmails":{"type":"array","items":{"type":"string"},"description":"Replacement list of authorised email addresses."},"authorizedGroups":{"type":"array","items":{"type":"string"},"description":"Replacement list of authorised group IDs."},"accessKeyAction":{"type":"string","enum":["generate","clear"],"description":"generate: rotate/create the access key (returns one-time plaintext accessKey). clear: remove the access key (server becomes unauthenticated)."}},"additionalProperties":false}}}},"responses":{"200":{"description":"Access control updated.","content":{"application/json":{"schema":{"type":"object","properties":{"isPublic":{"type":"boolean","description":"Current server visibility."},"accessKeyHint":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"Display-only hint of the current access key, or null when cleared."},"authorizedEmails":{"type":"array","items":{"type":"string"},"description":"Current authorised email list."},"authorizedGroups":{"type":"array","items":{"type":"string"},"description":"Current authorised group list."},"accessKey":{"type":"string","description":"One-time plaintext access key — only present when accessKeyAction=generate. Store it now; it cannot be retrieved again."}},"additionalProperties":false}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/skills":{"get":{"summary":"List skills","operationId":"listSkills","tags":["Skills"],"description":"Returns all skills configured for a chatbot. Required scope: `skills:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"List of skills.","content":{"application/json":{"schema":{"type":"object","properties":{"skillsEnabled":{"type":"boolean","description":"Whether the skills feature is enabled for this chatbot."},"skills":{"type":"array","items":{"$ref":"#/components/schemas/PublicSkill"}}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a skill","operationId":"createSkill","tags":["Skills"],"description":"Create a new skill for a chatbot. Auto-enables the skills feature on first creation. Name must be unique within the chatbot (max 25 skills per chatbot). Required scope: `skills:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Skill configuration.","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Skill name (max 80 chars, must be unique within chatbot)."},"description":{"type":"string","description":"Skill description (max 500 chars)."},"body":{"type":"string","description":"Skill body/instructions (max 32 KB)."},"mode":{"type":"string","enum":["on_demand","always"],"description":"When the skill is injected: on_demand (tool-call) or always (every turn)."},"linkedServerIds":{"type":"array","items":{"type":"string"},"description":"MCP server IDs that trigger this skill automatically."},"linkedAgentIds":{"type":"array","items":{"type":"string"},"description":"Inner agent IDs (eval/codegen/vision/native-mcp/sales) that trigger this skill."},"sourceServerId":{"type":"string","description":"MCP server ID this skill was imported from (informational)."}},"additionalProperties":false}}}},"responses":{"201":{"description":"Skill created.","content":{"application/json":{"schema":{"type":"object","properties":{"skill":{"$ref":"#/components/schemas/PublicSkill"}},"additionalProperties":false}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/skills/{skillId}":{"get":{"summary":"Get a skill","operationId":"getSkill","tags":["Skills"],"description":"Returns a single skill by ID. Required scope: `skills:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"skillId","in":"path","required":true,"description":"Skill ID (skill_…).","schema":{"type":"string"}}],"responses":{"200":{"description":"Skill found.","content":{"application/json":{"schema":{"type":"object","properties":{"skill":{"$ref":"#/components/schemas/PublicSkill"}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a skill","operationId":"patchSkill","tags":["Skills"],"description":"Partial update of a skill. Can update: name, description, body, enabled, mode, linkedServerIds, linkedAgentIds. Immutable fields (id, createdAt, source, sourceServerId) are ignored in the request body. Name must remain unique within the chatbot. Required scope: `skills:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"skillId","in":"path","required":true,"description":"Skill ID (skill_…).","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Skill fields to update (partial).","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Updated name."},"description":{"type":"string","description":"Updated description."},"body":{"type":"string","description":"Updated body/instructions."},"enabled":{"type":"boolean","description":"Enable or disable this skill."},"mode":{"type":"string","enum":["on_demand","always"],"description":"Injection mode."},"linkedServerIds":{"type":"array","items":{"type":"string"},"description":"Updated MCP server trigger list."},"linkedAgentIds":{"type":"array","items":{"type":"string"},"description":"Updated agent trigger list."}},"additionalProperties":false}}}},"responses":{"200":{"description":"Skill updated.","content":{"application/json":{"schema":{"type":"object","properties":{"skill":{"$ref":"#/components/schemas/PublicSkill"}},"additionalProperties":false}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a skill","operationId":"deleteSkill","tags":["Skills"],"description":"Remove a skill from a chatbot. Required scope: `skills:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"skillId","in":"path","required":true,"description":"Skill ID (skill_…).","schema":{"type":"string"}}],"responses":{"200":{"description":"Skill deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/access-protection":{"get":{"summary":"Get access-protection config","operationId":"getAccessProtection","tags":["Security"],"description":"Returns the widget access-protection configuration for a chatbot. jwtConfig.secret is collapsed to a boolean `hasSecret` flag — the raw secret is never returned. Key hashes are stripped; only safe key metadata (id, label, keyPrefix, status, usageCount, timestamps) is returned. Required scope: `security:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Access-protection configuration.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicAccessProtectionConfig"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update access-protection config","operationId":"patchAccessProtection","tags":["Security"],"description":"Update access-protection enabled flag and/or jwtConfig (enabled, emailClaim, secret). If a new JWT secret is provided it is encrypted at rest; the plaintext is never stored. Returns the updated config (secret collapsed to hasSecret). Required scope: `security:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Enable or disable access protection."},"jwtConfig":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Enable JWT verification."},"emailClaim":{"type":"string","description":"JWT claim containing the user email."},"secret":{"type":"string","description":"JWT signing secret (write-only; stored encrypted; never returned)."}},"additionalProperties":false}},"additionalProperties":false}}}},"responses":{"200":{"description":"Access-protection configuration updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicAccessProtectionConfig"}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/access-protection/keys":{"get":{"summary":"List access-protection keys","operationId":"listAccessProtectionKeys","tags":["Security"],"description":"Returns all access-protection keys for a chatbot. Key hashes are stripped; only safe metadata (id, label, keyPrefix, status, usageCount, timestamps) is returned. Required scope: `security:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"List of access-protection keys.","content":{"application/json":{"schema":{"type":"object","properties":{"keys":{"type":"array","items":{"$ref":"#/components/schemas/PublicAccessProtectionKey"}}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create an access-protection key","operationId":"createAccessProtectionKey","tags":["Security"],"description":"Mint a new widget access-protection key. The full plaintext key is returned ONCE in the response body. **Store it immediately — it cannot be retrieved again.** Subsequent reads expose only the `keyPrefix` display hint. Required scope: `security:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"label":{"type":"string","description":"Display label for the key (max 100 chars)."}},"additionalProperties":false}}}},"responses":{"201":{"description":"Key created. `key` is the one-time plaintext value.","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string","description":"One-time plaintext access key. Store it now; it cannot be retrieved again."},"record":{"$ref":"#/components/schemas/PublicAccessProtectionKey"}},"additionalProperties":false}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/access-protection/keys/{keyId}":{"delete":{"summary":"Revoke an access-protection key","operationId":"revokeAccessProtectionKey","tags":["Security"],"description":"Revoke (soft-delete) an access-protection key. Revoked keys are immediately rejected; they remain in the list with status=revoked for audit purposes. Required scope: `security:write`.\n\nReturns `{ id, status: \"revoked\", revokedAt }` on success.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"keyId","in":"path","required":true,"description":"Access-protection key ID (apk_…).","schema":{"type":"string"}}],"responses":{"200":{"description":"Key revoked.","content":{"application/json":{"schema":{"type":"object","required":["id","status","revokedAt"],"properties":{"id":{"type":"string","description":"The revoked key ID."},"status":{"type":"string","enum":["revoked"],"description":"Always \"revoked\"."},"revokedAt":{"type":"string","format":"date-time","description":"ISO-8601 revocation timestamp."}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/external-api":{"get":{"summary":"Get external-API config","operationId":"getExternalApiConfig","tags":["Security"],"description":"Returns the external-API configuration for a chatbot. Key hashes are stripped; only safe key metadata is returned. Required scope: `security:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"External-API configuration.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicExternalApiConfig"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update external-API config","operationId":"patchExternalApiConfig","tags":["Security"],"description":"Update the external-API enabled flag and/or rate limit. Required scope: `security:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Enable or disable the external API."},"rateLimitPerMinute":{"type":"integer","minimum":1,"maximum":1000,"description":"Requests per minute per key (1-1000)."}},"additionalProperties":false}}}},"responses":{"200":{"description":"External-API configuration updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicExternalApiConfig"}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/external-api/keys":{"get":{"summary":"List external-API keys","operationId":"listExternalApiKeys","tags":["Security"],"description":"Returns all external-API keys for a chatbot. Key hashes are stripped; only safe metadata is returned. Required scope: `security:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"List of external-API keys.","content":{"application/json":{"schema":{"type":"object","properties":{"keys":{"type":"array","items":{"$ref":"#/components/schemas/PublicExternalApiKey"}}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create an external-API key","operationId":"createExternalApiKey","tags":["Security"],"description":"Mint a new external-API key. The full plaintext key is returned ONCE in the response body (`key` field). **Store it immediately — it cannot be retrieved again.** Subsequent reads expose only the `keyPrefix` display hint. Maximum 5 active keys per chatbot. Required scope: `security:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Key display name (required; max 100 chars)."},"description":{"type":"string","description":"Optional description (max 500 chars)."}},"additionalProperties":false}}}},"responses":{"201":{"description":"Key created. `key` is the one-time plaintext value.","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string","description":"One-time plaintext API key. Store it now; it cannot be retrieved again."},"record":{"$ref":"#/components/schemas/PublicExternalApiKey"}},"additionalProperties":false}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/external-api/keys/{keyId}":{"delete":{"summary":"Revoke an external-API key","operationId":"revokeExternalApiKey","tags":["Security"],"description":"Revoke (soft-delete) an external-API key. Revoked keys are immediately rejected. Required scope: `security:write`.\n\nReturns `{ id, status: \"revoked\", revokedAt }` on success.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"keyId","in":"path","required":true,"description":"External-API key ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Key revoked.","content":{"application/json":{"schema":{"type":"object","required":["id","status","revokedAt"],"properties":{"id":{"type":"string","description":"The revoked key ID."},"status":{"type":"string","enum":["revoked"],"description":"Always \"revoked\"."},"revokedAt":{"type":"string","format":"date-time","description":"ISO-8601 revocation timestamp."}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/access-lists":{"get":{"summary":"Get access lists","operationId":"getAccessLists","tags":["Security"],"description":"Returns the authorizedEmails, authorizedDomains, and allowedEmbedDomains lists for a chatbot. Unlike the PublicChatbot listing (which collapses these to counts for privacy), this endpoint returns the full rosters for headless config-as-code use cases. Requires admin-level access (`canManageAccess`). Required scope: `security:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Access lists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicAccessLists"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update access lists","operationId":"patchAccessLists","tags":["Security"],"description":"Replace one or more access lists. Each supplied list is replaced wholesale; omitted lists are unchanged. At least one list must be provided. Requires admin-level access (`canManageAccess`). Required scope: `security:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"authorizedEmails":{"type":"array","items":{"type":"string"},"description":"Replacement list of authorised email addresses."},"authorizedDomains":{"type":"array","items":{"type":"string"},"description":"Replacement list of authorised email domains."},"allowedEmbedDomains":{"type":"array","items":{"type":"string"},"description":"Replacement list of allowed embed domains."}},"additionalProperties":false}}}},"responses":{"200":{"description":"Access lists updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicAccessLists"}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/telemetry":{"get":{"summary":"Get telemetry config","operationId":"getTelemetryConfig","tags":["Security"],"description":"Returns the telemetry configuration for a chatbot. Fields: level (anonymized|identified|full), endUserOptOutAllowed, pseudonymizeIdentifiers, retentionMonths. Required scope: `security:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Telemetry configuration.","content":{"application/json":{"schema":{"type":"object","properties":{"telemetryConfig":{"$ref":"#/components/schemas/PublicTelemetry"}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"summary":"Replace telemetry config","operationId":"putTelemetryConfig","tags":["Security"],"description":"Replace the telemetry configuration for a chatbot. All four fields are required. When `containsSensitiveData` is true on the chatbot, `level` must be \"anonymized\" and `endUserOptOutAllowed` will be forced to false. Required scope: `security:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicTelemetry"}}}},"responses":{"200":{"description":"Telemetry configuration updated.","content":{"application/json":{"schema":{"type":"object","properties":{"telemetryConfig":{"$ref":"#/components/schemas/PublicTelemetry"}},"additionalProperties":false}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/analytics/cost":{"get":{"summary":"Get cost analytics","operationId":"getChatbotCostAnalytics","tags":["Analytics"],"description":"Returns cost analytics for a single chatbot over the requested period. Tier-gated: the plan's analytics day-cap is applied automatically; a 403 is returned when the plan does not include cost analytics access. Combines completed-day cost rollups with the current day's partial usage. No PII — ownerEmail, userId, and per-other-chatbot breakdowns are always stripped. Required scope: `analytics:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"days","in":"query","required":false,"description":"Lookback window in days (default: 30). Clamped to the plan's analytics day-cap. 1-365.","schema":{"type":"integer","minimum":1,"maximum":365}}],"responses":{"200":{"description":"Cost analytics for the chatbot.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicCostAnalytics"}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/analytics/conversations":{"get":{"summary":"Get conversation-intelligence analytics","operationId":"getChatbotConversationAnalytics","tags":["Analytics"],"description":"Conversation-intelligence aggregates for a single chatbot at dashboard parity: top topics, knowledge gaps, language distribution, scope usage, hourly heatmap, conversation depth. topTopics/knowledgeGaps titles are k-anonymity-gated (≥3 distinct sessions) and exposed exactly as the dashboard shows the owner. No raw session or user identifiers are ever returned — only aggregated, anonymized results. Tier-gated (pro-plus+). Required scope: `analytics:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"days","in":"query","required":false,"description":"Lookback window in days (default 30). Clamped to the plan's analytics day-cap. 1-365.","schema":{"type":"integer","minimum":1,"maximum":365}}],"responses":{"200":{"description":"Conversation analytics for the chatbot.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicConversationAnalytics"}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/analytics/queries":{"get":{"summary":"Get query-log analytics","operationId":"getChatbotQueriesAnalytics","tags":["Analytics"],"description":"Row-level query log for a single chatbot at dashboard parity — the owner's own conversation log. Question and answer text are PII-redacted at insert and the end-user's email is masked. Internal session, row, and network identifiers are never emitted. Each row's sources[] is reshaped to a public citation {title, source, pdfSource} — the raw retrieved chunk text and all other internal source metadata are dropped. Tier-gated (starter+). Required scope: `analytics:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"days","in":"query","required":false,"description":"Lookback window in days (default 30). Clamped to the plan's analytics day-cap. 1-365.","schema":{"type":"integer","minimum":1,"maximum":365}},{"name":"limit","in":"query","required":false,"description":"Rows per page (1-500, default 100).","schema":{"type":"integer","minimum":1,"maximum":500}},{"name":"offset","in":"query","required":false,"description":"Pagination offset (default 0). Page forward with offset += limit while hasMore is true.","schema":{"type":"integer","minimum":0}},{"name":"thumb","in":"query","required":false,"description":"Feedback filter.","schema":{"type":"string","enum":["up","down","none"]}},{"name":"unanswered","in":"query","required":false,"description":"When true, only unanswered rows.","schema":{"type":"boolean"}},{"name":"search","in":"query","required":false,"description":"Case-insensitive substring match on question OR answer.","schema":{"type":"string"}}],"responses":{"200":{"description":"Query-log rows + feedback breakdown for the chatbot.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicQueriesAnalytics"}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/analytics/quality":{"get":{"summary":"Get agent quality analytics","operationId":"getChatbotQualityAnalytics","tags":["Analytics"],"description":"Agent behavior & quality aggregates for a single chatbot at dashboard parity (aggregate metrics only — no raw traces, no PII). Answer-quality breakdown, agent decisions, average latency, and MCP tool-error tracking. The chatbot scope is derived server-side from the authenticated owner — no email parameter is accepted. Tier-gated (pro-plus+). Required scope: `analytics:read`. `answerRate` is `number | null`, computed only over turns the chatbot classified; it is `null` (distinct from a genuine 0%) whenever `answerRateTracked` is false — i.e. no classified turns in the window, or the metrics were temporarily unavailable. Consumers must handle `null` before arithmetic and can read `answerRateTracked` to explain the absence.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"days","in":"query","required":false,"description":"Lookback window in days (default 30). Clamped to the plan's analytics day-cap. 1-365.","schema":{"type":"integer","minimum":1,"maximum":365}}],"responses":{"200":{"description":"Quality analytics for the chatbot.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicQualityAnalytics"}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/analytics/search":{"get":{"summary":"Get search / retrieval-quality analytics","operationId":"getChatbotSearchAnalytics","tags":["Analytics"],"description":"Retrieval-effectiveness aggregates for a single chatbot at dashboard parity: score histogram, per-scope retrieval quality, skip-RAG ratio, sources-per-query, and weakest queries. weakestQueries titles are k-anonymity-gated (≥3 sessions) and exposed exactly as the dashboard shows the owner. Tier-gated (pro+). Required scope: `analytics:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"days","in":"query","required":false,"description":"Lookback window in days (default 30). Clamped to the plan's analytics day-cap. 1-365.","schema":{"type":"integer","minimum":1,"maximum":365}}],"responses":{"200":{"description":"Search analytics for the chatbot.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicSearchAnalytics"}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/analytics/infra":{"get":{"summary":"Get infrastructure analytics","operationId":"getChatbotInfraAnalytics","tags":["Analytics"],"description":"Infrastructure signals for a single chatbot at dashboard parity: latency and TTFT percentiles + histograms, region split, deploy-version timeline (regression detection), and warm-vs-cold ratio. deploymentVersion and dataRegion are owner-facing operational metadata, not user machine ids. Tier-gated (pro+). Required scope: `analytics:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"days","in":"query","required":false,"description":"Lookback window in days (default 30). Clamped to the plan's analytics day-cap. 1-365.","schema":{"type":"integer","minimum":1,"maximum":365}}],"responses":{"200":{"description":"Infrastructure analytics for the chatbot.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicInfraAnalytics"}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/analytics/transcription":{"get":{"summary":"Get video-transcription analytics","operationId":"getChatbotTranscriptionAnalytics","tags":["Analytics"],"description":"Video-transcription aggregates for a single chatbot at dashboard parity: totals (videos, duration, cost, char/word counts) and breakdowns by service, language, platform, and model. Per-other-chatbot breakdowns are stripped. NOT tier-gated — matches the ungated dashboard route (auth + `analytics:read` + ownership only). All-time totals; no `days` parameter is applied. Required scope: `analytics:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Transcription analytics for the chatbot.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicTranscriptionAnalytics"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/analytics/tokens":{"get":{"summary":"Get token-efficiency analytics","operationId":"getChatbotTokenAnalytics","tags":["Analytics"],"description":"Token-usage & latency aggregates for a single chatbot at dashboard parity: total/avg/percentile tokens, model mix, context-growth buckets, and TTFT-by-model. Aggregate-only (no raw text). Tier-gated (pro+). Required scope: `analytics:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"days","in":"query","required":false,"description":"Lookback window in days (default 30). Clamped to the plan's analytics day-cap. 1-365.","schema":{"type":"integer","minimum":1,"maximum":365}}],"responses":{"200":{"description":"Token analytics for the chatbot.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicTokenAnalytics"}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/analytics/aggregate":{"get":{"summary":"Get workspace usage overview","operationId":"getAggregateAnalytics","tags":["Analytics"],"description":"Owner-level (NOT chatbot-scoped) 7-day cross-chatbot usage overview: per-day query trend + total queries, total chatbots, and deployed-chatbot count. Loops all chatbots the caller owns. FULL-ACCESS CALLER REQUIRED: a chatbot-scoped key (allowedChatbotIds set) receives 403 `chatbot_not_in_key_scope` — it cannot read workspace-wide totals. The dashboard `success` flag and `debug` block are never emitted. Tier-gated (usage, starter+). Required scope: `analytics:read`.","parameters":[],"responses":{"200":{"description":"Workspace-level usage overview for the calling owner.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicAggregateAnalytics"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/sales-agent/leads":{"get":{"summary":"List sales-agent leads (metadata only)","operationId":"listChatbotSalesAgentLeads","tags":["Analytics"],"description":"Lists a chatbot's captured sales-agent leads over the requested window. Returns METADATA ONLY — disposition, handoff status, country code, timestamps, coarse attribution, and disposition/country aggregates. NO decrypted PII (name/email/phone/custom capture fields, city, region, IP) is exposed by this list. Owner/admin gated (mirrors the dashboard leads view — CRM-sensitive disposition labels are not readable by domain-grant viewers). Required scope: `analytics:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"days","in":"query","required":false,"description":"Lookback window in days (default 30, max 365).","schema":{"type":"integer","minimum":1,"maximum":365}},{"name":"disposition","in":"query","required":false,"description":"Comma-list disposition filter (e.g. 'won,lost,unset'; 'unset' matches leads with no disposition).","schema":{"type":"string"}},{"name":"country","in":"query","required":false,"description":"ISO-3166-1 alpha-2 country filter (e.g. US).","schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"Page number, 1-based (default 1).","schema":{"type":"integer","minimum":1}},{"name":"pageSize","in":"query","required":false,"description":"Items per page (1-100, default 25).","schema":{"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"Lead metadata list + disposition/country aggregates for the chatbot.","content":{"application/json":{"schema":{"type":"object","properties":{"windowDays":{"type":"integer"},"windowStart":{"type":"string"},"windowEnd":{"type":"string"},"counts":{"type":"object","additionalProperties":true,"description":"Lead counts by disposition (total, won, lost, disqualified, unset)."},"countries":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Lead counts by ISO country code."},"pagination":{"type":"object","properties":{"page":{"type":"integer"},"pageSize":{"type":"integer"},"totalItems":{"type":"integer"},"totalPages":{"type":"integer"}}},"leads":{"type":"array","description":"Metadata-only lead items (no PII).","items":{"type":"object","properties":{"leadId":{"oneOf":[{"type":"string"},{"type":"null"}]},"capturedAtSeconds":{"oneOf":[{"type":"integer"},{"type":"null"}]},"countryCode":{"oneOf":[{"type":"string"},{"type":"null"}]},"scopeNamespace":{"oneOf":[{"type":"string"},{"type":"null"}]},"disposition":{"oneOf":[{"type":"string"},{"type":"null"}]},"handoffStatus":{"oneOf":[{"type":"string"},{"type":"null"}]},"handoffCompletedAtSeconds":{"oneOf":[{"type":"integer"},{"type":"null"}]},"turnsToCapture":{"oneOf":[{"type":"integer"},{"type":"null"}]},"piiErased":{"type":"boolean"},"piiErasedAtSeconds":{"oneOf":[{"type":"integer"},{"type":"null"}]},"attribution":{"oneOf":[{"type":"object","additionalProperties":true},{"type":"null"}]}}}},"totalAfterFilters":{"type":"integer"},"totalUnclampedInWindow":{"type":"integer"},"listCap":{"type":"integer"},"listTruncated":{"type":"boolean"}}}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/sales-agent/leads/export":{"get":{"summary":"Export sales-agent leads as CSV (with decrypted PII)","operationId":"exportSalesAgentLeads","tags":["Analytics"],"description":"Exports a chatbot's captured sales-agent leads as a CSV, over the requested window and filters. Unlike the metadata-only list, this DECRYPTS the captured contact fields (name/email/phone/company) server-side and includes the coarse geo columns. Cells are CSV-injection-hardened (a leading =+@-\\t\\r is escaped with a single quote). Each row carries a `pii_decrypt_status` (ok / failed / erased / none). Capped at 5000 rows after filters — a larger result returns 413 (narrow the window / filters). Owner/admin gated (bulk PII decrypt). Required scope: `security:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"days","in":"query","required":false,"description":"Lookback window in days (default 30, max 365).","schema":{"type":"integer","minimum":1,"maximum":365}},{"name":"disposition","in":"query","required":false,"description":"Comma-list disposition filter (e.g. 'won,lost,unset'; 'unset' matches leads with no disposition).","schema":{"type":"string"}},{"name":"country","in":"query","required":false,"description":"ISO-3166-1 alpha-2 country filter (e.g. US).","schema":{"type":"string"}}],"responses":{"200":{"description":"CSV of the leads slice with decrypted PII + per-row decrypt status.","content":{"text/csv":{"schema":{"type":"string","description":"CSV body (attachment). Header: lead_id, captured_at_iso, country, region, city, scope, disposition, handoff_status, turns_to_capture, pii_decrypt_status, name, email, phone, company, source_door, source_domain, referrer_url."}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Export exceeds the 5000-row per-export cap (after filters). Narrow the window/filters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/sales-agent/leads/{leadId}":{"get":{"summary":"Get a sales-agent lead (with decrypted PII)","operationId":"getSalesAgentLead","tags":["Analytics"],"description":"Reads a single captured sales-agent lead WITH decrypted PII (the audit-logged per-lead detail path). Returns the metadata the list returns PLUS the decrypted contact `pii` (name/email/phone/company/custom), the coarse geo detail (countryCode + region/city/timezone), and a `decryptError` string when server-side decrypt failed (the raw envelope, HMAC hashes, and IP hash are NEVER emitted). 404 for a missing, soft-deleted, or unauthorized lead (IDOR-safe). Owner/admin gated (PII reveal). Required scope: `security:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"leadId","in":"path","required":true,"description":"The lead ID (the conversation/room id).","schema":{"type":"string"}}],"responses":{"200":{"description":"The lead detail with decrypted PII.","content":{"application/json":{"schema":{"type":"object","required":["lead"],"additionalProperties":false,"properties":{"lead":{"$ref":"#/components/schemas/PublicLeadDetail"}}}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/sales-agent/leads/{leadId}/dsar-erase":{"post":{"summary":"DSAR-erase a sales-agent lead's PII (destructive)","operationId":"eraseSalesAgentLead","tags":["Analytics"],"description":"DESTRUCTIVE. GDPR DSAR hard-erase of a single lead's PII: scrubs the encrypted captured-fields envelope, HMAC hashes, and PII-adjacent geo (IP/region/city/timezone) to null while RETAINING the anonymous row (disposition, capturedAt, geo.countryCode) so aggregate metrics stay intact. Idempotent — a repeat erase on an already-erased lead returns 409 and preserves the original piiErasedAt (GDPR Article 17 audit-trail integrity). Owner/admin gated. Required scope: `security:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"leadId","in":"path","required":true,"description":"The lead ID (the conversation/room id).","schema":{"type":"string"}}],"responses":{"200":{"description":"PII scrubbed; anonymous row retained.","content":{"application/json":{"schema":{"type":"object","required":["success","leadId"],"additionalProperties":false,"properties":{"success":{"type":"boolean"},"leadId":{"type":"string"},"message":{"type":"string"}}}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Lead PII was already erased; the original piiErasedAt is preserved (idempotent no-op).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/documents":{"post":{"summary":"Ingest a PDF document from a URL","operationId":"ingestChatbotDocument","tags":["Documents"],"description":"Ingests a PDF into the chatbot's knowledge base by fetching it from a PUBLIC URL (SSRF-safe: http(s) only, public-IP-pinned + rebind-safe, size- and time-bounded), then running the standard extract → chunk → embed pipeline. This endpoint is PDF-only (the fileName / URL must end in .pdf); to ingest a local file, host it at a public URL first. Asynchronous — processing continues in the background; poll GET /documents for status. Required scope: `documents:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"The public PDF URL to ingest, plus optional visibility + scope.","content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Public http(s) URL of the .pdf to ingest."},"fileName":{"type":"string","maxLength":300,"description":"Override the stored file name (must end in .pdf)."},"viewAccessLevel":{"type":"string","enum":["owner-only","authenticated-users","anyone-with-link"],"description":"Visibility of the document (default 'anyone-with-link')."},"authorizedEmails":{"type":"array","items":{"type":"string"},"maxItems":200,"description":"Emails allowed to view (when access is restricted; ≤ 200)."},"scopeId":{"type":"string","description":"Target knowledge-scope id (omit for the default scope)."}}}}}},"responses":{"200":{"description":"Document accepted; processing started in the background.","content":{"application/json":{"schema":{"type":"object","required":["success","status"],"properties":{"success":{"type":"boolean"},"document_id":{"type":"string","description":"The document id — use with GET /documents to poll."},"fileName":{"type":"string"},"bytes":{"type":"integer","description":"Bytes fetched from the URL."},"contentType":{"type":"string"},"status":{"type":"string","description":"Initial status (typically 'processing')."}}}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"File exceeds the plan upload limit or account storage limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"Unsupported file type (this endpoint ingests PDF only).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/text":{"post":{"summary":"Add text or a .txt/.md file to the knowledge base","operationId":"ingestChatbotText","tags":["Documents"],"description":"Adds text or Markdown straight into the chatbot's knowledge base — no file upload needed. Send a single `text` string (with an optional `fileName`) or a batch of `items` (for example curated question/answer pairs). Each entry becomes its own searchable, listable, deletable knowledge source. Processing is synchronous — the response reports per-entry results. Long entries are split automatically. Required scope: `documents:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"A single `text` (with optional `fileName`) OR a batch of `items`, plus optional visibility + scope.","content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","description":"A single text/Markdown entry to add (use this OR items)."},"fileName":{"type":"string","maxLength":300,"description":"Display name / citation title for the single-text form."},"kind":{"type":"string","enum":["markdown","text","snippet"],"description":"How to split the content (inferred from the file name when omitted)."},"items":{"type":"array","maxItems":50,"description":"A batch of entries (use this OR text); each becomes its own source. Max 50 per request.","items":{"type":"object","required":["text"],"properties":{"text":{"type":"string","description":"The text/Markdown content (up to ~1 MB per entry)."},"fileName":{"type":"string","maxLength":300,"description":"Display name / citation title."},"title":{"type":"string","description":"Alias for fileName."},"kind":{"type":"string","enum":["markdown","text","snippet"]}}}},"viewAccessLevel":{"type":"string","enum":["owner-only","authenticated-users","anyone-with-link","permanent-public"],"description":"Visibility of the added content (default 'owner-only')."},"authorizedEmails":{"type":"array","items":{"type":"string"},"maxItems":200,"description":"Emails allowed to view when access is restricted (≤ 200)."},"authorizedGroups":{"type":"array","items":{"type":"string"},"maxItems":200,"description":"Email-group ids whose members may view when access is restricted; expanded to member emails when the entry is added."},"scopeId":{"type":"string","description":"Target knowledge-scope id (omit for the default scope)."}}}}}},"responses":{"200":{"description":"All entries were added.","content":{"application/json":{"schema":{"type":"object","required":["success","ingested","failed","documents"],"properties":{"success":{"type":"boolean"},"ingested":{"type":"integer","description":"Number of entries added."},"failed":{"type":"integer","description":"Number of entries that failed."},"documents":{"type":"array","description":"Per-entry results, in submission order.","items":{"$ref":"#/components/schemas/IngestChatbotTextDocument"}}}}}}},"207":{"description":"Partial success — some entries were added and some failed (see documents[].success).","content":{"application/json":{"schema":{"type":"object","required":["success","ingested","failed","documents"],"properties":{"success":{"type":"boolean"},"ingested":{"type":"integer","description":"Number of entries added."},"failed":{"type":"integer","description":"Number of entries that failed."},"documents":{"type":"array","description":"Per-entry results, in submission order.","items":{"$ref":"#/components/schemas/IngestChatbotTextDocument"}}}}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"An entry or the batch exceeds the size limit, or the account storage limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Every entry failed to be added (see documents[].error for per-entry reasons).","content":{"application/json":{"schema":{"type":"object","required":["error","documents"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["ingest_failed"],"description":"Always `ingest_failed` for this response.","example":"ingest_failed"},"message":{"type":"string","description":"Human-readable error message."}}},"documents":{"type":"array","description":"Per-entry results, in submission order — inspect `error` on each to see why it failed.","items":{"$ref":"#/components/schemas/IngestChatbotTextDocument"}}}}}}}}}},"/api/v1/chatbots/{chatbotId}/crawls":{"post":{"summary":"Start a website crawl","operationId":"startChatbotCrawl","tags":["Documents"],"description":"Crawls one or more website URLs into the chatbot's knowledge base (embedded as searchable vectors), mirroring the dashboard website-crawl flow. Provide a single `url` or a `urls` array (max 100; the App batches into ≤10-per-request worker calls). Asynchronous — returns 202 with a crawlId + jobId; poll `GET /crawls` to track progress. Refuses a second in-flight crawl for the same domain + version (409). Requires admin-level chatbot access (owner / team admin / per-chatbot admin) — plain team editors cannot crawl via this API. Required scope: `documents:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Crawl request. Provide `url` or `urls` (at least one; max 100).","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","description":"A single website URL to crawl (http/https)."},"urls":{"type":"array","items":{"type":"string"},"maxItems":100,"description":"Website URLs to crawl (http/https; max 100). Use instead of `url` for multiple."},"version":{"type":"string","description":"KB version label for this crawl (default 'V1')."},"customInstructions":{"type":"string","maxLength":4096,"description":"Optional extraction guidance passed to the crawler (≤ 4096 chars)."},"includeTags":{"type":"array","items":{"type":"string"},"maxItems":50,"description":"Optional CSS selectors of additional content to include (≤ 50)."},"excludeTags":{"type":"array","items":{"type":"string"},"maxItems":50,"description":"Optional CSS selectors to SKIP, added to the built-in defaults (cookie banners, chat widgets, footers; ≤ 50)."},"viewAccessLevel":{"type":"string","enum":["owner-only","authenticated-users","anyone-with-link","permanent-public"],"description":"Visibility of the crawled pages (default 'anyone-with-link')."},"authorizedEmails":{"type":"array","items":{"type":"string"},"maxItems":200,"description":"Emails allowed to view (when access is restricted; ≤ 200)."},"authorizedGroups":{"type":"array","items":{"type":"string"},"maxItems":50,"description":"Email-group ids allowed to view (when access is restricted; ≤ 50)."}}}}}},"responses":{"202":{"description":"Crawl accepted and dispatched.","content":{"application/json":{"schema":{"type":"object","required":["crawlId","jobId","status","urlCount"],"additionalProperties":false,"properties":{"crawlId":{"type":"string","description":"The crawl document id — use with GET /crawls?crawlId= to poll."},"jobId":{"type":"string","description":"The primary batch job id."},"status":{"type":"string","description":"Initial status (typically 'processing')."},"urlCount":{"type":"integer","description":"Number of URLs accepted."}}}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"A crawl for this domain + version is already in progress.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List website crawls","operationId":"listChatbotCrawls","tags":["Documents"],"description":"Lists the chatbot's website crawls (most recent first), with live progress. Non-terminal crawls are refreshed against the crawler on read (an API-initiated crawl has no dashboard poller, so this read is what advances its status). Pass `crawlId` to fetch a single crawl. No PII (authorized emails/groups are not returned). Required scope: `documents:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"crawlId","in":"query","required":false,"description":"Return only this crawl (the crawlId from POST /crawls).","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Max crawls to return (1-25, default 10). Ignored when crawlId is set.","schema":{"type":"integer","minimum":1,"maximum":25}}],"responses":{"200":{"description":"Website crawls for the chatbot (metadata + live progress).","content":{"application/json":{"schema":{"type":"object","required":["crawls","count"],"properties":{"crawls":{"type":"array","items":{"type":"object","properties":{"crawlId":{"oneOf":[{"type":"string"},{"type":"null"}]},"chatbotId":{"oneOf":[{"type":"string"},{"type":"null"}]},"displayName":{"oneOf":[{"type":"string"},{"type":"null"}]},"domain":{"oneOf":[{"type":"string"},{"type":"null"}]},"originalUrl":{"oneOf":[{"type":"string"},{"type":"null"}]},"urlCount":{"oneOf":[{"type":"integer"},{"type":"null"}]},"version":{"oneOf":[{"type":"string"},{"type":"null"}]},"status":{"oneOf":[{"type":"string"},{"type":"null"}]},"progress":{"oneOf":[{"type":"number"},{"type":"null"}]},"isComplete":{"type":"boolean"},"partial":{"oneOf":[{"type":"boolean"},{"type":"null"}],"description":"True when the crawl finalized but ≥1 batch's counts are uncounted (totals may understate reality); null until finalized."},"totals":{"oneOf":[{"type":"object","additionalProperties":true},{"type":"null"}]},"error":{"oneOf":[{"type":"string"},{"type":"null"}]},"viewAccessLevel":{"oneOf":[{"type":"string"},{"type":"null"}]},"createdAtSeconds":{"oneOf":[{"type":"integer"},{"type":"null"}]},"updatedAtSeconds":{"oneOf":[{"type":"integer"},{"type":"null"}]},"uploadedAtSeconds":{"oneOf":[{"type":"integer"},{"type":"null"}]}}}},"count":{"type":"integer"}}}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/scheduled-crawls":{"get":{"summary":"List scheduled crawls","operationId":"listChatbotScheduledCrawls","tags":["Documents"],"description":"Lists the chatbot's scheduled (recurring) website crawls, most recent first. A scheduled crawl periodically re-ingests a set of URLs into the KB on an hourly/daily/weekly/monthly cadence. No PII (authorized emails/groups are returned only as counts). Required scope: `documents:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Scheduled crawls for the chatbot.","content":{"application/json":{"schema":{"type":"object","required":["schedules","count"],"properties":{"schedules":{"type":"array","items":{"$ref":"#/components/schemas/ScheduledCrawl"}},"count":{"type":"integer"}}}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a scheduled crawl","operationId":"createChatbotScheduledCrawl","tags":["Documents"],"description":"Creates a recurring scheduled crawl that re-ingests the given URLs into the chatbot's knowledge base on the chosen cadence. `dayOfWeek` is required for `weekly`; `dayOfMonth` (1-28) for `monthly`. Limited to 3 schedules per chatbot and 100 URLs per schedule (a violation returns 400). Requires admin-level chatbot access. Required scope: `documents:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Scheduled-crawl configuration. `chatbotId` is taken from the path and ignored if present in the body. Storage-location fields (such as `domain` and `dataRegion`, plus any internal storage identifiers) are derived server-side from the chatbot — a scheduled crawl always (re-)ingests into that chatbot's own knowledge base — and are ignored if sent.","content":{"application/json":{"schema":{"type":"object","required":["name","urls","schedule"],"properties":{"name":{"type":"string","maxLength":200,"description":"User-friendly name (e.g. \"Weekly Blog Refresh\")."},"urls":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":100,"description":"URLs to (re-)crawl (1-100)."},"schedule":{"type":"string","enum":["hourly","daily","weekly","monthly"],"description":"Cadence."},"dayOfWeek":{"type":"integer","minimum":0,"maximum":6,"description":"0 (Sun) - 6 (Sat). Required for weekly."},"dayOfMonth":{"type":"integer","minimum":1,"maximum":28,"description":"1-28. Required for monthly."},"preferredHour":{"type":"integer","minimum":0,"maximum":23,"description":"Preferred hour of day (0-23)."},"timezone":{"type":"string","description":"IANA timezone (default 'UTC')."},"customInstructions":{"type":"string","maxLength":4096,"description":"Optional extraction guidance (≤ 4096 chars)."},"includeTags":{"type":"array","items":{"type":"string"},"maxItems":50,"description":"CSS selectors to include (≤ 50)."},"maxPages":{"type":"integer","minimum":1,"description":"Max pages per run (default 100)."},"viewAccessLevel":{"type":"string","enum":["public","private","authorized"],"description":"Visibility of crawled content (default 'private')."},"authorizedEmails":{"type":"array","items":{"type":"string"},"maxItems":200,"description":"Emails allowed to view (≤ 200). Not echoed back — only a count is returned."},"authorizedGroups":{"type":"array","items":{"type":"string"},"maxItems":50,"description":"Email-group ids allowed to view (≤ 50). Not echoed back — only a count is returned."}}}}}},"responses":{"201":{"description":"Scheduled crawl created.","content":{"application/json":{"schema":{"type":"object","required":["scheduleId","schedule"],"properties":{"scheduleId":{"type":"string"},"schedule":{"oneOf":[{"$ref":"#/components/schemas/ScheduledCrawl"},{"type":"null"}]}}}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/scheduled-crawls/{scheduleId}":{"get":{"summary":"Get a scheduled crawl","operationId":"getChatbotScheduledCrawl","tags":["Documents"],"description":"Returns one scheduled crawl including its recent run history. No PII (authorized emails/groups are returned only as counts). Required scope: `documents:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"scheduleId","in":"path","required":true,"description":"The scheduled-crawl ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"The scheduled crawl with recent runs.","content":{"application/json":{"schema":{"type":"object","required":["schedule"],"properties":{"schedule":{"$ref":"#/components/schemas/ScheduledCrawl"}}}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a scheduled crawl","operationId":"updateChatbotScheduledCrawl","tags":["Documents"],"description":"Updates a scheduled crawl. Any subset of the mutable fields may be provided; at least one is required. Changing the cadence recalculates the next run time. Required scope: `documents:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"scheduleId","in":"path","required":true,"description":"The scheduled-crawl ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Any subset of the mutable fields. At least one must be present.","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":200},"urls":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":100},"schedule":{"type":"string","enum":["hourly","daily","weekly","monthly"]},"dayOfWeek":{"type":"integer","minimum":0,"maximum":6},"dayOfMonth":{"type":"integer","minimum":1,"maximum":28},"preferredHour":{"type":"integer","minimum":0,"maximum":23},"timezone":{"type":"string"},"customInstructions":{"type":"string","maxLength":4096},"includeTags":{"type":"array","items":{"type":"string"},"maxItems":50},"maxPages":{"type":"integer","minimum":1},"viewAccessLevel":{"type":"string","enum":["public","private","authorized"]},"authorizedEmails":{"type":"array","items":{"type":"string"},"maxItems":200,"description":"Not echoed back — only a count is returned."},"authorizedGroups":{"type":"array","items":{"type":"string"},"maxItems":50,"description":"Not echoed back — only a count is returned."},"enabled":{"type":"boolean"}},"additionalProperties":false}}}},"responses":{"200":{"description":"Scheduled crawl updated.","content":{"application/json":{"schema":{"type":"object","required":["schedule"],"properties":{"schedule":{"oneOf":[{"$ref":"#/components/schemas/ScheduledCrawl"},{"type":"null"}]}}}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a scheduled crawl","operationId":"deleteChatbotScheduledCrawl","tags":["Documents"],"description":"Deletes a scheduled crawl and its run history. Pass `deleteVectors=true` to also purge the vectors this schedule ingested into the KB. Fails closed: if the vector purge fails, the schedule is NOT deleted (its metadata is preserved so the purge can be retried) and a 502 is returned. Required scope: `documents:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"scheduleId","in":"path","required":true,"description":"The scheduled-crawl ID.","schema":{"type":"string"}},{"name":"deleteVectors","in":"query","required":false,"description":"Set to 'true' to also delete the schedule's ingested vectors.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Scheduled crawl deleted.","content":{"application/json":{"schema":{"type":"object","required":["success","deletedVectors"],"additionalProperties":false,"properties":{"success":{"type":"boolean"},"deletedVectors":{"type":"boolean","description":"Whether ingested vectors were ACTUALLY purged (true only when deleteVectors was requested AND the purge succeeded)."}}}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Vector deletion failed — the schedule was NOT deleted (retry, or remove it in the dashboard).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/settings":{"patch":{"summary":"Update chatbot runtime settings","operationId":"updateChatbotSettings","tags":["Chatbots"],"description":"Updates the chatbot's runtime toggles: web search, unanswered-question tracking, long-term conversation memory, and short-term memory strategy. Mirrors the dashboard settings screen. Enabling long-term memory requires a Pro (or higher) plan AND authentication-required access. Disabling long-term memory, or switching the memory strategy away from 'graph', permanently deletes the stored memory and therefore requires `confirmDelete: true`. Only provided fields change; at least one must be present. Required scope: `chatbots:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Any subset of the settings fields. At least one must be present.","content":{"application/json":{"schema":{"type":"object","properties":{"webSearchEnabled":{"type":"boolean","description":"Enable web-search augmentation."},"trackUnanswered":{"type":"boolean","description":"Track questions the chatbot could not answer."},"memoryEnabled":{"type":"boolean","description":"Enable long-term cross-session memory (Pro+ plan and authentication-required access only)."},"memoryStrategy":{"type":"string","enum":["graph","conventional"],"description":"Short-term within-session memory mechanism."},"confirmDelete":{"type":"boolean","description":"Must be true to confirm a change that permanently deletes stored memory (disabling long-term memory, or switching strategy away from graph)."}},"additionalProperties":false}}}},"responses":{"200":{"description":"Settings applied.","content":{"application/json":{"schema":{"type":"object","required":["success","updated"],"additionalProperties":false,"properties":{"success":{"type":"boolean"},"updated":{"type":"array","items":{"type":"string"},"description":"The settings fields that were updated."}}}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/memory-data":{"delete":{"summary":"Delete chatbot memory data","operationId":"deleteChatbotMemoryData","tags":["Chatbots"],"description":"Permanently deletes the chatbot's graph conversation memory for the given scope. `scope=long-term` removes stored facts + session summaries; `scope=short-term` removes within-session conversation nodes. Destructive and irreversible. Required scope: `chatbots:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"scope","in":"query","required":true,"description":"Which memory to delete.","schema":{"type":"string","enum":["long-term","short-term"]}}],"responses":{"200":{"description":"Memory data deleted.","content":{"application/json":{"schema":{"type":"object","required":["success","scope"],"properties":{"success":{"type":"boolean"},"scope":{"type":"string","enum":["long-term","short-term"]},"deleted":{"type":"object","additionalProperties":true,"description":"Counts of deleted nodes/records (shape varies by scope)."},"errors":{"type":"array","items":{"type":"string"},"description":"Non-fatal errors encountered during deletion."}},"additionalProperties":false}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/users":{"get":{"summary":"List deployed users","operationId":"listChatbotUsers","tags":["Security"],"description":"Lists the chatbot's deployed (invited) end-users — each user's email, display name, role, status, email-verification flag, and timestamps. Requires admin-level chatbot access. Required scope: `security:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Deployed users for the chatbot.","content":{"application/json":{"schema":{"type":"object","required":["users","count"],"additionalProperties":false,"properties":{"users":{"type":"array","items":{"$ref":"#/components/schemas/PublicChatbotUser"}},"count":{"type":"integer"}}}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Invite a deployed user","operationId":"inviteChatbotUser","tags":["Security"],"description":"Invites an end-user to the chatbot by email; the user receives a verification email. Required scope: `security:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"additionalProperties":false,"properties":{"email":{"type":"string","description":"Email address to invite."},"displayName":{"type":"string","description":"Optional display name."},"role":{"type":"string","enum":["user","admin"],"description":"Role for the user (default 'user')."}}}}}},"responses":{"201":{"description":"Invitation sent.","content":{"application/json":{"schema":{"type":"object","required":["success"],"additionalProperties":false,"properties":{"success":{"type":"boolean"},"userId":{"type":"string","description":"The invited user id."}}}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Revoke a deployed user","operationId":"removeChatbotUser","tags":["Security"],"description":"Revokes a deployed user's access to the chatbot (disables their account). Required scope: `security:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"userId","in":"query","required":true,"description":"The deployed user id to revoke.","schema":{"type":"string"}}],"responses":{"200":{"description":"User access revoked.","content":{"application/json":{"schema":{"type":"object","required":["success"],"additionalProperties":false,"properties":{"success":{"type":"boolean"}}}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/crawls/drift":{"get":{"summary":"Detect website crawl drift","operationId":"getChatbotCrawlDrift","tags":["Documents"],"description":"Compares a chatbot's crawled URL set for a domain against the domain's CURRENT sitemap.xml, reporting pages ADDED (in the sitemap but not yet crawled) and REMOVED (crawled but no longer in the sitemap). The domain MUST be one the chatbot has already crawled (SSRF-bounded). Content changes on already-crawled pages are NOT detected — no per-page baseline is stored for existing crawls. Required scope: `documents:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"domain","in":"query","required":false,"description":"Crawled domain to check (e.g. docs.example.com). Required only if the chatbot has crawled more than one domain.","schema":{"type":"string"}}],"responses":{"200":{"description":"Crawl-drift report for the domain.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["domain","sitemapAvailable","crawledCount","currentCount","newCount","removedCount","unchangedCount","newUrls","removedUrls","newUrlsTruncated","removedUrlsTruncated","contentChangeSupported","note"],"properties":{"domain":{"type":"string"},"sitemapAvailable":{"type":"boolean","description":"False when no sitemap.xml was found — added/removed cannot be computed."},"crawledCount":{"type":"integer","description":"Distinct crawled URLs (normalized)."},"currentCount":{"type":"integer","description":"Distinct URLs in the current sitemap (normalized)."},"newCount":{"type":"integer","description":"Pages in the sitemap not yet crawled."},"removedCount":{"type":"integer","description":"Crawled pages no longer in the sitemap."},"unchangedCount":{"type":"integer","description":"Pages present in both sets."},"newUrls":{"type":"array","items":{"type":"string"},"maxItems":200,"description":"Sample of new (not-yet-crawled) URLs, capped at 200."},"removedUrls":{"type":"array","items":{"type":"string"},"maxItems":200,"description":"Sample of removed URLs, capped at 200."},"newUrlsTruncated":{"type":"boolean"},"removedUrlsTruncated":{"type":"boolean"},"contentChangeSupported":{"type":"boolean","description":"Always false today — no per-page content baseline is stored, so content changes on crawled pages are not detected."},"note":{"type":"string"}}}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/analytics/usage":{"get":{"summary":"Get usage analytics","operationId":"getChatbotUsageAnalytics","tags":["Analytics"],"description":"Returns usage analytics for a single chatbot. tokenCount and queryCount are derived from cost rollup data (same tier-gating as cost analytics). Note: queryCount counts recorded cost/usage events (model calls — embeddings, completions, reranks, tool calls — plus other cost events), not user Q&A turns, so expect it to run several × the conversations/quality turn counts. documentCount is the current KB size (total docs in user_documents — not time-windowed). No PII. Required scope: `analytics:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"days","in":"query","required":false,"description":"Lookback window in days for tokenCount + queryCount (default: 30, range 1-365). documentCount is always the current total, unaffected by this parameter.","schema":{"type":"integer","minimum":1,"maximum":365}}],"responses":{"200":{"description":"Usage analytics for the chatbot.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicUsageAnalytics"}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/config":{"get":{"summary":"Export aggregate config","operationId":"getChatbotConfig","tags":["Config"],"description":"Assembles the chatbot's CURRENT configuration into a single canonical desired-state document (the \"export current config\" / drift baseline for the declarative apply). Reuses every section serializer; never returns secrets — accessProtection.jwtConfig.secret is collapsed to hasSecret, and the access-protection / external-api key arrays are excluded entirely. Baseline scope: `chatbots:read`. Per-section read-scope gating (least-privilege): a section is OMITTED from `spec` when the caller's key lacks its read scope, and listed in `_omittedSections` (core/scopes→chatbots:read, mcpServers→mcp:read, skills→skills:read, accessProtection/externalApi/accessLists/telemetry→security:read, domains→domains:read). Firebase callers hold all scopes.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Assembled desired-state config document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatbotConfigDocument"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/apply":{"post":{"summary":"Apply a desired-state config document","operationId":"applyChatbotConfig","tags":["Config"],"description":"Declarative config-as-code apply. Diffs a desired-state document against the chatbot's CURRENT config and either returns the PLAN (dry-run) or executes it via the shared reconcile services. `prune` defaults to true: array sections (mcpServers, skills, domains) are AUTHORITATIVE — resources present on the chatbot but absent from the document are DELETED. Every delete is surfaced in the dry-run plan before any apply. prune:false = merge-only (create/update, never delete). Only sections present in `spec` are reconciled; absent sections are left untouched (never pruned). Scope model (least-privilege, per present section): dry-run requires the READ scope, apply the WRITE scope, of every section present, PLUS baseline chatbots:read (dry-run) / chatbots:write (apply). Section→scope: core/scopes→chatbots, mcpServers→mcp, skills→skills, accessProtection/externalApi/accessLists/telemetry→security, domains→domains. Execution stops on the FIRST failing op (apply is idempotent, so a stopped apply is recoverable after fixing the offending section); a partial failure returns HTTP 422 with the ops applied so far plus the offending op.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"dryRun","in":"query","required":false,"description":"When true, compute and return the plan WITHOUT writing. May also be set via the body `dryRun` flag. Dry-run requires only the READ scope of each present section.","schema":{"type":"boolean"}}],"requestBody":{"required":true,"description":"The desired-state document. Unknown top-level keys and unknown spec sections are rejected (400).","content":{"application/json":{"schema":{"type":"object","required":["apiVersion","kind","spec"],"properties":{"apiVersion":{"type":"string","enum":["v1"],"description":"Document API version (always \"v1\")."},"kind":{"type":"string","enum":["ChatbotConfig"],"description":"Document kind (always \"ChatbotConfig\")."},"prune":{"type":"boolean","description":"Authoritative-array prune (default true). false = merge-only (create/update, never delete)."},"dryRun":{"type":"boolean","description":"Compute the plan without writing (alternative to ?dryRun=true)."},"spec":{"type":"object","description":"The config sections to reconcile. Every section is OPTIONAL; only sections present are reconciled. accessProtection.jwtConfig.secret is write-only (accepted here, never echoed back by GET /config).","properties":{"core":{"type":"object","additionalProperties":true},"scopes":{"type":"object","additionalProperties":true},"accessProtection":{"type":"object","additionalProperties":true},"externalApi":{"type":"object","additionalProperties":true},"accessLists":{"$ref":"#/components/schemas/PublicAccessLists"},"telemetry":{"$ref":"#/components/schemas/PublicTelemetry"},"mcpServers":{"type":"array","items":{"type":"object","additionalProperties":true}},"skills":{"type":"array","items":{"type":"object","additionalProperties":true}},"domains":{"type":"array","items":{"type":"object","required":["domain"],"properties":{"domain":{"type":"string"}},"additionalProperties":false}}},"additionalProperties":false}},"additionalProperties":false}}}},"responses":{"200":{"description":"Dry-run: the computed plan ({ dryRun:true, plan }). Apply (all ops succeeded): { dryRun:false, plan, applied, failed:null, config }.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplyResult"}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Partial failure — execution stopped on the first failing op. Body is an ApplyResult with `failed` populated and `applied` listing the ops that succeeded before it. Re-run after fixing the offending section (idempotent).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplyResult"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/team":{"get":{"operationId":"listTeamMembers","summary":"List the owner's team members","tags":["Team"],"description":"Returns the caller's team/workspace + its members (userId, email, displayName, role, status, invite/accept/last-active timestamps). Workspace-level — no chatbotId. Returns the team from the caller's OWN membership (their active team), so a caller can only read a team they belong to (never cross-tenant); a member/admin sees the roster, matching the dashboard. Returns team:null for a solo user. Internal fields (permissionOverrides, invitedBy) are dropped. Required scope: `security:read`.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The owner's team + members (team:null if none).","content":{"application/json":{"schema":{"type":"object","properties":{"team":{"oneOf":[{"type":"object","properties":{"id":{"oneOf":[{"type":"string"},{"type":"null"}]},"name":{"oneOf":[{"type":"string"},{"type":"null"}]},"ownerId":{"oneOf":[{"type":"string"},{"type":"null"}]},"plan":{"oneOf":[{"type":"string"},{"type":"null"}]}}},{"type":"null"}]},"members":{"type":"array","items":{"type":"object","properties":{"userId":{"oneOf":[{"type":"string"},{"type":"null"}]},"email":{"oneOf":[{"type":"string"},{"type":"null"}]},"displayName":{"oneOf":[{"type":"string"},{"type":"null"}]},"role":{"oneOf":[{"type":"string"},{"type":"null"}]},"status":{"oneOf":[{"type":"string"},{"type":"null"}]},"invitedAtSeconds":{"oneOf":[{"type":"integer"},{"type":"null"}]},"acceptedAtSeconds":{"oneOf":[{"type":"integer"},{"type":"null"}]},"lastActiveAtSeconds":{"oneOf":[{"type":"integer"},{"type":"null"}]}}}},"count":{"type":"integer"}}}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/teams":{"get":{"operationId":"listTeams","summary":"List all your teams","tags":["Team"],"description":"Returns every team/workspace you belong to — including teams you created and own — with your role in each (owner, admin, or member) and a flag marking which one is your currently active workspace. Use this to see all your teams at once; the singular `/api/v1/team` endpoint returns only your active team and its members. Returns an empty list for a solo account with no teams. Required scope: `security:read`.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The teams you belong to (empty list if none).","content":{"application/json":{"schema":{"type":"object","properties":{"teams":{"type":"array","items":{"type":"object","properties":{"id":{"oneOf":[{"type":"string"},{"type":"null"}]},"name":{"oneOf":[{"type":"string"},{"type":"null"}]},"ownerId":{"oneOf":[{"type":"string"},{"type":"null"}]},"role":{"oneOf":[{"type":"string","enum":["owner","admin","member"]},{"type":"null"}]},"isActive":{"type":"boolean"}}}},"count":{"type":"integer"}}}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/usage":{"get":{"operationId":"getUsage","summary":"Get daily API quota status","tags":["Usage"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Quota status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageSummary"}}}},"401":{"description":"Invalid or missing API key"},"429":{"description":"Quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/oauth/clients":{"get":{"operationId":"listOAuthClients","summary":"List OAuth clients","description":"List OAuth clients registered by the authenticated user. Requires Firebase authentication.","tags":["OAuth"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Client list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/OAuthClient"}}}}}}},"401":{"description":"Unauthenticated"},"403":{"description":"Not Firebase auth"}}},"post":{"operationId":"createOAuthClient","summary":"Register an OAuth client","tags":["OAuth"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","scopes"],"properties":{"name":{"type":"string"},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/ManagementApiScope"}},"allowedChatbotIds":{"type":["array","null"],"items":{"type":"string"}}}}}}},"responses":{"201":{"description":"Created — secret shown once","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthClientCreated"}}}},"400":{"description":"Validation error"},"403":{"description":"Not Firebase auth"}}}},"/api/v1/oauth/clients/{clientId}":{"get":{"operationId":"getOAuthClient","summary":"Get an OAuth client","tags":["OAuth"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"clientId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Client","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthClient"}}}},"403":{"description":"Not Firebase auth or not owner"},"404":{"description":"Not found"}}},"delete":{"operationId":"deleteOAuthClient","summary":"Revoke an OAuth client","tags":["OAuth"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"clientId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Revoked"},"403":{"description":"Not Firebase auth or not owner"},"404":{"description":"Not found"}}}},"/api/v1/oauth/token":{"post":{"operationId":"createOAuthToken","summary":"Issue an access token (client credentials)","description":"Public endpoint — no bearer auth required. Exchange `client_id` + `client_secret` for a short-lived access token. Supports `client_credentials` grant type only (RFC 6749 §4.4). Accepts both `application/json` (developer convenience) and `application/x-www-form-urlencoded` (RFC 6749 §4.4.2 standard). Returns `400 invalid_request` for any other Content-Type. Rate-limited: returns `429 rate_limited` + `Retry-After` header when the IP bucket is exceeded.","tags":["OAuth"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthTokenRequest"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/OAuthTokenRequest"}}}},"responses":{"200":{"description":"Access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthTokenResponse"}}}},"400":{"description":"Invalid request, unsupported grant type, or unsupported Content-Type","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Invalid client credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — IP rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/email-groups":{"get":{"summary":"List email groups","operationId":"listEmailGroups","tags":["Security"],"description":"Lists the owner's email groups (reusable email allow-lists shared across their chatbots). Pass `includeUsage=true` to include how many documents use each group. Email groups are OWNER-scoped, not per-chatbot: a key restricted to specific chatbots still has full access to all of the owner's email groups. Required scope: `security:read`.","parameters":[{"name":"includeUsage","in":"query","required":false,"description":"Include a usageCount per group.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"The owner's email groups.","content":{"application/json":{"schema":{"type":"object","required":["groups","count"],"additionalProperties":false,"properties":{"groups":{"type":"array","items":{"$ref":"#/components/schemas/PublicEmailGroup"}},"count":{"type":"integer"}}}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create an email group","operationId":"createEmailGroup","tags":["Security"],"description":"Creates an email group (name + member emails). Emails are normalized and de-duplicated. Group names must be unique per owner. Required scope: `security:write`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","emails"],"additionalProperties":false,"properties":{"name":{"type":"string","description":"Group name (unique per owner)."},"emails":{"type":"array","items":{"type":"string"},"description":"Member email addresses."}}}}}},"responses":{"201":{"description":"Group created.","content":{"application/json":{"schema":{"type":"object","required":["group"],"additionalProperties":false,"properties":{"group":{"$ref":"#/components/schemas/PublicEmailGroup"}}}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"A group with this name already exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/email-groups/{groupId}":{"get":{"summary":"Get an email group","operationId":"getEmailGroup","tags":["Security"],"description":"Fetch one of the owner's email groups by id. Required scope: `security:read`.","parameters":[{"name":"groupId","in":"path","required":true,"description":"The email group id.","schema":{"type":"string"}}],"responses":{"200":{"description":"The email group.","content":{"application/json":{"schema":{"type":"object","required":["group"],"additionalProperties":false,"properties":{"group":{"$ref":"#/components/schemas/PublicEmailGroup"}}}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"summary":"Update an email group","operationId":"updateEmailGroup","tags":["Security"],"description":"Update an email group's name and/or member emails (provide at least one). Required scope: `security:write`.","parameters":[{"name":"groupId","in":"path","required":true,"description":"The email group id.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","description":"New group name (unique per owner)."},"emails":{"type":"array","items":{"type":"string"},"description":"Replacement member email addresses."}}}}}},"responses":{"200":{"description":"Group updated.","content":{"application/json":{"schema":{"type":"object","required":["group"],"additionalProperties":false,"properties":{"group":{"$ref":"#/components/schemas/PublicEmailGroup"}}}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"A group with this name already exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete an email group","operationId":"deleteEmailGroup","tags":["Security"],"description":"Delete an email group. Required scope: `security:write`.","parameters":[{"name":"groupId","in":"path","required":true,"description":"The email group id.","schema":{"type":"string"}}],"responses":{"200":{"description":"Group deleted.","content":{"application/json":{"schema":{"type":"object","required":["success"],"additionalProperties":false,"properties":{"success":{"type":"boolean"}}}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}":{"get":{"summary":"Get a chatbot","operationId":"getChatbot","tags":["Chatbots"],"description":"Returns a single chatbot by ID. Required scope: `chatbots:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Chatbot found.","content":{"application/json":{"schema":{"type":"object","required":["chatbot"],"properties":{"chatbot":{"$ref":"#/components/schemas/PublicChatbot"}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a chatbot","operationId":"patchChatbot","tags":["Chatbots"],"description":"Partial update of chatbot configuration fields. Only top-level scalar/object fields accepted; nested arrays are replaced wholesale (not merged). Required scope: `chatbots:write`. The response `chatbot` field may be `null` if the document was deleted concurrently during the update (write-then-read race).","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Fields to update. Unknown or protected fields are ignored.","content":{"application/json":{"schema":{"type":"object","description":"Partial chatbot config — any subset of PublicChatbot writable fields.","properties":{"name":{"type":"string"},"description":{"type":"string"},"behavior":{"type":"object","additionalProperties":true},"appearance":{"type":"object","additionalProperties":true},"aiConfig":{"type":"object","additionalProperties":true}},"additionalProperties":true}}}},"responses":{"200":{"description":"Update applied. `chatbot` is `null` only on a concurrent-delete race.","content":{"application/json":{"schema":{"type":"object","required":["chatbot"],"properties":{"chatbot":{"oneOf":[{"$ref":"#/components/schemas/PublicChatbot"},{"type":"null"}],"description":"Updated chatbot, or null if concurrently deleted."}},"additionalProperties":false}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a chatbot","operationId":"deleteChatbot","tags":["Chatbots"],"description":"Permanently deletes a chatbot and cascades its cleanup (knowledge base, storage, deployment, and configuration). Requires the chatbot OWNER or a team owner/admin (stricter than edit access). Returns 200 on full deletion, or 207 Multi-Status if the chatbot was removed but some downstream cleanup failed (see `errors`). Returns 409 if a shared resource (e.g. a knowledge base still used by another chatbot) blocks deletion — nothing is deleted in that case. Required scope: `chatbots:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Chatbot fully deleted.","content":{"application/json":{"schema":{"type":"object","required":["success","chatbotId","message","details","errors"],"properties":{"success":{"type":"boolean","description":"True on full deletion."},"chatbotId":{"type":"string"},"message":{"type":"string"},"details":{"oneOf":[{"type":"object","additionalProperties":true},{"type":"null"}]},"errors":{"type":"array","items":{"type":"string"},"description":"Cleanup error messages; empty on full success."}},"additionalProperties":false}}}},"207":{"description":"Chatbot removed, but some downstream cleanup failed — see `errors`.","content":{"application/json":{"schema":{"type":"object","required":["success","chatbotId","message","details","errors"],"properties":{"success":{"type":"boolean","description":"False — partial cleanup."},"chatbotId":{"type":"string"},"message":{"type":"string"},"details":{"oneOf":[{"type":"object","additionalProperties":true},{"type":"null"}]},"errors":{"type":"array","items":{"type":"string"},"description":"Per-step cleanup error messages."}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict — a shared resource blocks deletion; nothing was deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Deletion failed (nothing deleted, or a partial cascade before an error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/videos/{videoId}/corrections":{"get":{"summary":"List a video's transcript corrections","operationId":"listVideoCorrections","tags":["Videos"],"description":"Returns a page of transcript corrections for a single video (IDOR-guarded), each with a short context snippet from the surrounding transcript so a caller can judge the most probable word. Never returns the full transcript body or the resolver identity. Required scope: `documents:read`.","parameters":[{"name":"videoId","in":"path","required":true,"description":"The video ID.","schema":{"type":"string"}},{"name":"offset","in":"query","required":false,"description":"Zero-based index of the first correction to return (default: 0).","schema":{"type":"integer","minimum":0}},{"name":"limit","in":"query","required":false,"description":"Maximum corrections to return (default: 20, max: 50).","schema":{"type":"integer","minimum":1,"maximum":50}}],"responses":{"200":{"description":"A page of corrections for the video.","content":{"application/json":{"schema":{"type":"object","required":["videoId","corrections","correctionTotal","hasMoreCorrections","correctionPage"],"properties":{"videoId":{"type":"string"},"corrections":{"type":"array","items":{"$ref":"#/components/schemas/PublicCorrection"}},"correctionTotal":{"type":"integer","description":"Total corrections on the video."},"hasMoreCorrections":{"type":"boolean"},"correctionPage":{"type":"object","properties":{"offset":{"type":"integer"},"limit":{"type":"integer"},"returned":{"type":"integer"}}},"correctionSummary":{"oneOf":[{"type":"object","additionalProperties":true},{"type":"null"}],"description":"Per-status counts (suggested/needs-review/accepted/rejected/custom), or null."},"correctionReembedStatus":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"Status of the background re-embed triggered by a resolve, or null."}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Resolve a transcript correction","operationId":"resolveVideoCorrection","tags":["Videos"],"description":"Resolves a single correction on a video (IDOR-guarded): accept a suggested/candidate word, reject it, apply custom text, or undo a previous resolution. Accepting or applying custom text rebuilds the transcript and re-embeds it in the background. Required scope: `documents:write`.","parameters":[{"name":"videoId","in":"path","required":true,"description":"The video ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Which correction to resolve, and how. Identify it by `correctionId` or `correctionIndex`.","content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["accept","reject","custom","undo"],"description":"The resolution to apply."},"correctionId":{"type":"string","description":"The correction id (alternative to correctionIndex)."},"correctionIndex":{"type":"integer","description":"The correction's position (alternative to correctionId)."},"pickedCandidate":{"type":"string","description":"For action=\"accept\": the candidate text to apply."},"customText":{"type":"string","description":"For action=\"custom\": the replacement text."}},"additionalProperties":false}}}},"responses":{"200":{"description":"Correction resolved.","content":{"application/json":{"schema":{"type":"object","required":["success","correction"],"properties":{"success":{"type":"boolean"},"correction":{"oneOf":[{"$ref":"#/components/schemas/PublicCorrection"},{"type":"null"}]},"correctionSummary":{"oneOf":[{"type":"object","additionalProperties":true},{"type":"null"}]},"transcriptChanged":{"type":"boolean","description":"Whether the resolution changed the transcript."},"reembedDispatched":{"type":"boolean","description":"Whether a background re-embed was dispatched."}},"additionalProperties":false}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/corrections":{"get":{"summary":"List a chatbot's transcript-correction review queue","operationId":"listChatbotCorrections","tags":["Videos"],"description":"Returns every transcript correction across the chatbot's videos as one needs-review-first queue, with per-status totals. Never returns the transcript body or the resolver identity. Required scope: `documents:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"status","in":"query","required":false,"description":"Filter to a single correction status.","schema":{"type":"string","enum":["suggested_auto","needs_review","accepted","rejected","custom"]}},{"name":"limit","in":"query","required":false,"description":"Maximum corrections to return (default: 500).","schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"The chatbot's correction review queue with per-status totals.","content":{"application/json":{"schema":{"type":"object","required":["chatbotId","totals","corrections","truncated"],"properties":{"chatbotId":{"type":"string"},"totals":{"type":"object","properties":{"suggestedAuto":{"type":"integer"},"needsReview":{"type":"integer"},"accepted":{"type":"integer"},"rejected":{"type":"integer"},"custom":{"type":"integer"},"all":{"type":"integer"}}},"corrections":{"type":"array","items":{"$ref":"#/components/schemas/PublicCorrection"}},"truncated":{"type":"boolean","description":"True when more corrections exist than the limit returned."}},"additionalProperties":false}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/sales-agent":{"get":{"summary":"Get the Sales Agent configuration","operationId":"getSalesAgent","tags":["Sales Agent"],"description":"Returns the chatbot's Sales Agent configuration (persona, prompt slots/overlay, model override, lead-capture fields, guardrails, and sales-trigger settings), plus `salesAgentEnabled` — whether the agent is live at runtime. Returns an empty config with `salesAgentEnabled:false` when no agent has been configured yet. Never returns access-key material or internal fields. Required scope: `chatbots:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"The Sales Agent configuration.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicSalesAgent"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update the Sales Agent configuration","operationId":"updateSalesAgent","tags":["Sales Agent"],"description":"Upserts the Sales Agent configuration. The agent doc is created on the first update (`created:true`). Only known config fields are applied; unknown fields are ignored. Configuring the agent does not enable it at runtime — enablement is a separate control. Required scope: `chatbots:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"A partial Sales Agent configuration. Any subset of the writable fields (enabled, promptOverlay, promptSlots, modelOverride, fieldsToCapture, guardrails, sales-trigger settings, etc.). Unknown fields are silently dropped.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"enabled":{"type":"boolean"},"promptOverlay":{"type":"string"},"modelOverride":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"A model override in `model@provider` form, or null to clear it."},"fieldsToCapture":{"type":"array","items":{"type":"object","additionalProperties":true}},"guardrailEnabled":{"type":"boolean"},"triggerSensitivity":{"type":"string"}}}}}},"responses":{"200":{"description":"Configuration applied.","content":{"application/json":{"schema":{"type":"object","properties":{"promptVersionId":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"The prompt version id after the write."},"created":{"type":"boolean","description":"True when this write created the agent config."},"promptVersionBumped":{"type":"boolean","description":"True when this write rotated the prompt version."}},"additionalProperties":false}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/sales-agent/handoffs":{"get":{"summary":"List lead-delivery channels","operationId":"listSalesAgentHandoffs","tags":["Sales Agent"],"description":"Lists the Sales Agent's lead-delivery (hand-off) channels. Each channel reports its recipients/routing and whether a sending secret is configured (`hasSecret`/`hasWebhookHmac`) without ever exposing the secret itself. Creating a channel is done from the dashboard (it is inseparable from storing the sending secret). Required scope: `chatbots:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"The configured lead-delivery channels.","content":{"application/json":{"schema":{"type":"object","required":["handoffs"],"properties":{"handoffs":{"type":"array","items":{"$ref":"#/components/schemas/PublicHandoff"}}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/sales-agent/handoffs/{handoffId}":{"patch":{"summary":"Update a lead-delivery channel","operationId":"updateSalesAgentHandoff","tags":["Sales Agent"],"description":"Updates an existing lead-delivery channel (recipients, routing mode, verified sender, webhook URL, included fields, enable/disable). The sending secret cannot be set here — it is managed from the dashboard, and enabling a channel requires its secret to already exist. Required scope: `chatbots:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"handoffId","in":"path","required":true,"description":"The lead-delivery channel ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"The channel fields to update.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"enabled":{"type":"boolean"},"routingMode":{"type":"string","enum":["single","distribution","country","scope"]},"routingConfig":{"type":"object","additionalProperties":true},"verifiedSender":{"type":"string"},"webhookUrl":{"type":"string"},"fieldsToInclude":{"type":"array","items":{"type":"string"}},"templateVersionId":{"type":"string"}}}}}},"responses":{"200":{"description":"Channel updated.","content":{"application/json":{"schema":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean"},"validated":{"type":"boolean","description":"Whether the channel's configuration was validated on this write."}},"additionalProperties":false}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation failed — see `error.details.errors` for per-field messages.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a lead-delivery channel","operationId":"deleteSalesAgentHandoff","tags":["Sales Agent"],"description":"Removes a lead-delivery channel. Idempotent — deleting an unknown channel still returns success. Required scope: `chatbots:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}},{"name":"handoffId","in":"path","required":true,"description":"The lead-delivery channel ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Channel deleted (or already absent).","content":{"application/json":{"schema":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean"}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/youtube/connect":{"post":{"summary":"Start the YouTube connect flow","operationId":"connectYouTube","tags":["YouTube"],"description":"Begins connecting the caller's YouTube account. Returns an `authorizeUrl` the caller opens in a browser to grant access; the connection is completed by the browser callback. Account-scoped (no chatbot). Required scope: `videos:write`.","responses":{"200":{"description":"The URL to open to authorize the YouTube connection.","content":{"application/json":{"schema":{"type":"object","required":["authorizeUrl"],"properties":{"authorizeUrl":{"type":"string","description":"Open this URL in a browser to authorize."}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/youtube/status":{"get":{"summary":"Get YouTube connection status","operationId":"getYouTubeStatus","tags":["YouTube"],"description":"Reports whether the caller's YouTube account is connected, and the connected channel title when available. Never returns tokens. Account-scoped (no chatbot). Required scope: `videos:read`.","responses":{"200":{"description":"The caller's YouTube connection status.","content":{"application/json":{"schema":{"type":"object","required":["connected"],"properties":{"connected":{"type":"boolean"},"channelTitle":{"type":"string","description":"The connected channel title, when connected."}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/youtube/videos":{"get":{"summary":"List the caller's YouTube videos","operationId":"listYouTubeVideos","tags":["YouTube"],"description":"Lists videos from the caller's own connected YouTube channel. Without `search`, returns one page plus a `nextPageToken`; with `search`, scans the whole channel by title. Returns `connected:false` as data (not an error) when no channel is connected. Exposes only public video metadata — never tokens. Account-scoped (no chatbot). Required scope: `videos:read`.","parameters":[{"name":"search","in":"query","required":false,"description":"Case-insensitive title scan across the whole channel. Mutually exclusive with pageToken.","schema":{"type":"string"}},{"name":"pageToken","in":"query","required":false,"description":"Page cursor from a previous response's nextPageToken. Mutually exclusive with search.","schema":{"type":"string"}},{"name":"maxResults","in":"query","required":false,"description":"Page size for a browse (default: 50, range 1-50). Ignored when search is used.","schema":{"type":"integer","minimum":1,"maximum":50}}],"responses":{"200":{"description":"A page of the caller's channel videos (or connected:false when not connected).","content":{"application/json":{"schema":{"type":"object","required":["connected","videos"],"properties":{"connected":{"type":"boolean"},"videos":{"type":"array","items":{"$ref":"#/components/schemas/YouTubeVideo"}},"nextPageToken":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"Cursor for the next page of a browse, or null on the last page."},"search":{"type":"string","description":"Echo of the search term (search mode only)."},"totalScanned":{"type":"integer","description":"Videos scanned (search mode only)."},"totalResults":{"oneOf":[{"type":"integer"},{"type":"null"}],"description":"Approximate total (an estimate that can drift for very large channels)."},"truncated":{"type":"boolean","description":"True when the search scan hit its cap (search mode only)."},"message":{"type":"string","description":"Advisory message, e.g. no channel found for this account."}},"additionalProperties":false}}}},"401":{"description":"Unauthorized, or the YouTube connection needs to be re-established (reconnect required).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or YouTube API quota exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/youtube/videos":{"post":{"summary":"Ingest YouTube videos into a chatbot","operationId":"ingestYouTubeVideos","tags":["YouTube"],"description":"Ingests videos from the caller's connected YouTube channel into the chatbot's knowledge base. Videos already in the knowledge base are skipped (`skippedAlreadyImported`). Returns a `jobId` for the ingest. Required scope: `videos:write`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"The videos to ingest and their visibility.","content":{"application/json":{"schema":{"type":"object","description":"Provide at least one video via videoIds and/or links.","properties":{"videoIds":{"type":"array","items":{"type":"string"},"description":"YouTube video IDs to ingest. Provide videoIds and/or links."},"links":{"type":"array","items":{"type":"string"},"description":"YouTube video links (watch, youtu.be, embed, shorts URLs) or bare 11-character IDs, parsed to video IDs. Useful for adding a video that is not shown in the channel list. Provide videoIds and/or links."},"scopeId":{"type":"string","description":"Optional knowledge scope to attach the videos to."},"isPublic":{"type":"boolean","description":"Whether the ingested videos are public. Defaults to false."},"channel":{"type":"string","description":"The linked YouTube channel to import from, identified by the value returned by the list-channels endpoint. Optional when only one channel is linked; required when several are."}},"additionalProperties":false}}}},"responses":{"200":{"description":"Ingest accepted.","content":{"application/json":{"schema":{"type":"object","required":["jobId"],"properties":{"jobId":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"The ingest job id."},"skippedAlreadyImported":{"type":"array","items":{"type":"string"},"description":"Video IDs skipped because they are already in the knowledge base."},"invalidLinks":{"type":"array","items":{"type":"string"},"description":"Input links that could not be parsed to a YouTube video ID (ignored)."},"message":{"type":"string","description":"Advisory message about the ingest."}},"additionalProperties":false}}}},"400":{"description":"Bad Request — invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized, or the YouTube connection needs to be re-established (reconnect required).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chatbots/{chatbotId}/youtube/channels":{"get":{"summary":"List linked YouTube channels","operationId":"listLinkedYouTubeChannels","tags":["YouTube"],"description":"Returns the YouTube channels linked to the chatbot. Each item includes a `channel` selector value and the channel title. Pass the `channel` value to the ingest endpoint to specify which channel to import from. Required scope: `videos:read`.","parameters":[{"name":"chatbotId","in":"path","required":true,"description":"The chatbot ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Linked channels retrieved.","content":{"application/json":{"schema":{"type":"object","required":["channels"],"properties":{"channels":{"type":"array","items":{"type":"object","required":["channel","title"],"properties":{"channel":{"type":"string","description":"Opaque selector value to pass to the ingest endpoint."},"title":{"type":"string","description":"Display name of the linked channel."}},"additionalProperties":false},"description":"The channels linked to this chatbot."}},"additionalProperties":false}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden — key lacks required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too Many Requests — rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"wpk_live_…","description":"A `wpk_live_…` Management API key issued via the WizChat dashboard, or a Firebase ID token for owner-authenticated requests."}},"schemas":{"ScheduledCrawl":{"type":"object","description":"A scheduled (recurring) website crawl as returned by the management API. Internal storage and ownership identifiers are stripped, and PII rosters are collapsed to counts.","properties":{"id":{"oneOf":[{"type":"string"},{"type":"null"}]},"chatbotId":{"oneOf":[{"type":"string"},{"type":"null"}]},"name":{"oneOf":[{"type":"string"},{"type":"null"}]},"domain":{"oneOf":[{"type":"string"},{"type":"null"}]},"urls":{"type":"array","items":{"type":"string"}},"urlCount":{"type":"integer"},"schedule":{"oneOf":[{"type":"string","enum":["hourly","daily","weekly","monthly"]},{"type":"null"}]},"dayOfWeek":{"oneOf":[{"type":"integer"},{"type":"null"}]},"dayOfMonth":{"oneOf":[{"type":"integer"},{"type":"null"}]},"preferredHour":{"oneOf":[{"type":"integer"},{"type":"null"}]},"timezone":{"oneOf":[{"type":"string"},{"type":"null"}]},"customInstructions":{"oneOf":[{"type":"string"},{"type":"null"}]},"includeTags":{"oneOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"maxPages":{"oneOf":[{"type":"integer"},{"type":"null"}]},"viewAccessLevel":{"oneOf":[{"type":"string","enum":["public","private","authorized"]},{"type":"null"}]},"authorizedEmailCount":{"type":"integer","description":"Count only — the email roster is never returned."},"authorizedGroupCount":{"type":"integer","description":"Count only — the group roster is never returned."},"enabled":{"oneOf":[{"type":"boolean"},{"type":"null"}]},"lastRunStatus":{"oneOf":[{"type":"string","enum":["success","partial","failed"]},{"type":"null"}]},"lastRunError":{"oneOf":[{"type":"string"},{"type":"null"}]},"lastRunAtSeconds":{"oneOf":[{"type":"integer"},{"type":"null"}]},"nextRunAtSeconds":{"oneOf":[{"type":"integer"},{"type":"null"}]},"runCount":{"type":"integer"},"createdAtSeconds":{"oneOf":[{"type":"integer"},{"type":"null"}]},"updatedAtSeconds":{"oneOf":[{"type":"integer"},{"type":"null"}]},"recentRuns":{"type":"array","description":"Recent run history (present only on the single-schedule GET).","items":{"type":"object","properties":{"id":{"oneOf":[{"type":"string"},{"type":"null"}]},"status":{"oneOf":[{"type":"string","enum":["queued","processing","completed","failed"]},{"type":"null"}]},"triggeredBy":{"oneOf":[{"type":"string","enum":["schedule","manual"]},{"type":"null"}]},"jobId":{"oneOf":[{"type":"string"},{"type":"null"}]},"queuedAtSeconds":{"oneOf":[{"type":"integer"},{"type":"null"}]},"startedAtSeconds":{"oneOf":[{"type":"integer"},{"type":"null"}]},"completedAtSeconds":{"oneOf":[{"type":"integer"},{"type":"null"}]},"stats":{"oneOf":[{"type":"object","additionalProperties":true},{"type":"null"}]},"errors":{"oneOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]}}}}}},"PublicDocument":{"type":"object","description":"A document as returned by the management API. Storage paths, userId, and PII rosters are stripped or collapsed to counts.","properties":{"document_id":{"type":"string","description":"Unique document identifier (used for embedding vectors)."},"id":{"type":"string","description":"The document ID."},"chatbotId":{"type":"string","description":"ID of the chatbot this document belongs to."},"displayName":{"type":"string","description":"User-visible document name."},"originalFileName":{"type":"string","description":"Original uploaded filename."},"documentType":{"type":"string","description":"Document type (e.g., pdf, markdown, html)."},"status":{"type":"string","enum":["processing","completed","failed"],"description":"Processing status."},"uploadedAt":{"type":"string","description":"ISO-8601 upload timestamp."},"fileSize":{"type":"integer","description":"File size in bytes."},"vectorCount":{"type":"integer","description":"Number of embedding vectors generated from this document."},"viewAccessLevel":{"type":"string","enum":["owner-only","authenticated-users","anyone-with-link","permanent-public"],"description":"Access level for the document: owner-only = private (only the owner); authenticated-users = signed-in chatbot users; anyone-with-link = shareable link; permanent-public = public download token."},"scopeId":{"type":"string","description":"Knowledge scope this document is assigned to."},"authorizedEmailCount":{"type":"integer","description":"Number of individually authorised email addresses."},"authorizedGroupCount":{"type":"integer","description":"Number of authorised groups."}},"additionalProperties":false},"PublicVideo":{"type":"object","description":"A video as returned by the management API. Storage paths, userId, transcription content, and PII rosters are stripped or collapsed to counts.","properties":{"document_id":{"type":"string","description":"Unique document identifier (used for embedding vectors)."},"id":{"type":"string","description":"The document ID."},"chatbotId":{"type":"string","description":"ID of the chatbot this video belongs to."},"displayName":{"type":"string","description":"User-visible display name."},"originalFileName":{"type":"string","description":"Original uploaded filename."},"videoFileName":{"type":"string","description":"Stored video filename."},"platform":{"type":"string","enum":["youtube","upload"],"description":"Video source platform."},"source":{"type":"string","enum":["oauth","url_paste","upload"],"description":"How the video was added."},"status":{"type":"string","enum":["processing","completed","failed"],"description":"Processing status."},"duration":{"type":"number","description":"Video duration in seconds."},"language":{"type":"string","description":"Detected language code."},"vectorCount":{"type":"integer","description":"Number of embedding vectors generated from this video."},"uploadedAt":{"type":"string","description":"ISO-8601 upload timestamp."},"videoTitle":{"type":"string","description":"Clean video title (no .youtube suffix)."},"thumbnail":{"type":"string","description":"Thumbnail URL (YouTube videos)."},"scopeId":{"type":"string","description":"Knowledge scope this video is assigned to."},"viewAccessLevel":{"type":"string","enum":["owner-only","authenticated-users","anyone-with-link","permanent-public"],"description":"Access level for the video: owner-only = private (only the owner); authenticated-users = signed-in chatbot users; anyone-with-link = shareable link; permanent-public = public download token."},"authorizedEmailCount":{"type":"integer","description":"Number of individually authorised email addresses."},"authorizedGroupCount":{"type":"integer","description":"Number of authorised groups."}},"additionalProperties":false},"PublicScope":{"type":"object","description":"A knowledge scope as returned by the management API. Billing details (costLimit) and PII rosters (emails/domains) are stripped or collapsed to counts.","properties":{"id":{"type":"string","description":"Namespace slug (e.g., \"sw-2025\")."},"label":{"type":"string","description":"Admin display name (e.g., \"SolidWorks 2025\")."},"description":{"type":"string","description":"Optional scope description."},"requireAuth":{"type":"boolean","description":"Whether users must authenticate to access this scope."},"systemPrompt":{"type":"string","description":"Per-scope system prompt override."},"persona":{"type":"string","description":"Per-scope persona override."},"responseLength":{"type":"string","description":"Per-scope response length preference."},"firstMessage":{"type":"string","description":"Per-scope first message shown to users."},"webSearchEnabled":{"type":"boolean","description":"Per-scope web search override."},"inlineCitations":{"type":"boolean","description":"Per-scope inline citations override."},"sourceQuality":{"type":"string","enum":["normal","medium","high"],"description":"Per-scope source quality level."},"trackUnanswered":{"type":"boolean","description":"Per-scope unanswered question tracking."},"mcpServerIds":{"type":"array","items":{"type":"string"},"description":"MCP server IDs active for this scope."},"authorizedEmailCount":{"type":"integer","description":"Number of individually authorised email addresses."},"authorizedDomainCount":{"type":"integer","description":"Number of authorised email domains."}},"additionalProperties":false},"ScopeCreateInput":{"type":"object","description":"Writable fields for creating a knowledge scope. `label` is required; `id` optional (derived from label if absent). Unknown fields are rejected. `authorizedEmails`/`authorizedDomains`/`costLimit` are accepted here (write) even though the public read serializer strips/collapses them.","required":["label"],"properties":{"id":{"type":"string","description":"Optional slug (else derived from label). Must be a non-empty string when provided."},"label":{"type":"string","description":"Admin display name. Required; non-empty."},"description":{"type":"string","description":"Optional scope description."},"requireAuth":{"type":"boolean","description":"Whether users must authenticate to access this scope."},"authorizedEmails":{"type":"array","items":{"type":"string"},"description":"Individually authorised email addresses."},"authorizedDomains":{"type":"array","items":{"type":"string"},"description":"Authorised email domains."},"costLimit":{"type":"object","description":"Per-scope spend cap (atomic replace — never deep-merged).","properties":{"amount":{"type":"number","description":"Spend cap amount."},"period":{"type":"string","enum":["monthly","weekly"],"description":"Reset period."}},"required":["amount","period"],"additionalProperties":false},"mcpServerIds":{"type":"array","items":{"type":"string"},"description":"MCP server IDs active for this scope."},"systemPrompt":{"type":"string","description":"Per-scope system prompt override."},"persona":{"type":"string","description":"Per-scope persona override."},"responseLength":{"type":"string","description":"Per-scope response length preference."},"firstMessage":{"type":"string","description":"Per-scope first message shown to users."},"webSearchEnabled":{"type":"boolean","description":"Per-scope web search override."},"inlineCitations":{"type":"boolean","description":"Per-scope inline citations override."},"sourceQuality":{"type":"string","enum":["normal","medium","high"],"description":"Per-scope source quality level."},"trackUnanswered":{"type":"boolean","description":"Per-scope unanswered question tracking."}},"additionalProperties":false},"ScopeUpdateInput":{"type":"object","description":"Writable fields for updating a knowledge scope. All optional. Clearable optional fields accept `null` to delete them. `label` cannot be cleared; `id` is immutable. Unknown fields are rejected.","properties":{"id":{"type":"string","description":"Immutable — ignored on update (a non-string id is rejected)."},"label":{"type":"string","description":"Admin display name. Non-empty when present; cannot be cleared."},"description":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"Optional scope description. `null` clears it."},"requireAuth":{"oneOf":[{"type":"boolean"},{"type":"null"}],"description":"Auth requirement. `null` clears it."},"authorizedEmails":{"oneOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Authorised emails. `[]` clears the list; `null` deletes the key."},"authorizedDomains":{"oneOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Authorised domains. `[]` clears the list; `null` deletes the key."},"costLimit":{"oneOf":[{"type":"object","properties":{"amount":{"type":"number","description":"Spend cap amount."},"period":{"type":"string","enum":["monthly","weekly"],"description":"Reset period."}},"required":["amount","period"],"additionalProperties":false},{"type":"null"}],"description":"Per-scope spend cap (atomic replace — never deep-merged). `null` clears it."},"mcpServerIds":{"oneOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"MCP server IDs. `[]` clears the list; `null` deletes the key."},"systemPrompt":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"Per-scope system prompt override. `null` clears it."},"persona":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"Per-scope persona override. `null` clears it."},"responseLength":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"Per-scope response length preference. `null` clears it."},"firstMessage":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"Per-scope first message. `null` clears it."},"webSearchEnabled":{"oneOf":[{"type":"boolean"},{"type":"null"}],"description":"Per-scope web search override. `null` clears it."},"inlineCitations":{"oneOf":[{"type":"boolean"},{"type":"null"}],"description":"Per-scope inline citations override. `null` clears it."},"sourceQuality":{"oneOf":[{"type":"string","enum":["normal","medium","high"]},{"type":"null"}],"description":"Per-scope source quality level. `null` clears it."},"trackUnanswered":{"oneOf":[{"type":"boolean"},{"type":"null"}],"description":"Per-scope unanswered question tracking. `null` clears it."}},"additionalProperties":false},"PublicChatbot":{"type":"object","description":"A chatbot as returned by the management API. Sensitive fields (email rosters, key hashes, connection strings) are redacted or collapsed to counts.","required":["id","name"],"properties":{"id":{"type":"string","description":"The document ID.","example":"abc123xyz"},"name":{"type":"string","description":"Display name of the chatbot.","example":"My Support Bot"},"description":{"type":"string","description":"Short description shown in the dashboard."},"status":{"type":"string","enum":["draft","preview","active","paused","archived","deploying","deployed","pending","failed","inactive","error"],"description":"Chatbot lifecycle status."},"dataRegion":{"type":"string","enum":["EU","US"],"description":"Data-residency region locked at creation."},"logoUrl":{"type":"string","description":"Public URL of the chatbot logo."},"createdAt":{"type":"string","description":"ISO-8601 creation timestamp."},"updatedAt":{"type":"string","description":"ISO-8601 last-modified timestamp."},"aiConfig":{"type":"object","description":"AI model and inference settings.","additionalProperties":true},"behavior":{"type":"object","description":"Prompt, tone, and conversation-flow settings.","additionalProperties":true},"appearance":{"type":"object","description":"UI theming (colours, fonts, widget position).","additionalProperties":true},"embedConfig":{"type":"object","description":"Allowed embed domains and iframe settings.","additionalProperties":true},"requireAuth":{"type":"boolean","description":"When true, unauthenticated users cannot access this chatbot. Top-level field (not nested under authConfig)."},"authConfig":{"type":"object","description":"Authentication settings. PII rosters (emails, domains, users) are collapsed to counts for privacy.","properties":{"authorizedEmailCount":{"type":"integer","description":"Number of individually authorised email addresses."},"authorizedDomainCount":{"type":"integer","description":"Number of authorised email domains."},"invitedUserCount":{"type":"integer","description":"Number of invited users."},"ssoConfig":{"type":"object","description":"SSO settings. `clientSecret` is never returned; `hasClientSecret` indicates presence.","properties":{"provider":{"type":"string"},"hasClientSecret":{"type":"boolean"}},"additionalProperties":true}},"additionalProperties":true},"externalApiConfig":{"type":"object","description":"External API access configuration. Key hashes are stripped; `keys[].keyPrefix` is safe.","properties":{"enabled":{"type":"boolean"},"rateLimitPerMinute":{"type":"integer","minimum":1,"maximum":1000},"keys":{"type":"array","description":"Redacted key list — hashes removed, keyPrefix/status/usage retained.","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"keyPrefix":{"type":"string","example":"wpk_live_abc1"},"status":{"type":"string","enum":["active","revoked"]}},"additionalProperties":true}}},"additionalProperties":true},"accessProtection":{"type":"object","description":"Widget access-protection settings. Key hashes stripped.","properties":{"enabled":{"type":"boolean"},"keys":{"type":"array","items":{"type":"object","additionalProperties":true}},"jwtConfig":{"type":"object","properties":{"enabled":{"type":"boolean"},"emailClaim":{"type":"string"},"hasSecret":{"type":"boolean","description":"True if a JWT signing secret is configured."}},"additionalProperties":true}},"additionalProperties":true},"byokConfig":{"type":"object","description":"Bring-Your-Own-Keys summary — provider keys are never returned.","properties":{"enabled":{"type":"boolean"},"hasProviders":{"type":"boolean","description":"True when at least one provider key is configured."}},"additionalProperties":false},"byoiConfig":{"type":"object","description":"Bring-Your-Own-Inference summary.","properties":{"enabled":{"type":"boolean"}},"additionalProperties":true},"salesAgentEnabled":{"type":"boolean","description":"Whether the autonomous sales agent is enabled for this chatbot."},"deployment":{"oneOf":[{"type":"object","description":"Current deployment state.","properties":{"status":{"type":"string","enum":["pending","deploying","deployed","failed"]},"deploymentUrl":{"oneOf":[{"type":"string"},{"type":"null"}]},"deployedAt":{"oneOf":[{"type":"string","format":"date-time"},{"type":"null"}]}},"additionalProperties":true},{"type":"null"}],"description":"Current deployment state. null when no deployment has been triggered."}},"additionalProperties":true},"PublicDeployment":{"type":"object","description":"Deployment status as returned by the management API. Internal IDs and auth tokens are stripped.","properties":{"status":{"type":"string","enum":["pending","deploying","deployed","failed"],"description":"Current deployment lifecycle status."},"deploymentUrl":{"type":"string","description":"Production URL of the deployed chatbot."},"customDomain":{"type":"string","description":"Custom domain configured for this chatbot (if any)."},"wizchatDomain":{"type":"string","description":"Auto-attached <chatbot>.wizchat.app subdomain."},"wizchatDomainConfigured":{"type":"boolean","description":"True once the wizchat.app domain attach has been verified."},"customDomainAuthorized":{"type":"boolean","description":"True once the custom domain has been authorized."},"domainVerified":{"type":"boolean","description":"True once DNS verification succeeded."},"domainStatus":{"type":"string","description":"Domain verification status (e.g., active, pending)."},"deployedAt":{"type":"string","description":"ISO-8601 timestamp of the initial deployment."},"lastDeploymentAt":{"type":"string","description":"ISO-8601 timestamp of the most recent deployment."},"failureReason":{"type":"string","description":"Human-readable reason for the failure (only present when status is \"failed\")."},"elapsedMs":{"type":"integer","description":"Milliseconds since the deployment started (only present when status is \"deploying\")."},"message":{"type":"string","description":"Informational message (only present when status is \"pending\")."}},"additionalProperties":false},"PublicDomainStatus":{"type":"object","description":"Domain status as returned by the management API. Internal IDs and auth tokens are stripped.","properties":{"domain":{"type":"string","description":"The domain name."},"verified":{"type":"boolean","description":"True when DNS verification has succeeded."},"configured":{"type":"boolean","description":"True when the domain is fully configured."},"status":{"type":"string","description":"Domain status (e.g., authorized, pending_manual, removed)."},"customDomain":{"type":"string","description":"The custom domain string."},"customDomainAuthorized":{"type":"boolean","description":"True once the domain is authorized."},"authorizedDomains":{"type":"array","items":{"type":"string"},"description":"Full list of authorized login domains for this chatbot."},"warning":{"type":"string","description":"Present on soft-failure responses (e.g. removal_failed) when the server could not confirm the actual post-operation state. Absent on success paths."},"verification":{"description":"DNS records or manual instructions the caller needs to complete setup. Each record has type, domain, and value fields. Internal challenge IDs are stripped.","oneOf":[{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","description":"Record type (e.g. CNAME, TXT, manual)."},"domain":{"type":"string","description":"Domain to configure."},"value":{"type":"string","description":"Value to set."}},"additionalProperties":false}},{"type":"object","properties":{"type":{"type":"string"},"domain":{"type":"string"},"value":{"type":"string"}},"additionalProperties":false}]}},"additionalProperties":false},"PublicMcpServer":{"type":"object","description":"An MCP server as returned by the management API. Credentials, headers, env vars, and access-key hashes are stripped. Email/group rosters are collapsed to counts. OAuth flows, PostgreSQL scan, and metrics are dashboard-only.","properties":{"id":{"type":"string","description":"Server ID (mcp_…)."},"name":{"type":"string","description":"Display name."},"description":{"type":"string","description":"Server description."},"type":{"type":"string","description":"Server type (e.g., atlassian, postgresql, generic)."},"enabled":{"type":"boolean","description":"Whether this server is active."},"transport":{"type":"string","description":"Transport protocol (http, stdio, native)."},"url":{"type":"string","description":"Server URL (HTTP transport)."},"httpProtocol":{"type":"string","description":"HTTP MCP protocol variant."},"credentialSource":{"type":"string","description":"Credential source (wizchat/user/oauth)."},"executionMode":{"type":"string","description":"Tool call execution mode."},"modelOverride":{"type":"string","description":"Per-server gateway model override."},"reasoningEffort":{"type":"string","enum":["none","minimal","low","medium","high","default"],"description":"Per-MCP thinking level applied to the override model at runtime; null/absent = inherit."},"instructions":{"type":"string","description":"Server-level instructions."},"isPublic":{"type":"boolean","description":"Whether the server is publicly accessible."},"toolNamePrefix":{"type":"string","description":"Prefix applied to tool names."},"allowUiResources":{"type":"boolean","description":"Whether MCP UI resources are enabled."},"allowLlmCompletion":{"type":"boolean","description":"Whether in-server LLM completion is enabled."},"accessKeyHint":{"type":"string","description":"Truncated display hint for the access key (never the hash)."},"centralizedCredentials":{"type":"array","items":{"type":"string"},"description":"Names of centralized env-var credentials (names only, never values)."},"tools":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Discovered tool definitions (sanitized, no secrets)."},"localScriptFormat":{"type":"object","description":"Local script format config for return_script execution mode.","additionalProperties":true},"authorizedEmailCount":{"type":"integer","description":"Number of individually authorised email addresses."},"authorizedGroupCount":{"type":"integer","description":"Number of authorised groups."},"toolsDiscoveredAt":{"type":"string","description":"ISO-8601 timestamp of last tool discovery."},"createdAt":{"type":"string","description":"ISO-8601 creation timestamp."},"updatedAt":{"type":"string","description":"ISO-8601 last-modified timestamp."},"accessKeyUpdatedAt":{"type":"string","description":"ISO-8601 timestamp of last access-key rotation."},"oauthConfig":{"type":"object","description":"OAuth configuration metadata (non-secret fields only; client secrets stripped).","properties":{"provider":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"authorizationUrl":{"type":"string"},"tokenUrl":{"type":"string"},"instanceUrl":{"type":"string"}},"additionalProperties":false},"postgresqlConfig":{"type":"object","description":"PostgreSQL schema scan status (connectionString and graphName stripped).","properties":{"status":{"type":"string"},"schemaNames":{"type":"array","items":{"type":"string"}},"tableCount":{"type":"integer"},"columnCount":{"type":"integer"},"relationshipCount":{"type":"integer"},"connectionHost":{"type":"string"},"connectionDatabase":{"type":"string"},"hasConnectionString":{"type":"boolean","description":"True if a connection string is configured."},"lastScannedAt":{"type":"string","description":"ISO-8601 timestamp of last schema scan."},"errorMessage":{"type":"string"}},"additionalProperties":false}},"additionalProperties":false},"PublicSkill":{"type":"object","description":"A skill as returned by the management API. Only the explicitly allow-listed fields are emitted.","properties":{"id":{"type":"string","description":"Skill ID (skill_…)."},"name":{"type":"string","description":"Skill name."},"description":{"type":"string","description":"Skill description."},"body":{"type":"string","description":"Skill body/instructions (markdown)."},"enabled":{"type":"boolean","description":"Whether this skill is active."},"mode":{"type":"string","enum":["on_demand","always"],"description":"Injection mode: on_demand (tool-call) or always (every turn)."},"source":{"type":"string","enum":["manual","imported"],"description":"How the skill was created."},"sourceServerId":{"type":"string","description":"MCP server ID this skill was imported from (imported skills only)."},"linkedServerIds":{"type":"array","items":{"type":"string"},"description":"MCP server IDs that trigger this skill automatically."},"linkedAgentIds":{"type":"array","items":{"type":"string"},"description":"Inner agent IDs (eval/codegen/vision/native-mcp/sales) that trigger this skill."},"createdAt":{"type":"string","description":"ISO-8601 creation timestamp."},"updatedAt":{"type":"string","description":"ISO-8601 last-modified timestamp."}},"additionalProperties":false},"PublicAccessProtectionKey":{"type":"object","description":"An access-protection key as returned by the management API. keyHash and metadata.createdBy are stripped.","properties":{"id":{"type":"string","description":"Key ID."},"label":{"type":"string","description":"Display label."},"keyPrefix":{"type":"string","description":"Truncated key prefix for display (never the full key)."},"status":{"type":"string","enum":["active","revoked"],"description":"Key status."},"usageCount":{"type":"integer","description":"Number of times this key has been used."},"createdAt":{"type":"string","format":"date-time","description":"ISO-8601 creation timestamp."},"lastUsedAt":{"type":"string","format":"date-time","description":"ISO-8601 last-used timestamp."},"revokedAt":{"type":"string","format":"date-time","description":"ISO-8601 revocation timestamp (present when status=revoked)."}},"additionalProperties":false},"PublicAccessProtectionConfig":{"type":"object","description":"Widget access-protection configuration as returned by the management API. jwtConfig.secret is collapsed to a hasSecret boolean.","properties":{"enabled":{"type":"boolean","description":"Whether access protection is enabled."},"jwtConfig":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether JWT verification is enabled."},"algorithm":{"type":"string","description":"JWT signing algorithm (always HS256)."},"emailClaim":{"type":"string","description":"JWT claim containing the user email."},"hasSecret":{"type":"boolean","description":"True if a JWT signing secret is configured."}},"additionalProperties":false},"keys":{"type":"array","items":{"$ref":"#/components/schemas/PublicAccessProtectionKey"},"description":"Configured access-protection keys."}},"additionalProperties":false},"PublicExternalApiKey":{"type":"object","description":"An external-API key as returned by the management API. keyHash and metadata.createdBy are stripped.","properties":{"id":{"type":"string","description":"Key ID."},"name":{"type":"string","description":"Key display name."},"keyPrefix":{"type":"string","description":"Truncated key prefix for display (never the full key)."},"status":{"type":"string","enum":["active","revoked"],"description":"Key status."},"usageCount":{"type":"integer","description":"Number of times this key has been used."},"createdAt":{"type":"string","format":"date-time","description":"ISO-8601 creation timestamp."},"lastUsedAt":{"type":"string","format":"date-time","description":"ISO-8601 last-used timestamp."},"revokedAt":{"type":"string","format":"date-time","description":"ISO-8601 revocation timestamp (present when status=revoked)."},"metadata":{"type":"object","properties":{"description":{"type":"string","description":"Optional key description."}},"additionalProperties":false}},"additionalProperties":false},"PublicExternalApiConfig":{"type":"object","description":"External-API configuration as returned by the management API. Key hashes are stripped.","properties":{"enabled":{"type":"boolean","description":"Whether the external API is enabled."},"rateLimitPerMinute":{"type":"integer","minimum":1,"maximum":1000,"description":"Requests per minute per key (1-1000)."},"keys":{"type":"array","items":{"$ref":"#/components/schemas/PublicExternalApiKey"},"description":"Configured external-API keys."}},"additionalProperties":false},"PublicAccessLists":{"type":"object","description":"Chatbot access lists as returned by the /access-lists endpoint. Returns full rosters (not counts), available to callers with security:read scope.","properties":{"authorizedEmails":{"type":"array","items":{"type":"string"},"description":"Authorised email addresses."},"authorizedDomains":{"type":"array","items":{"type":"string"},"description":"Authorised email domains."},"allowedEmbedDomains":{"type":"array","items":{"type":"string"},"description":"Allowed embed domains."}},"additionalProperties":false},"PublicTelemetry":{"type":"object","description":"Telemetry configuration as returned by the management API. Exactly 4 fields; any internal fields are stripped.","properties":{"level":{"type":"string","enum":["anonymized","identified","full"],"description":"Telemetry level. anonymized: no PII stored; identified: email/userId retained; full: all interaction data."},"endUserOptOutAllowed":{"type":"boolean","description":"Whether end users may opt out of telemetry collection."},"pseudonymizeIdentifiers":{"type":"boolean","description":"Whether user identifiers are pseudonymized before storage."},"retentionMonths":{"type":"integer","description":"Number of months telemetry data is retained (1-24)."}},"additionalProperties":false},"PublicCostAnalytics":{"type":"object","description":"Cost analytics for a single chatbot as returned by the management API. ownerEmail, userId, per-other-chatbot breakdowns, hourlyActivity, latency, and ttft are always stripped.","properties":{"totalCost":{"type":"number","description":"Total AI cost in USD for the period."},"costPerQuery":{"type":"number","description":"Average cost per query in USD."},"dailyTrend":{"type":"array","description":"Daily cost trend. Sorted ascending by date.","items":{"type":"object","properties":{"date":{"type":"string","description":"ISO date (YYYY-MM-DD)."},"cost":{"type":"number","description":"Total cost for this day in USD."}},"additionalProperties":false}},"modelBreakdown":{"type":"array","description":"Cost breakdown by model. Sorted by cost descending.","items":{"type":"object","properties":{"model":{"type":"string","description":"Model identifier."},"cost":{"type":"number","description":"Total cost for this model in USD."},"tokens":{"type":"integer","description":"Total tokens consumed by this model."},"calls":{"type":"integer","description":"Number of API calls to this model."}},"additionalProperties":false}},"currency":{"type":"string","description":"Currency code (always USD)."},"days":{"type":"integer","description":"Effective lookback window in days (after tier-cap). The window includes today's partial data, so dailyTrend may contain up to days + 1 buckets."},"periodStart":{"type":"string","format":"date-time","description":"ISO-8601 start of the period."},"periodEnd":{"type":"string","format":"date-time","description":"ISO-8601 end of the period."}},"additionalProperties":false},"PublicUsageAnalytics":{"type":"object","description":"Usage analytics for a single chatbot as returned by the management API. ownerEmail, userId, dailyUsage, and aggregate-chatbot fields are stripped.","properties":{"tokenCount":{"type":"integer","description":"Total tokens consumed over the period (input + output)."},"documentCount":{"type":"integer","description":"Current number of documents in the chatbot knowledge base (not time-windowed)."},"queryCount":{"type":"integer","description":"Total recorded cost/usage EVENTS over the period (model calls — embeddings, LLM completions, reranks, agent tool calls — plus other recorded cost events) derived from the cost rollup. NOT user Q&A turns, so this runs several × higher than the per-turn counts. For a turn count use the conversations category (`totalQueries`) or the quality category (`total`). (Cost analytics has no queryCount — it reports costPerQuery + per-model calls.)"},"days":{"type":"integer","description":"Effective lookback window in days (after tier-cap). The window includes today's partial data."}},"additionalProperties":false},"PublicConversationAnalytics":{"type":"object","description":"Conversation-intelligence analytics for a single chatbot. Aggregates + k-anonymity-gated topic/gap titles (dashboard parity). Raw session and user identifiers are never returned.","properties":{"periodStart":{"type":"string","format":"date-time","description":"ISO-8601 start of the period."},"periodEnd":{"type":"string","format":"date-time","description":"ISO-8601 end of the period."},"totalQueries":{"type":"integer","description":"Total user Q&A TURNS in the period (one row per turn). This is the per-turn denominator — it is much lower than usage `queryCount`, which counts recorded model-call and other cost/usage events."},"uniqueSessions":{"type":"integer","description":"Distinct chat sessions."},"answerRate":{"type":"number","description":"Percentage of Q&A turns that were answered (0-100), derived from the unanswered flag. (Independent of the quality category's answerRate, which can be null when unclassified.)"},"unansweredCount":{"type":"integer","description":"Number of unanswered queries."},"kAnonymityThreshold":{"type":"integer","description":"Minimum distinct sessions a topic needs before its title surfaces."},"languages":{"type":"array","description":"Language distribution.","items":{"type":"object","additionalProperties":false,"properties":{"language":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"Language code."},"count":{"type":"integer","description":"Query count for this language."}}}},"topTopics":{"type":"array","description":"Top conversation topics (k-anonymity-gated titles).","items":{"type":"object","additionalProperties":false,"properties":{"title":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"LLM-generated conversation title (k-anon ≥3 sessions)."},"sessions":{"type":"integer"},"turns":{"type":"integer"},"avgTopScore":{"oneOf":[{"type":"number"},{"type":"null"}],"description":"Average retrieval top score (null when no data)."},"unansweredRate":{"oneOf":[{"type":"number"},{"type":"null"}]}}}},"knowledgeGaps":{"type":"array","description":"Topics with high unanswered rate or low retrieval (k-anon-gated titles).","items":{"type":"object","additionalProperties":false,"properties":{"title":{"oneOf":[{"type":"string"},{"type":"null"}]},"sessions":{"type":"integer"},"turns":{"type":"integer"},"unansweredCount":{"type":"integer"},"avgTopScore":{"oneOf":[{"type":"number"},{"type":"null"}]}}}},"scopeUsage":{"type":"array","description":"Per-scope usage + quality.","items":{"type":"object","additionalProperties":false,"properties":{"scopeNamespace":{"oneOf":[{"type":"string"},{"type":"null"}]},"count":{"type":"integer"},"unansweredRate":{"oneOf":[{"type":"number"},{"type":"null"}]},"avgTopScore":{"oneOf":[{"type":"number"},{"type":"null"}]}}}},"hourlyHeatmap":{"type":"array","description":"Query count by UTC hour.","items":{"type":"object","additionalProperties":false,"properties":{"hour":{"type":"integer","description":"0-23 (UTC)."},"count":{"type":"integer"}}}},"conversationDepth":{"type":"array","description":"Session count by turn-count bucket.","items":{"type":"object","additionalProperties":false,"properties":{"bucket":{"oneOf":[{"type":"string"},{"type":"null"}]},"sessions":{"type":"integer"}}}}},"additionalProperties":false},"PublicQueriesAnalytics":{"type":"object","description":"Row-level query log for a single chatbot. Question and answer text are PII-redacted at insert and the end-user's email is masked. Internal session, row, and network identifiers are stripped. Each row's sources[] is a public citation {title, source, pdfSource} — the raw retrieved chunk text and internal source metadata are dropped.","properties":{"queries":{"type":"array","description":"Query-log rows (most recent first).","items":{"type":"object","additionalProperties":false,"properties":{"question":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"User question (PII-redacted at insert)."},"answer":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"Bot answer (PII-redacted at insert)."},"thumb":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"'up' | 'down' | null."},"comment":{"oneOf":[{"type":"string"},{"type":"null"}]},"model":{"oneOf":[{"type":"string"},{"type":"null"}]},"language":{"oneOf":[{"type":"string"},{"type":"null"}]},"userEmail":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"Masked end-user email (never raw)."},"unanswered":{"type":"boolean"},"sources":{"type":"array","description":"Public citations (raw chunk text and internal metadata dropped).","items":{"type":"object","additionalProperties":false,"properties":{"title":{"oneOf":[{"type":"string"},{"type":"null"}]},"source":{"oneOf":[{"type":"string"},{"type":"null"}]},"pdfSource":{"oneOf":[{"type":"string"},{"type":"null"}]}}}},"createdAt":{"oneOf":[{"type":"string"},{"type":"null"}],"format":"date-time"},"inputTokens":{"oneOf":[{"type":"integer"},{"type":"null"}]},"outputTokens":{"oneOf":[{"type":"integer"},{"type":"null"}]},"totalTokens":{"oneOf":[{"type":"integer"},{"type":"null"}]},"latencyMs":{"oneOf":[{"type":"number"},{"type":"null"}]},"ttftMs":{"oneOf":[{"type":"number"},{"type":"null"}]},"sourcesCount":{"oneOf":[{"type":"integer"},{"type":"null"}]},"retrievalTopScore":{"oneOf":[{"type":"number"},{"type":"null"}]},"answerSource":{"oneOf":[{"type":"string"},{"type":"null"}]},"featuresUsed":{"oneOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Feature names used for this turn (string list), or null."},"scopeNamespace":{"oneOf":[{"type":"string"},{"type":"null"}]},"finishReason":{"oneOf":[{"type":"string"},{"type":"null"}]},"containsPII":{"type":"boolean"}}}},"total":{"type":"integer","description":"Total matching rows (pre-pagination)."},"hasMore":{"type":"boolean","description":"True when more rows exist beyond this page."},"feedback":{"type":"object","additionalProperties":false,"description":"Feedback breakdown across all matching rows.","properties":{"thumbsUp":{"type":"integer"},"thumbsDown":{"type":"integer"},"noFeedback":{"type":"integer"},"unanswered":{"type":"integer"}}}},"additionalProperties":false},"PublicQualityAnalytics":{"type":"object","description":"Agent behavior & quality analytics for a single chatbot (aggregate metrics only — no PII).","properties":{"periodStart":{"type":"string","format":"date-time"},"periodEnd":{"type":"string","format":"date-time"},"total":{"type":"integer","description":"All chat-request traces in the period (classified or not)."},"answeredWithContext":{"type":"integer","description":"Answered-with-context turns among CLASSIFIED turns only (the classifier ran and tagged the turn)."},"answeredWithoutContext":{"type":"integer","description":"Answered-without-context turns among CLASSIFIED turns only."},"unanswered":{"type":"integer","description":"Unanswered turns among CLASSIFIED turns only."},"answerRate":{"oneOf":[{"type":"number"},{"type":"null"}],"description":"Percentage of answers that were answered (0-100), computed ONLY over turns the chatbot classified. `null` when there is no such classified data in the window — classification off, no traffic, only unclassified activity, or a query failure. `null` is intentionally distinct from a genuine 0%. See `answerRateTracked`."},"answerRateTracked":{"type":"boolean","description":"Whether `answerRate` is available and reflects provably-classified turns. true → the rate is trustworthy. false → `answerRate` is null: either no classified turns in the window (classification off / no traffic / only unclassified activity) or the underlying metrics were temporarily unavailable. Either way `answerRate` is null (avoids the false ~100% that counting unclassified turns as answered would produce), distinct from a genuine 0%."},"agentDataCollected":{"type":"integer"},"agentToolBudgetExhausted":{"type":"integer"},"avgLatency":{"type":"number","description":"Average latency (ms)."},"thinkingModes":{"type":"array","description":"Thinking-mode usage breakdown.","items":{"type":"object","additionalProperties":false,"properties":{"mode":{"oneOf":[{"type":"string"},{"type":"null"}]},"count":{"type":"integer"}}}},"scopeQuality":{"type":"array","description":"Per-scope quality comparison.","items":{"type":"object","additionalProperties":false,"properties":{"scopeNamespace":{"oneOf":[{"type":"string"},{"type":"null"}]},"total":{"type":"integer"},"answerRate":{"oneOf":[{"type":"number"},{"type":"null"}]}}}},"mcpToolErrors":{"type":"object","additionalProperties":false,"description":"MCP tool-error observability.","properties":{"total":{"type":"integer","description":"Count of terminal MCP tool failures."},"trackingSince":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"Date since which errors have been tracked."}}}},"additionalProperties":false},"PublicSearchAnalytics":{"type":"object","description":"Retrieval-effectiveness analytics for a single chatbot. weakestQueries titles are k-anonymity-gated (≥3 sessions) and exposed at dashboard parity.","properties":{"periodStart":{"type":"string","format":"date-time"},"periodEnd":{"type":"string","format":"date-time"},"kAnonymityThreshold":{"type":"integer"},"weakRetrievalThreshold":{"type":"number"},"total":{"type":"integer"},"skipRagCount":{"type":"integer"},"skipRagRate":{"type":"number","description":"0-100."},"unansweredCount":{"type":"integer"},"scopesUsed":{"type":"integer"},"avgScore":{"oneOf":[{"type":"number"},{"type":"null"}]},"p50Score":{"oneOf":[{"type":"number"},{"type":"null"}]},"p95Score":{"oneOf":[{"type":"number"},{"type":"null"}]},"scoreDistribution":{"type":"array","items":{"type":"object","additionalProperties":false,"properties":{"bucket":{"oneOf":[{"type":"string"},{"type":"null"}]},"count":{"type":"integer"}}}},"scopeQuality":{"type":"array","items":{"type":"object","additionalProperties":false,"properties":{"scopeNamespace":{"oneOf":[{"type":"string"},{"type":"null"}]},"queries":{"type":"integer"},"avgScore":{"oneOf":[{"type":"number"},{"type":"null"}]},"weakRate":{"oneOf":[{"type":"number"},{"type":"null"}]},"unansweredRate":{"oneOf":[{"type":"number"},{"type":"null"}]}}}},"weakestQueries":{"type":"array","description":"Weakest-retrieval topics (k-anon-gated titles).","items":{"type":"object","additionalProperties":false,"properties":{"title":{"oneOf":[{"type":"string"},{"type":"null"}]},"sessions":{"type":"integer"},"avgScore":{"oneOf":[{"type":"number"},{"type":"null"}]},"unansweredCount":{"type":"integer"}}}},"sourcesPerQuery":{"type":"array","items":{"type":"object","additionalProperties":false,"properties":{"bucket":{"oneOf":[{"type":"string"},{"type":"null"}]},"count":{"type":"integer"}}}}},"additionalProperties":false},"PublicInfraAnalytics":{"type":"object","description":"Infrastructure analytics for a single chatbot: latency/TTFT distributions, region split, deploy timeline. deploymentVersion + dataRegion are owner-facing operational metadata.","properties":{"periodStart":{"type":"string","format":"date-time"},"periodEnd":{"type":"string","format":"date-time"},"totalQueries":{"type":"integer"},"avgLatencyMs":{"oneOf":[{"type":"number"},{"type":"null"}]},"p50LatencyMs":{"oneOf":[{"type":"number"},{"type":"null"}]},"p95LatencyMs":{"oneOf":[{"type":"number"},{"type":"null"}]},"p99LatencyMs":{"oneOf":[{"type":"number"},{"type":"null"}]},"avgTtftMs":{"oneOf":[{"type":"number"},{"type":"null"}]},"p50TtftMs":{"oneOf":[{"type":"number"},{"type":"null"}]},"p95TtftMs":{"oneOf":[{"type":"number"},{"type":"null"}]},"coldStartCount":{"type":"integer"},"coldStartRate":{"oneOf":[{"type":"number"},{"type":"null"}],"description":"null when no vector-store-init samples."},"vsiSampleCount":{"type":"integer"},"latencyHistogram":{"type":"array","items":{"type":"object","additionalProperties":false,"properties":{"bucket":{"oneOf":[{"type":"string"},{"type":"null"}]},"count":{"type":"integer"}}}},"ttftHistogram":{"type":"array","items":{"type":"object","additionalProperties":false,"properties":{"bucket":{"oneOf":[{"type":"string"},{"type":"null"}]},"count":{"type":"integer"}}}},"regionSplit":{"type":"array","items":{"type":"object","additionalProperties":false,"properties":{"dataRegion":{"oneOf":[{"type":"string"},{"type":"null"}]},"queries":{"type":"integer"},"avgLatencyMs":{"oneOf":[{"type":"number"},{"type":"null"}]}}}},"deployTimeline":{"type":"array","description":"Last 10 deploy versions with latency stats.","items":{"type":"object","additionalProperties":false,"properties":{"deploymentVersion":{"oneOf":[{"type":"string"},{"type":"null"}]},"queries":{"type":"integer"},"firstSeen":{"oneOf":[{"type":"string"},{"type":"null"}],"format":"date-time"},"lastSeen":{"oneOf":[{"type":"string"},{"type":"null"}],"format":"date-time"},"avgLatencyMs":{"oneOf":[{"type":"number"},{"type":"null"}]},"p95LatencyMs":{"oneOf":[{"type":"number"},{"type":"null"}]}}}}},"additionalProperties":false},"PublicTranscriptionAnalytics":{"type":"object","description":"Video-transcription analytics for a single chatbot. Per-other-chatbot breakdowns (byChatbot) are stripped.","properties":{"totals":{"type":"object","additionalProperties":false,"properties":{"videos":{"type":"integer"},"durationSeconds":{"type":"number"},"costUsd":{"type":"number"},"charCount":{"type":"integer"},"wordCount":{"type":"integer"},"avgProcessingTimeSec":{"type":"number"},"avgCostPerVideoUsd":{"type":"number"}}},"byService":{"type":"array","items":{"type":"object","additionalProperties":false,"properties":{"service":{"oneOf":[{"type":"string"},{"type":"null"}]},"count":{"type":"integer"},"durationSec":{"type":"number"},"costUsd":{"type":"number"}}}},"byLanguage":{"type":"array","items":{"type":"object","additionalProperties":false,"properties":{"language":{"oneOf":[{"type":"string"},{"type":"null"}]},"count":{"type":"integer"}}}},"byPlatform":{"type":"array","items":{"type":"object","additionalProperties":false,"properties":{"platform":{"oneOf":[{"type":"string"},{"type":"null"}]},"count":{"type":"integer"}}}},"byModel":{"type":"array","items":{"type":"object","additionalProperties":false,"properties":{"model":{"oneOf":[{"type":"string"},{"type":"null"}]},"count":{"type":"integer"},"costUsd":{"type":"number"}}}}},"additionalProperties":false},"PublicTokenAnalytics":{"type":"object","description":"Token-usage & latency analytics for a single chatbot (aggregate-only).","properties":{"periodStart":{"type":"string","format":"date-time"},"periodEnd":{"type":"string","format":"date-time"},"totalQueries":{"type":"integer"},"sumTotalTokens":{"type":"integer"},"avgInputTokens":{"oneOf":[{"type":"number"},{"type":"null"}]},"avgOutputTokens":{"oneOf":[{"type":"number"},{"type":"null"}]},"avgTotalTokens":{"oneOf":[{"type":"number"},{"type":"null"}]},"avgLatencyMs":{"oneOf":[{"type":"number"},{"type":"null"}]},"p50TotalTokens":{"oneOf":[{"type":"number"},{"type":"null"}]},"p95TotalTokens":{"oneOf":[{"type":"number"},{"type":"null"}]},"modelMix":{"type":"array","items":{"type":"object","additionalProperties":false,"properties":{"modelName":{"oneOf":[{"type":"string"},{"type":"null"}]},"queries":{"type":"integer"},"avgInputTokens":{"oneOf":[{"type":"number"},{"type":"null"}]},"avgOutputTokens":{"oneOf":[{"type":"number"},{"type":"null"}]},"avgLatencyMs":{"oneOf":[{"type":"number"},{"type":"null"}]}}}},"contextGrowth":{"type":"array","items":{"type":"object","additionalProperties":false,"properties":{"bucket":{"oneOf":[{"type":"string"},{"type":"null"}]},"count":{"type":"integer"},"avgLatencyMs":{"oneOf":[{"type":"number"},{"type":"null"}]},"avgOutputTokens":{"oneOf":[{"type":"number"},{"type":"null"}]}}}},"ttftByModel":{"type":"array","items":{"type":"object","additionalProperties":false,"properties":{"modelName":{"oneOf":[{"type":"string"},{"type":"null"}]},"samples":{"type":"integer"},"avgTtftMs":{"oneOf":[{"type":"number"},{"type":"null"}]},"p50TtftMs":{"oneOf":[{"type":"number"},{"type":"null"}]},"p95TtftMs":{"oneOf":[{"type":"number"},{"type":"null"}]}}}}},"additionalProperties":false},"PublicAggregateAnalytics":{"type":"object","description":"Owner-level workspace usage overview (7-day cross-chatbot trend). The dashboard `success` flag and `debug` block are stripped. generatedAt is always present.","properties":{"dailyUsage":{"type":"array","description":"Per-day query totals for the last 7 days.","items":{"type":"object","additionalProperties":false,"properties":{"date":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"ISO date (YYYY-MM-DD)."},"day":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"Short weekday name."},"queries":{"type":"integer"}}}},"totalQueries":{"type":"integer"},"totalChatbots":{"type":"integer","description":"Number of chatbots the owner has."},"deployedChatbots":{"type":"integer","description":"Number that returned analytics."},"generatedAt":{"type":"string","format":"date-time","description":"When this overview was generated (always present)."}},"additionalProperties":false},"ChatbotConfigDocument":{"type":"object","required":["apiVersion","kind","spec"],"description":"Canonical desired-state configuration document for a chatbot (config-as-code). Returned by GET /config and accepted by the declarative apply. Secrets are never emitted.","properties":{"apiVersion":{"type":"string","enum":["v1"],"description":"Document API version (always \"v1\")."},"kind":{"type":"string","enum":["ChatbotConfig"],"description":"Document kind (always \"ChatbotConfig\")."},"prune":{"type":"boolean","description":"Authoritative-array prune (default true). When true, array sections (mcpServers, skills, domains) are authoritative — resources present on the chatbot but absent from the document are deleted on apply. false = merge-only (create/update, never delete)."},"spec":{"type":"object","description":"The config sections. Every section is OPTIONAL; only sections present are reconciled on apply (absent sections are left untouched, never pruned).","properties":{"core":{"type":"object","description":"Config-relevant core fields (name, description, status, logoUrl, requireAuth, aiConfig, behavior, appearance, byoiConfig, salesAgentEnabled). Deployment/runtime/derived fields and PII rosters are excluded.","additionalProperties":true},"scopes":{"type":"object","description":"Knowledge-scope configuration (mirrors GET /scopes).","properties":{"scopes":{"type":"array","items":{"$ref":"#/components/schemas/PublicScope"}},"uiTree":{"type":"array","items":{"type":"object","additionalProperties":true}},"slotWorkflows":{"type":"array","items":{"type":"object","additionalProperties":true}},"defaultSlotId":{"type":"string"},"defaultScopeLabel":{"type":"string"},"showDefaultScopeInSelector":{"type":"boolean"}},"additionalProperties":false},"accessProtection":{"type":"object","description":"Access-protection config WITHOUT the keys array. jwtConfig.secret is collapsed to hasSecret (never emitted).","properties":{"enabled":{"type":"boolean"},"jwtConfig":{"type":"object","properties":{"enabled":{"type":"boolean"},"algorithm":{"type":"string"},"emailClaim":{"type":"string"},"hasSecret":{"type":"boolean","description":"True if a JWT signing secret is configured."}},"additionalProperties":false}},"additionalProperties":false},"externalApi":{"type":"object","description":"External-API config WITHOUT the keys array.","properties":{"enabled":{"type":"boolean"},"rateLimitPerMinute":{"type":"integer","minimum":1,"maximum":1000}},"additionalProperties":false},"accessLists":{"$ref":"#/components/schemas/PublicAccessLists"},"telemetry":{"$ref":"#/components/schemas/PublicTelemetry"},"mcpServers":{"type":"array","items":{"$ref":"#/components/schemas/PublicMcpServer"},"description":"MCP servers (identity key: name)."},"skills":{"type":"array","items":{"$ref":"#/components/schemas/PublicSkill"},"description":"Skills (identity key: name)."},"domains":{"type":"array","description":"Configured custom domain(s) (identity key: domain).","items":{"type":"object","required":["domain"],"properties":{"domain":{"type":"string","description":"Normalized custom domain (lowercase, no protocol/trailing slash)."}},"additionalProperties":false}}},"additionalProperties":false},"_omittedSections":{"type":"array","items":{"type":"string"},"description":"Sections omitted from `spec` because the caller's key lacks the section read scope (least-privilege). Empty for callers with all scopes."}},"additionalProperties":false},"Plan":{"type":"object","required":["ops","summary"],"description":"A reconcile plan: the per-section, per-resource diff of the desired document against the current config. Deterministic / stable for stable input.","properties":{"ops":{"type":"array","description":"The diff operations.","items":{"type":"object","required":["section","action"],"properties":{"section":{"type":"string","enum":["core","scopes","accessProtection","externalApi","accessLists","telemetry","mcpServers","skills","domains"],"description":"The config section this op targets."},"action":{"type":"string","enum":["noop","create","update","delete"],"description":"The reconcile action."},"key":{"type":"string","description":"Identity key for array-section ops (mcpServers/skills→name, domains→domain). Absent for singleton sections."},"before":{"type":"object","additionalProperties":true,"description":"Current resource (update/delete/noop)."},"after":{"type":"object","additionalProperties":true,"description":"Desired resource (create/update/noop)."},"changedFields":{"type":"array","items":{"type":"string"},"description":"For update ops: the fields that differ (sorted)."}},"additionalProperties":false}},"summary":{"type":"object","required":["create","update","delete","noop"],"description":"Op counts by action.","properties":{"create":{"type":"integer","minimum":0},"update":{"type":"integer","minimum":0},"delete":{"type":"integer","minimum":0},"noop":{"type":"integer","minimum":0}},"additionalProperties":false}},"additionalProperties":false},"ApplyResult":{"type":"object","required":["dryRun","plan"],"description":"The apply response. On dry-run only { dryRun, plan } are present. On execute, applied/failed/config are also present.","properties":{"dryRun":{"type":"boolean","description":"True for a dry-run (no writes)."},"plan":{"$ref":"#/components/schemas/Plan"},"applied":{"type":"array","description":"Ops applied (execute only). Present in apply responses.","items":{"type":"object","required":["section","action"],"properties":{"section":{"type":"string","description":"The op section."},"action":{"type":"string","enum":["noop","create","update","delete"]},"key":{"type":"string","description":"Identity key for array-section ops."},"noop":{"type":"boolean","description":"True when the op was a noop (no service call)."}},"additionalProperties":false}},"failed":{"description":"The op that failed plus its error (execute only). null when all ops succeeded.","oneOf":[{"type":"object","required":["op"],"properties":{"op":{"type":"object","additionalProperties":true,"description":"The offending op."},"status":{"type":"integer","description":"HTTP status the reconcile service returned."},"code":{"type":"string","description":"Machine-readable error code."},"message":{"type":"string","description":"Human-readable error message."}},"additionalProperties":false},{"type":"null"}]},"config":{"description":"The re-assembled config document AFTER apply (execute only). null when the chatbot was deleted concurrently.","oneOf":[{"$ref":"#/components/schemas/ChatbotConfigDocument"},{"type":"null"}]}},"additionalProperties":false},"PublicChatbotUser":{"type":"object","required":["id","email","role","status","emailVerified"],"additionalProperties":false,"properties":{"id":{"type":"string"},"email":{"type":"string"},"displayName":{"type":"string"},"role":{"type":"string","enum":["user","admin"]},"status":{"type":"string","enum":["pending","active","disabled"]},"emailVerified":{"type":"boolean"},"invitedAt":{"type":"string","format":"date-time"},"lastSignInAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"PublicEmailGroup":{"type":"object","required":["id","name","emails"],"additionalProperties":false,"properties":{"id":{"type":"string"},"name":{"type":"string"},"emails":{"type":"array","items":{"type":"string"}},"usageCount":{"type":"integer","description":"Documents using this group (only present when includeUsage=true)."},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"ManagementApiScope":{"type":"string","enum":["chatbots:read","chatbots:write","documents:read","documents:write","videos:read","videos:write","deploy","domains:read","domains:write","mcp:read","mcp:write","skills:read","skills:write","analytics:read","security:read","security:write"],"description":"A single management API scope string."},"OAuthClient":{"type":"object","required":["clientId","name","scopes","status","createdAt","allowedChatbotIds","lastUsedAt"],"properties":{"clientId":{"type":"string","description":"Stable public identifier (occ_…)."},"name":{"type":"string"},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/ManagementApiScope"}},"allowedChatbotIds":{"type":["array","null"],"items":{"type":"string"}},"status":{"type":"string","enum":["active","revoked"]},"createdAt":{"type":"string","format":"date-time"},"lastUsedAt":{"type":["string","null"],"format":"date-time"}}},"OAuthClientCreated":{"type":"object","required":["clientId","name","scopes","status","createdAt","allowedChatbotIds","lastUsedAt","clientSecret"],"properties":{"clientId":{"type":"string"},"name":{"type":"string"},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/ManagementApiScope"}},"allowedChatbotIds":{"type":["array","null"],"items":{"type":"string"}},"status":{"type":"string","enum":["active","revoked"]},"createdAt":{"type":"string","format":"date-time"},"lastUsedAt":{"type":["string","null"],"format":"date-time"},"clientSecret":{"type":"string","description":"The client secret — shown exactly once on creation."}}},"OAuthTokenRequest":{"type":"object","required":["grant_type","client_id","client_secret"],"properties":{"grant_type":{"type":"string","enum":["client_credentials"]},"client_id":{"type":"string"},"client_secret":{"type":"string"},"scope":{"type":"string","description":"Space-separated requested scopes (subset of registered)."}}},"OAuthTokenResponse":{"type":"object","required":["access_token","token_type","expires_in","scope"],"properties":{"access_token":{"type":"string"},"token_type":{"type":"string","enum":["Bearer"]},"expires_in":{"type":"integer","description":"Seconds until expiry."},"scope":{"type":"string","description":"Space-separated granted scopes."}}},"UsageSummary":{"type":"object","required":["period","used","limit","remaining","resetsAt","plan"],"properties":{"period":{"type":"string","enum":["day"],"description":"Quota window."},"used":{"type":"integer","minimum":0,"description":"Calls consumed today via wpk_ keys. Always 0 for unlimited tiers."},"limit":{"type":"integer","minimum":-1,"description":"-1 = unlimited."},"remaining":{"type":["integer","null"],"minimum":0,"description":"null when unlimited."},"resetsAt":{"type":"string","format":"date-time"},"plan":{"type":"string"}}},"IngestChatbotTextDocument":{"type":"object","required":["success","fileName"],"properties":{"fileName":{"type":"string","description":"The entry name (from the request item, or a generated default)."},"success":{"type":"boolean","description":"Whether this entry was added."},"document_id":{"type":"string","description":"Id of the added source — use with GET /documents / DELETE. Absent when the entry failed."},"chunks":{"type":"integer","description":"Number of searchable segments created from this entry."},"error":{"type":"string","description":"Failure reason; present only when success is false."}}},"Error":{"type":"object","required":["error"],"description":"Standard error envelope returned by all v1 endpoints on failure.","properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable error code. Possible values: `not_found`, `unauthorized`, `invalid_api_key`, `insufficient_scope`, `chatbot_not_in_key_scope`, `rate_limited`, `quota_exceeded` (daily API call quota exhausted — Retry-After header present), `bad_request`, `not_implemented`, `internal_error`, `conflict` (a shared resource blocks the operation, e.g. chatbot delete), `deletion_failed` (chatbot deletion could not complete), `plan_upgrade_required` (analytics tier gate — details object contains tab/currentPlan/requiredPlan).","example":"not_found"},"message":{"type":"string","description":"Human-readable error message.","example":"Chatbot not found."},"details":{"type":"object","description":"Optional structured context (e.g., validation field errors).","additionalProperties":true}},"additionalProperties":false}},"additionalProperties":false},"PublicCorrection":{"type":"object","description":"A single transcript correction.","required":["id","status","original","corrected","candidates","charStart","charEnd"],"properties":{"id":{"type":"string"},"status":{"type":"string","enum":["suggested_auto","needs_review","accepted","rejected","custom"]},"original":{"type":"string","description":"The original transcribed text."},"corrected":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"The suggested replacement text, or null."},"candidates":{"type":"array","items":{"type":"string"},"description":"Alternative candidate replacements."},"charStart":{"type":"integer","description":"Start offset of the span in the raw transcript."},"charEnd":{"type":"integer","description":"End offset of the span in the raw transcript."},"videoId":{"type":"string","description":"Present on aggregate (chatbot-level) rows."},"videoTitle":{"type":"string","description":"Present on aggregate (chatbot-level) rows."},"timestamp":{"type":"string","description":"Present on aggregate (chatbot-level) rows."},"context":{"type":"string","description":"A short transcript window around the span, with the span marked."}},"additionalProperties":false},"PublicHandoff":{"type":"object","description":"A Sales Agent lead-delivery (hand-off) channel.","required":["id","enabled","channel","routingMode"],"properties":{"id":{"type":"string"},"enabled":{"type":"boolean"},"channel":{"type":"string","enum":["smtp","resend","webhook"]},"routingMode":{"type":"string","enum":["single","distribution","country","scope"]},"routingConfig":{"type":"object","additionalProperties":true},"templateVersionId":{"type":"string"},"fieldsToInclude":{"type":"array","items":{"type":"string"}},"retryPolicy":{"type":"object","additionalProperties":true},"verifiedSender":{"oneOf":[{"type":"string"},{"type":"null"}]},"webhookUrl":{"oneOf":[{"type":"string"},{"type":"null"}]},"hasSecret":{"type":"boolean","description":"Whether a sending secret is configured."},"hasWebhookHmac":{"type":"boolean","description":"Whether a webhook HMAC secret is configured."},"createdAtSeconds":{"type":"integer"},"updatedAtSeconds":{"type":"integer"}},"additionalProperties":false},"YouTubeVideo":{"type":"object","description":"Public metadata for a YouTube video on the caller's connected channel.","required":["id","title"],"properties":{"id":{"type":"string","description":"The YouTube video ID."},"title":{"type":"string"},"publishedAt":{"type":"string","description":"ISO 8601 publish timestamp."},"thumbnail":{"type":"string","description":"Thumbnail image URL."}},"additionalProperties":false},"PublicSalesAgent":{"type":"object","description":"The Sales Agent configuration plus its runtime enablement flag.","properties":{"salesAgentEnabled":{"type":"boolean","description":"Whether the Sales Agent is live at runtime."},"agentType":{"type":"string"},"promptVersionId":{"type":"string"},"createdAt":{"type":"string","description":"ISO 8601 timestamp."},"updatedAt":{"type":"string","description":"ISO 8601 timestamp."},"enabled":{"type":"boolean"},"promptOverlay":{"type":"string"},"promptSlots":{"type":"object","additionalProperties":true},"promptEditorMode":{"type":"string"},"modelOverride":{"oneOf":[{"type":"string"},{"type":"null"}]},"componentModels":{"type":"object","additionalProperties":true},"fieldsToCapture":{"type":"array","items":{"type":"object","additionalProperties":true}},"guardrailEnabled":{"type":"boolean"},"guardrailRules":{"type":"object","additionalProperties":true},"guardrailCustomRules":{"type":"array","items":{"type":"object","additionalProperties":true}},"triggerCategories":{"type":"array","items":{"type":"string"}},"triggerCustomRules":{"type":"array","items":{"type":"object","additionalProperties":true}},"triggerExclusions":{"type":"array","items":{"type":"object","additionalProperties":true}},"triggerSensitivity":{"type":"string"},"scopeOverrides":{"type":"object","additionalProperties":true},"evalEnabled":{"type":"boolean"}},"additionalProperties":false},"PublicLeadDetail":{"type":"object","description":"A single sales-agent lead with decrypted PII (audit-logged detail read).","properties":{"leadId":{"oneOf":[{"type":"string"},{"type":"null"}]},"capturedAtSeconds":{"oneOf":[{"type":"integer"},{"type":"null"}]},"countryCode":{"oneOf":[{"type":"string"},{"type":"null"}]},"scopeNamespace":{"oneOf":[{"type":"string"},{"type":"null"}]},"disposition":{"oneOf":[{"type":"string"},{"type":"null"}]},"handoffStatus":{"oneOf":[{"type":"string"},{"type":"null"}]},"handoffCompletedAtSeconds":{"oneOf":[{"type":"integer"},{"type":"null"}]},"turnsToCapture":{"oneOf":[{"type":"integer"},{"type":"null"}]},"piiErased":{"type":"boolean"},"piiErasedAtSeconds":{"oneOf":[{"type":"integer"},{"type":"null"}]},"attribution":{"oneOf":[{"type":"object","additionalProperties":true},{"type":"null"}]},"geo":{"oneOf":[{"type":"object","additionalProperties":false,"description":"Coarse geo detail (ipAddressHashed is never surfaced).","properties":{"countryCode":{"oneOf":[{"type":"string"},{"type":"null"}]},"region":{"oneOf":[{"type":"string"},{"type":"null"}]},"city":{"oneOf":[{"type":"string"},{"type":"null"}]},"timezone":{"oneOf":[{"type":"string"},{"type":"null"}]}}},{"type":"null"}]},"pii":{"type":"object","additionalProperties":{"type":"string"},"description":"Decrypted captured contact fields (name/email/phone/company/custom). Empty object when erased or on decrypt failure."},"decryptError":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"Non-null string when server-side PII decrypt failed."}},"additionalProperties":false}}}}