MonCha API Vendor — 2026-09-26 Base URL: https://api.dev-moncha.com/vendor/v1 Available after successful enterprise payment with API - vendor enabled. Store MONCHA_VENDOR_API_KEY and MONCHA_VENDOR_TENANT_ID only on your backend. Every request requires Authorization: Bearer mcv_live_ and X-Vendor-Tenant-Id: . JSON writes require Content-Type: application/json. Keys never go in URLs, browser JavaScript, widgets, or emails. FLOW 1. Save your application name/logo, exact callback URLs and origins in Dashboard → API Vendor. 2. Generate a key (shown once). 3. Your signed-in customer's backend creates random state and a PKCE S256 challenge/verifier. 4. POST /connect/sessions. Open authorization_url in a browser popup or full-page redirect. MonCha displays MonCha × your application. 5. The customer signs in/signs up and consents. On your registered callback, verify state and exchange code from the backend with the verifier. Never send MonCha a customer password. 6. Store connection_id against your authenticated local customer. Create a bot with name and tier. 7. Configure settings/domains, queue knowledge, and poll training status every five seconds. 8. Install with returned customer tenantId and chatbotId. Enterprise tenant ID is ONLY for backend vendor authentication. 9. Customers own/edit their bots in MonCha. You manage only your sponsored bots. 10. Deactivation stops funded service and keeps the slot. Deletion returns one tier slot. Disconnection stops service and revokes access. 11. When enterprise coverage ends, customers see payment required. They may adopt the SAME bot through normal MonCha billing; after takeover you cannot access it. TYPE DEFINITIONS UUID = UUID string. Tier = starter | growth | scale. Slot = { allocated: number; used: number; available: number }. All timestamps are ISO 8601 strings. A missing optional field is omitted, not null, unless documented nullable. Customer = { connection_id: UUID; customer_tenant_id: UUID; external_customer_id: string; connected_at: string; name: string; email: string }. Bot = { bot_id: UUID; customer_tenant_id: UUID; name: string; plan_tier: Tier; is_active: boolean; vendor_deactivated_at: string | null }. KnowledgeDocument = { id: UUID; title: string; status: queued | processing | ready | failed; chunk_count: number; pages_count: number }. GET /account Enterprise name and vendor tenant ID Scope: customers:read Request: none Response: { tenant_id: UUID, name: string } Success: 200 GET /slots Contract capacity and occupied slots by tier Scope: bots:read Request: none Response: { data: { starter: Slot, growth: Slot, scale: Slot } } Success: 200 POST /connect/sessions Start branded browser authorization Scope: connect:write Request: { external_customer_id: string(1..200), redirect_uri: registered URL, state: string(32..256), code_challenge: PKCE S256 base64url(43), scopes?: Scope[] } Response: { authorization_url: URL, expires_in: 600 } Success: 201 POST /connect/exchange Exchange a single-use code on your backend Scope: connect:write Request: { code: hex(64), code_verifier: PKCE string(43..128), redirect_uri: registered URL } Response: { connection_id: UUID, customer_tenant_id: UUID, customer?: { name: string, email: string } }; profile requires customers:read consent Success: 200 GET /customers List your active connected customers Scope: customers:read Request: query: cursor?: UUID, limit?: integer(1..100), default 50 Response: { data: Customer[], next_cursor: UUID | null } Success: 200 GET /customers/{id} Read your connected customer Scope: customers:read Request: none Response: { data: Customer } Success: 200 DELETE /customers/{id}/connection Disconnect; revoke access and stop sponsorship Scope: connect:write Request: none Response: { revoked: true } Success: 200 GET /customers/{id}/bots List that customer’s bots sponsored by you (up to 100) Scope: bots:read Request: none Response: { data: Bot[] } Success: 200 POST /customers/{id}/bots Create a customer-owned bot and reserve one tier slot Scope: bots:write Request: { name: string, plan_tier: starter | growth | scale, language?: string = en }; Idempotency-Key required Response: { data: { bot_id: UUID, customer_tenant_id: UUID, connection_id: UUID, plan_tier: Tier, funding: vendor, install: { tenantId: UUID, chatbotId: UUID } } } Success: 201 GET /bots/{botId} Read a sponsored bot Scope: bots:read Request: none Response: { data: Bot } Success: 200 PATCH /bots/{botId} Edit bot name/publication/autosync Scope: bots:write Request: { name?: string, is_active?: boolean, auto_sync_enabled?: boolean } Response: { data: Bot } Success: 200 DELETE /bots/{botId} Delete bot; return its tier slot exactly once Scope: bots:write Request: none Response: { deleted: UUID } Success: 200 POST /bots/{botId}/deactivate Stop sponsored live service; retain slot and data Scope: bots:write Request: none Response: { ok: true } Success: 200 POST /bots/{botId}/reactivate Resume with active payment, consent and slot Scope: bots:write Request: none Response: { ok: true } Success: 200 PATCH /bots/{botId}/plan Change tier atomically; retain used quota Scope: bots:write Request: { plan_tier: Tier } Response: { ok: true } Success: 200 GET /bots/{botId}/settings Read bot behavior settings Scope: bots:read Request: none Response: { settings: BotSettings } Success: 200 PUT /bots/{botId}/settings Save bot behavior settings Scope: bots:write Request: BotSettings (same schema as MonCha bot settings) Response: { settings: BotSettings } Success: 200 GET /bots/{botId}/widget Read widget appearance Scope: bots:read Request: none Response: { config: WidgetConfig } Success: 200 PUT /bots/{botId}/widget Save widget appearance Scope: bots:write Request: WidgetConfig; hideBranding=true is excluded from vendor API purchases and returns branding_locked Response: { config: WidgetConfig } Success: 200 GET /bots/{botId}/domains Read allowed embed hostnames Scope: bots:read Request: none Response: { domains: string[] } Success: 200 PUT /bots/{botId}/domains Set allowed embed domains; empty means unrestricted Scope: bots:write Request: { domains: string[0..20] } Response: { domains: string[] } Success: 200 GET /bots/{botId}/install Get the CUSTOMER tenant and bot IDs for installation Scope: bots:read Request: none Response: { tenantId: UUID, chatbotId: UUID, apiUrl: URL, widgetUrl: URL | null } Success: 200 GET /bots/{botId}/usage Read isolated tier usage and funding Scope: bots:read Request: none Response: { funding: vendor, live: boolean, plan_tier: Tier, limits: Plan, usage: { messages: number, voice_minutes: number, pages: number } } Success: 200 POST /bots/{botId}/knowledge/ingest Queue one URL/text document Scope: kb:write Request: { documents: [{ title: string(1..300), url?: public URL, text?: string(1..200000), source_type?: url | qa | snippet }] }; exactly one url/text; Idempotency-Key required Response: { data: { training_id: UUID, status: queued, total: number } } Success: 202 POST /bots/{botId}/knowledge/ingest-bulk Queue up to 100 selected documents Scope: kb:write Request: { documents: DocumentInput[1..100] }; Idempotency-Key required Response: { data: { training_id: UUID, status: queued, total: number } } Success: 202 GET /bots/{botId}/knowledge List knowledge documents and status Scope: kb:read Request: none Response: { data: KnowledgeDocument[] } Success: 200 GET /bots/{botId}/training/{trainingId} Poll tracked training progress Scope: kb:read Request: none Response: { training_id: UUID, total: number, ready: number, failed: number, status: processing | completed | partial_failure | failed, documents: KnowledgeDocument[], poll_after_seconds: 5 } Success: 200 POST /bots/{botId}/knowledge/discover Find website pages without starting training Scope: kb:write Request: { url: public URL, max_pages?: integer(1..100)=100, include_paths?: string[0..20], exclude_paths?: string[0..20] } Response: { urls: URL[], pagesFetched: number, truncated: boolean, next_step: string } Success: 200 POST /bots/{botId}/knowledge/upload Upload PDF, DOCX, XLSX, XLS, TXT, CSV or Markdown (10 MiB maximum) Scope: kb:write Request: Raw file bytes; X-File-Name: percent-encoded filename; Idempotency-Key required Response: { data: { training_id: UUID, status: queued, total: number } } Success: 202 GET /bots/{botId}/knowledge/{documentId} Read one document, its content and current status Scope: kb:read Request: none Response: { data: KnowledgeDocument } Success: 200 DELETE /bots/{botId}/knowledge/{documentId} Delete one document and return its page usage Scope: kb:write Request: none Response: { deleted: UUID } Success: 200 PATCH /bots/{botId}/knowledge/{documentId} Edit and retrain a QA/snippet source; queued documents keep their in-progress job Scope: kb:write Request: { title?: string(1..300), text: string(1..200000) } Response: { data: { training_id: UUID, status: queued | processing, document_id: UUID } } Success: 202 POST /bots/{botId}/knowledge/{documentId}/retrain Retrain using the stored source; active jobs are reused Scope: kb:write Request: none Response: { data: { training_id: UUID, status: queued | processing, document_id: UUID } } Success: 202 GET /bots/{botId}/analytics Read this bot’s analytics. Bounded results: leads 500, conversations 100, tickets/contacts 300, bookings 200, summaries 12. Scope: operations:read Request: none Response: { data: analytics[] | object, settings?: object } Success: 200 GET /bots/{botId}/leads Read this bot’s leads. Bounded results: leads 500, conversations 100, tickets/contacts 300, bookings 200, summaries 12. Scope: operations:read Request: none Response: { data: leads[] | object, settings?: object } Success: 200 GET /bots/{botId}/conversations Read this bot’s conversations. Bounded results: leads 500, conversations 100, tickets/contacts 300, bookings 200, summaries 12. Scope: operations:read Request: none Response: { data: conversations[] | object, settings?: object } Success: 200 GET /bots/{botId}/tickets Read this bot’s tickets. Bounded results: leads 500, conversations 100, tickets/contacts 300, bookings 200, summaries 12. Scope: operations:read Request: none Response: { data: tickets[] | object, settings?: object } Success: 200 GET /bots/{botId}/contacts Read this bot’s contacts. Bounded results: leads 500, conversations 100, tickets/contacts 300, bookings 200, summaries 12. Scope: operations:read Request: none Response: { data: contacts[] | object, settings?: object } Success: 200 GET /bots/{botId}/bookings Read this bot’s bookings. Bounded results: leads 500, conversations 100, tickets/contacts 300, bookings 200, summaries 12. Scope: operations:read Request: none Response: { data: bookings[] | object, settings?: object } Success: 200 GET /bots/{botId}/summaries Read this bot’s summaries. Bounded results: leads 500, conversations 100, tickets/contacts 300, bookings 200, summaries 12. Scope: operations:read Request: none Response: { data: summaries[] | object, settings?: object } Success: 200 GET /bots/{botId}/conversations/{sessionId} Read up to 1,000 messages belonging to this bot and session Scope: operations:read Request: none Response: { data: { id: UUID, role: string, content: string, created_at: ISODate, feedback: string | null }[] } Success: 200 GET /bots/{botId}/inbox Read up to 80 existing email inbox threads; does not purchase or enable an inbox Scope: operations:read Request: none Response: { data: InboxThread[] } Success: 200 GET /bots/{botId}/inbox/{id} Read up to 1,000 messages in this bot’s email thread Scope: operations:read Request: none Response: { data: Message[] } Success: 200 PATCH /bots/{botId}/leads/{id} Edit lead status and notes Scope: operations:write Request: { status?: new | contacted | qualified | won | lost, notes?: string(0..10000) } Response: { data: Lead } Success: 200 GET /bots/{botId}/tickets/{id}/messages Read a ticket thread belonging to this bot Scope: operations:read Request: none Response: { data: TicketMessage[] } Success: 200 PATCH /bots/{botId}/tickets/{id} Update ticket status Scope: operations:write Request: { status: open | in_progress | resolved | closed } Response: { data: Ticket } Success: 200 POST /bots/{botId}/tickets/{id}/messages Reply as the enterprise support team; visible to the visitor Scope: operations:write Request: { body: string(1..10000) } Response: { data: TicketMessage } Success: 201 PATCH /bots/{botId}/bookings/{id} Confirm/reject/reschedule/cancel using MonCha booking rules; vendor handles its own customer notifications Scope: operations:write Request: { action: confirm | reject | reschedule | cancel, requestedSlot?: ISODate, rejectionComment?: string(0..1000), cancelComment?: string(0..1000) } Response: { data: Booking } Success: 200 PATCH /bots/{botId}/summaries/settings Set the bot’s summary cadence Scope: operations:write Request: { interval_days: 30 | 90 } Response: { ok: true } Success: 200 PUT /bots/{botId}/notifications Set bot notification destinations; vendor overrides clear on disconnect/takeover Scope: bots:write Request: { notify_email: email | null, ticket_alert_emails: email[0..10] } Response: { ok: true } Success: 200 GET /bots/{botId}/functions List only functions created through this vendor connection; secrets are omitted Scope: integrations:manage Request: none Response: { data: Function[] } Success: 200 DELETE /bots/{botId}/functions/{id} Delete a vendor-owned functions resource Scope: integrations:manage Request: none Response: { deleted: UUID } Success: 200 GET /bots/{botId}/webhooks List only webhooks created through this vendor connection; secrets are omitted Scope: integrations:manage Request: none Response: { data: Webhook[] } Success: 200 DELETE /bots/{botId}/webhooks/{id} Delete a vendor-owned webhooks resource Scope: integrations:manage Request: none Response: { deleted: UUID } Success: 200 POST /bots/{botId}/functions Create a bot-only function, subject to tier and enterprise API entitlement Scope: integrations:manage Request: { name: lowercase identifier(2..61), description: string(10..500), method: GET | POST, url: HTTPS URL, headers?: Record, params: Record } Response: { data: Function } Success: 201 POST /bots/{botId}/webhooks Create a bot-only webhook; the signing secret is shown once; requires tier and contract webhooks Scope: integrations:manage Request: { url: HTTPS URL, events: (lead.created | conversation.handoff | message.unanswered | ticket.created | booking.created)[] } Response: { data: Webhook & { secret: string } } Success: 201 POST /bots/{botId}/integrations/{provider}/authorize Start separate provider consent for notion or google; never uses customer-wide connections Scope: integrations:manage Request: { redirect_uri: registered URL, state: string(32..256) } Response: { authorization_url: URL, expires_in: 600 } Success: 201 GET /bots/{botId}/integrations/{provider} List pages/files available through this bot/vendor’s provider grant Scope: integrations:manage Request: none Response: { data: { id: string, title: string, url?: URL, mimeType?: string }[] } Success: 200 DELETE /bots/{botId}/integrations/{provider} Delete this scoped provider grant; imported documents are retained Scope: integrations:manage Request: none Response: { disconnected: true } Success: 200 POST /bots/{botId}/integrations/{provider}/import Import up to 20 selected pages/files as editable text snapshots Scope: integrations:manage + kb:write Request: { items: { id: string, title: string(1..300), mime_type?: string }[1..20] }; Idempotency-Key required Response: { data: { training_id: UUID, status: queued, total: number } } Success: 202 GET /bots/{botId}/channels/whatsapp Read this vendor’s bring-your-own WhatsApp connection Scope: integrations:manage Request: none Response: { data: Channel | null } Success: 200 POST /bots/{botId}/channels/whatsapp/connect Start provider-hosted connection in a dedicated bot profile; does not buy numbers Scope: integrations:manage Request: { redirect_uri: registered URL } Response: { authorization_url: URL } Success: 200 POST /bots/{botId}/channels/whatsapp/complete Complete after provider consent; retry 409 connect_incomplete after five seconds Scope: integrations:manage Request: none Response: { data: { id: UUID, status: active, phone_number: string | null } } Success: 200 PATCH /bots/{botId}/channels/whatsapp Configure the vendor-owned channel Scope: integrations:manage Request: { aiEnabled?: boolean, showOnWidget?: boolean } Response: { ok: true } Success: 200 DELETE /bots/{botId}/channels/whatsapp Disconnect the vendor-owned channel Scope: integrations:manage Request: none Response: { disconnected: true } Success: 200 ERRORS Envelope: { error: { code: string, message: string }, request_id?: string }. Validation failures identify invalid fields. 401 invalid_vendor_credentials / vendor_tenant_mismatch. 403 vendor_disabled / insufficient_scope / insufficient_consent. 402 vendor_subscription_required. 404 customer_not_found / bot_not_found / training_not_found. 409 slot_unavailable / bot_name_taken / idempotency_conflict. 429 rate_limit_exceeded with Retry-After. 503 request_failed (retry with backoff). Never retry 4xx unchanged. Retry transient 5xx with the SAME Idempotency-Key and body. Keys are 8–200 letters/digits/underscore/colon/dot/hyphen. Reusing a key with different content is a conflict. Bot and ingestion results are stored durably. 300 requests/minute per enterprise (best-effort KV limiter); apply backoff on 429. Rotate by creating a new key, updating your backend, then revoking the old key. EXAMPLE curl 'https://api.dev-moncha.com/vendor/v1/customers/CONNECTION_ID/bots' \ -H 'Authorization: Bearer YOUR_SERVER_SECRET' \ -H 'X-Vendor-Tenant-Id: ENTERPRISE_TENANT_ID' \ -H 'Idempotency-Key: customer-bot-001' \ -H 'Content-Type: application/json' \ -d '{"name":"Support","plan_tier":"growth","language":"en"}' ACCOUNT BOUNDARIES Personal billing, passwords, team membership, enterprise contract editing and customer-wide API tokens are not delegated. Paid phone-number/inbox/add-on purchases are not part of this vendor release. No training webhooks are advertised; use polling. Never assume an undocumented endpoint exists. KNOWLEDGE A YouTube watch URL can be submitted to knowledge/ingest as a URL document. QA uses source_type=qa, question as title and answer as text. Snippets use source_type=snippet. Bulk sources support scrape_options: {extractMainOnly?: boolean, includeSelectors?: string[0..30], excludeSelectors?: string[0..30]}; custom authorization/cookie headers are not accepted. Discover first, select returned URLs, then ingest-bulk. Retrain a whole bot by listing knowledge and calling each document’s retrain endpoint; retain each returned training_id and poll. Imports are snapshots; import again to refresh from the provider. PROVIDER FLOW For integrations/{provider}, provider is notion or google. Authorize returns the provider URL; open it in the browser. MonCha uses its existing configured provider callback. The vendor callback receives state and connected=provider or error=provider_connection_failed. Verify state, then list and import. No provider access token is returned. WhatsApp uses a dedicated bot profile: open connect’s authorization_url, then call complete from your backend. Existing customer channel connections cannot be replaced by vendor credentials. WEBHOOKS Events: lead.created, conversation.handoff, message.unanswered, ticket.created, booking.created. Payload: {event:string,timestamp:number,data:object}. Verify X-MonCha-Signature as lowercase hex HMAC-SHA256(secret, timestamp + '.' + rawBody), compare in constant time, check X-MonCha-Timestamp within five minutes, and deduplicate. Current delivery is best effort without durable retries. Revocation, payment loss and takeover suppress vendor-created hooks/functions. SCHEMAS { "Error": { "type": "object", "properties": { "error": { "type": "object", "properties": { "code": { "type": "string" }, "message": { "type": "string" } }, "required": [ "code", "message" ], "additionalProperties": false }, "request_id": { "type": "string" } }, "required": [ "error" ], "additionalProperties": false }, "Tier": { "type": "string", "enum": [ "starter", "growth", "scale" ] }, "Slot": { "type": "object", "properties": { "allocated": { "type": "number" }, "used": { "type": "number" }, "available": { "type": "number" } }, "required": [ "allocated", "used", "available" ], "additionalProperties": false }, "Bot": { "type": "object", "properties": { "bot_id": { "type": "string", "format": "uuid" }, "customer_tenant_id": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "plan_tier": { "$ref": "#/components/schemas/Tier" }, "is_active": { "type": "boolean" }, "vendor_deactivated_at": { "type": [ "string", "null" ], "format": "date-time" } }, "required": [ "bot_id", "customer_tenant_id", "name", "plan_tier", "is_active", "vendor_deactivated_at" ], "additionalProperties": false }, "Customer": { "type": "object", "properties": { "connection_id": { "type": "string", "format": "uuid" }, "customer_tenant_id": { "type": "string", "format": "uuid" }, "external_customer_id": { "type": "string" }, "connected_at": { "type": "string", "format": "date-time" }, "name": { "type": "string" }, "email": { "type": "string", "format": "email" } }, "required": [ "connection_id", "customer_tenant_id", "external_customer_id", "connected_at", "name", "email" ], "additionalProperties": false }, "BotCreate": { "type": "object", "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 30 }, "plan_tier": { "$ref": "#/components/schemas/Tier" }, "language": { "type": "string", "minLength": 2, "maxLength": 10, "default": "en" } }, "required": [ "name", "plan_tier" ], "additionalProperties": false }, "BotSettings": { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "modelTier": { "type": "string", "enum": [ "fast", "quality" ] }, "language": { "type": "string", "minLength": 2, "maxLength": 10 }, "languageMode": { "type": "string", "enum": [ "match", "fixed" ] }, "supportedLanguages": { "maxItems": 40, "type": "array", "items": { "type": "string", "minLength": 2, "maxLength": 10 } }, "fallbackMessage": { "type": "string", "maxLength": 500 }, "historyMessages": { "type": "integer", "minimum": 0, "maximum": 6 }, "instructions": { "type": "string", "maxLength": 4000 }, "instructionPreset": { "type": "string", "maxLength": 40 }, "persona": { "type": "string", "maxLength": 2000 }, "personaPreset": { "type": "string", "maxLength": 40 }, "temperature": { "type": "number", "minimum": 0, "maximum": 1 }, "collectUserData": { "type": "string", "enum": [ "mandatory", "optional", "off" ] }, "collectFields": { "maxItems": 3, "type": "array", "items": { "type": "string", "enum": [ "name", "email", "phone" ] } }, "captureMode": { "type": "string", "enum": [ "off", "passive", "conversational", "form" ] }, "leadGoal": { "type": "string", "maxLength": 2000 }, "notifyThreshold": { "type": "integer", "minimum": 0, "maximum": 100 }, "translations": { "type": "object", "propertyNames": { "type": "string", "minLength": 2, "maxLength": 10 }, "additionalProperties": { "type": "object", "properties": { "fallbackMessage": { "type": "string", "maxLength": 500 } }, "additionalProperties": false } } }, "additionalProperties": false }, "WidgetConfig": { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "botName": { "type": "string", "minLength": 1, "maxLength": 30 }, "tooltip": { "type": "string", "maxLength": 300 }, "welcomeMessage": { "type": "string", "maxLength": 500 }, "inputPlaceholder": { "type": "string", "maxLength": 80 }, "primaryColor": { "type": "string", "pattern": "^#[0-9a-fA-F]{6}$" }, "textColor": { "type": "string", "pattern": "^#[0-9a-fA-F]{6}$" }, "linkColor": { "type": "string", "pattern": "^#[0-9a-fA-F]{6}$" }, "iconBgColor": { "type": "string", "pattern": "^#[0-9a-fA-F]{6}$" }, "botAvatarBgColor": { "type": "string", "pattern": "^#[0-9a-fA-F]{6}$" }, "voiceOrbColor": { "type": "string", "pattern": "^#[0-9a-fA-F]{6}$" }, "transparentIconBg": { "type": "boolean" }, "launcherIconUrl": { "type": "string", "format": "uri" }, "botIconUrl": { "type": "string", "format": "uri" }, "userIconUrl": { "type": "string", "format": "uri" }, "agentIconUrl": { "type": "string", "format": "uri" }, "showExpand": { "type": "boolean" }, "fontSize": { "type": "integer", "minimum": 11, "maximum": 20 }, "chatHeightPct": { "type": "integer", "minimum": 40, "maximum": 100 }, "darkMode": { "type": "boolean" }, "autoOpenDesktopSec": { "anyOf": [ { "type": "integer", "minimum": 0, "maximum": 120 }, { "type": "null" } ] }, "autoOpenMobileSec": { "anyOf": [ { "type": "integer", "minimum": 0, "maximum": 120 }, { "type": "null" } ] }, "requireTerms": { "type": "boolean" }, "termsUrl": { "type": "string", "format": "uri" }, "disclaimerText": { "type": "string", "maxLength": 500 }, "rtl": { "type": "boolean" }, "externalLinkUrl": { "type": "string", "format": "uri" }, "chatDisabled": { "type": "boolean" }, "disabledMessage": { "type": "string", "maxLength": 300 }, "maxMessagesPerConversation": { "type": "integer", "minimum": 0, "maximum": 100 }, "showDownload": { "type": "boolean" }, "iconSize": { "type": "string", "enum": [ "small", "medium", "large" ] }, "iconShape": { "type": "string", "enum": [ "circle", "square", "rounded" ] }, "position": { "type": "string", "enum": [ "bottom-right", "bottom-left" ] }, "offsetBottom": { "type": "integer", "minimum": 0, "maximum": 200 }, "offsetSide": { "type": "integer", "minimum": 0, "maximum": 200 }, "offsetBottomMobile": { "type": "integer", "minimum": 0, "maximum": 200 }, "offsetSideMobile": { "type": "integer", "minimum": 0, "maximum": 200 }, "launcherShade": { "type": "integer", "minimum": 0, "maximum": 100 }, "hideBranding": { "type": "boolean" }, "hideSources": { "type": "boolean" }, "hideFeedback": { "type": "boolean" }, "hideRefresh": { "type": "boolean" }, "hideTooltip": { "type": "boolean" }, "voiceMode": { "type": "boolean" }, "ticketDepartments": { "type": "object", "propertyNames": { "type": "string" }, "additionalProperties": { "type": "boolean" } }, "watermarkLink": { "type": "string", "format": "uri" }, "watermarkIconUrl": { "type": "string", "format": "uri" }, "quickPrompts": { "maxItems": 6, "type": "array", "items": { "type": "string", "minLength": 1, "maxLength": 120 } }, "followUpChips": { "type": "boolean" }, "followUpPrompts": { "maxItems": 3, "type": "array", "items": { "type": "string", "minLength": 1, "maxLength": 120 } }, "strings": { "type": "object", "properties": { "online": { "type": "string", "maxLength": 40 }, "welcome": { "type": "string", "maxLength": 500 }, "placeholder": { "type": "string", "maxLength": 80 }, "errorGeneric": { "type": "string", "maxLength": 200 }, "errorNoResponse": { "type": "string", "maxLength": 200 }, "poweredBy": { "type": "string", "maxLength": 60 }, "openChat": { "type": "string", "maxLength": 60 }, "closeChat": { "type": "string", "maxLength": 60 }, "send": { "type": "string", "maxLength": 40 }, "startVoice": { "type": "string", "maxLength": 80 }, "stopVoice": { "type": "string", "maxLength": 80 }, "recording": { "type": "string", "maxLength": 40 }, "talkToHuman": { "type": "string", "maxLength": 60 }, "handoffSent": { "type": "string", "maxLength": 200 }, "handoffFailed": { "type": "string", "maxLength": 200 }, "fieldName": { "type": "string", "maxLength": 40 }, "fieldEmail": { "type": "string", "maxLength": 40 }, "fieldPhone": { "type": "string", "maxLength": 40 }, "submit": { "type": "string", "maxLength": 40 }, "skip": { "type": "string", "maxLength": 40 }, "leadThanks": { "type": "string", "maxLength": 200 } }, "additionalProperties": false }, "bookingEnabled": { "type": "boolean" }, "bookingConfig": { "type": "object", "properties": { "label": { "type": "string", "maxLength": 60 }, "requireNotes": { "type": "boolean" }, "extraFields": { "maxItems": 5, "type": "array", "items": { "type": "object", "properties": { "key": { "type": "string", "maxLength": 40 }, "label": { "type": "string", "maxLength": 60 }, "required": { "type": "boolean" } }, "required": [ "key", "label", "required" ], "additionalProperties": false } }, "hoursStart": { "type": "string", "pattern": "^\\d{2}:\\d{2}$" }, "hoursEnd": { "type": "string", "pattern": "^\\d{2}:\\d{2}$" }, "slotMinutes": { "type": "integer", "minimum": 1, "maximum": 1440 }, "visitorPicksTime": { "type": "boolean" }, "mapsUrl": { "type": "string", "maxLength": 2000 } }, "additionalProperties": false }, "homeChannels": { "maxItems": 1, "type": "array", "items": {} }, "translations": { "type": "object", "propertyNames": { "type": "string", "minLength": 2, "maxLength": 10 }, "additionalProperties": { "type": "object", "properties": { "welcomeMessage": { "type": "string", "maxLength": 500 }, "tooltip": { "type": "string", "maxLength": 300 }, "quickPrompts": { "maxItems": 6, "type": "array", "items": { "type": "string", "minLength": 1, "maxLength": 120 } } }, "additionalProperties": false } } }, "additionalProperties": false }, "Training": { "type": "object", "properties": { "training_id": { "type": "string", "format": "uuid" }, "status": { "type": "string", "enum": [ "queued", "processing", "completed", "partial_failure", "failed" ] }, "total": { "type": "integer" }, "ready": { "type": "integer" }, "failed": { "type": "integer" }, "document_id": { "type": "string", "format": "uuid" }, "documents": { "type": "array", "items": { "$ref": "#/components/schemas/KnowledgeDocument" } }, "poll_after_seconds": { "type": "integer" } }, "required": [ "training_id", "status" ], "additionalProperties": false }, "KnowledgeDocument": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "title": { "type": "string" }, "source_url": { "type": [ "string", "null" ] }, "source_type": { "type": "string" }, "content": { "type": [ "string", "null" ] }, "status": { "type": "string", "enum": [ "queued", "processing", "ready", "failed" ] }, "chunk_count": { "type": "integer" }, "pages_count": { "type": "integer" }, "synced_at": { "type": [ "string", "null" ] }, "error_message": { "type": [ "string", "null" ] } }, "required": [ "id", "title", "status", "chunk_count", "pages_count" ], "additionalProperties": false }, "DocumentInput": { "type": "object", "properties": { "title": { "type": "string", "minLength": 1, "maxLength": 300 }, "url": { "type": "string", "format": "uri", "maxLength": 2048 }, "text": { "type": "string", "minLength": 1, "maxLength": 200000 }, "source_type": { "type": "string", "enum": [ "url", "qa", "snippet" ], "default": "url" }, "scrape_options": { "type": "object", "properties": { "extractMainOnly": { "type": "boolean" }, "includeSelectors": { "type": "array", "items": { "type": "string", "maxLength": 200 }, "maxItems": 30 }, "excludeSelectors": { "type": "array", "items": { "type": "string", "maxLength": 200 }, "maxItems": 30 } }, "required": [], "additionalProperties": false } }, "required": [ "title" ], "additionalProperties": false, "oneOf": [ { "required": [ "url" ], "not": { "required": [ "text" ] } }, { "required": [ "text" ], "not": { "required": [ "url" ] } } ] }, "TicketMessage": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "ticket_id": { "type": "string", "format": "uuid" }, "tenant_id": { "type": "string", "format": "uuid" }, "author_type": { "type": "string" }, "author_id": { "type": [ "string", "null" ] }, "author_name": { "type": [ "string", "null" ] }, "body": { "type": "string" }, "attachment_url": { "type": [ "string", "null" ] }, "attachment_type": { "type": [ "string", "null" ] }, "created_at": { "type": "string" } }, "required": [ "id", "ticket_id", "tenant_id", "author_type", "author_id", "author_name", "body", "attachment_url", "attachment_type", "created_at" ], "additionalProperties": false }, "Message": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "role": { "type": "string" }, "content": { "type": "string" }, "created_at": { "type": "string", "format": "date-time" }, "feedback": { "type": [ "string", "null" ] } }, "required": [ "id", "role", "content", "created_at", "feedback" ], "additionalProperties": false }, "Conversation": { "type": "object", "properties": { "session_id": { "type": "string" }, "message_count": { "type": "number" }, "last_at": { "type": "string", "format": "date-time" } }, "required": [ "session_id", "message_count", "last_at" ], "additionalProperties": false }, "Contact": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "name": { "type": [ "string", "null" ] }, "email": { "type": [ "string", "null" ] }, "is_guest": { "type": "boolean" }, "created_at": { "type": "string", "format": "date-time" } }, "required": [ "id", "name", "email", "is_guest", "created_at" ], "additionalProperties": false }, "Lead": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "tenant_id": { "type": "string", "format": "uuid" }, "chatbot_id": { "type": [ "string", "null" ] }, "session_id": { "type": "string" }, "name": { "type": [ "string", "null" ] }, "email": { "type": [ "string", "null" ] }, "phone": { "type": [ "string", "null" ] }, "external_user_id": { "type": [ "string", "null" ] }, "company": { "type": [ "string", "null" ] }, "intent_category": { "type": [ "string", "null" ] }, "intent_summary": { "type": [ "string", "null" ] }, "sentiment": { "type": [ "string", "null" ] }, "notified_at": { "type": [ "string", "null" ] }, "asked_contact_at": { "type": [ "string", "null" ] }, "notes": { "type": [ "string", "null" ] }, "country": { "type": [ "string", "null" ] }, "region": { "type": [ "string", "null" ] }, "score": { "type": "number" }, "score_band": { "type": "string", "enum": [ "hot", "warm", "cold" ] }, "qualification": { "type": [ "object", "null" ], "additionalProperties": { "type": "string" } }, "status": { "type": "string", "enum": [ "new", "contacted", "qualified", "won", "lost" ] }, "captured_via": { "type": "string" }, "confidence": { "type": [ "number", "null" ] }, "channel": { "type": "string" }, "created_at": { "type": "string", "format": "date-time" }, "updated_at": { "type": "string", "format": "date-time" } }, "required": [ "id", "tenant_id", "chatbot_id", "session_id", "name", "email", "phone", "external_user_id", "company", "intent_category", "intent_summary", "sentiment", "notified_at", "asked_contact_at", "notes", "country", "region", "score", "score_band", "qualification", "status", "captured_via", "confidence", "channel", "created_at", "updated_at" ], "additionalProperties": true }, "Ticket": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "tenant_id": { "type": "string", "format": "uuid" }, "contact_id": { "type": "string", "format": "uuid" }, "chatbot_id": { "type": [ "string", "null" ] }, "session_id": { "type": [ "string", "null" ] }, "subject": { "type": "string" }, "department": { "type": [ "string", "null" ] }, "status": { "type": "string", "enum": [ "open", "in_progress", "resolved", "closed" ] }, "priority": { "type": "string" }, "assigned_to": { "type": [ "string", "null" ] }, "agent_unread": { "type": "boolean" }, "visitor_unread": { "type": "boolean" }, "created_at": { "type": "string", "format": "date-time" }, "updated_at": { "type": "string", "format": "date-time" }, "last_message_at": { "type": "string", "format": "date-time" } }, "required": [ "id", "tenant_id", "contact_id", "chatbot_id", "session_id", "subject", "department", "status", "priority", "assigned_to", "agent_unread", "visitor_unread", "created_at", "updated_at", "last_message_at" ], "additionalProperties": true, "description": "List responses additionally include contact_name and contact_email." }, "Booking": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "tenant_id": { "type": "string", "format": "uuid" }, "chatbot_id": { "type": [ "string", "null" ] }, "name": { "type": "string" }, "email": { "type": "string" }, "phone": { "type": "string" }, "notes": { "type": [ "string", "null" ] }, "requested_slot": { "type": [ "string", "null" ] }, "country": { "type": [ "string", "null" ] }, "time_zone": { "type": [ "string", "null" ] }, "rejection_comment": { "type": [ "string", "null" ] }, "confirmed_at": { "type": [ "string", "null" ] }, "rejected_at": { "type": [ "string", "null" ] }, "cancelled_at": { "type": [ "string", "null" ] }, "custom_fields": { "type": [ "object", "null" ], "additionalProperties": { "type": "string" } }, "status": { "type": "string", "enum": [ "pending", "confirmed", "rejected", "cancelled" ] }, "created_at": { "type": "string", "format": "date-time" } }, "required": [ "id", "tenant_id", "chatbot_id", "name", "email", "phone", "notes", "requested_slot", "country", "time_zone", "rejection_comment", "confirmed_at", "rejected_at", "cancelled_at", "custom_fields", "status", "created_at" ], "additionalProperties": true }, "Function": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "description": { "type": "string" }, "method": { "type": "string" }, "url": { "type": "string" }, "params": { "type": "object", "additionalProperties": { "type": "string" } }, "is_active": { "type": "boolean" }, "created_at": { "type": "string", "format": "date-time" }, "tenant_id": { "type": "string", "format": "uuid" }, "chatbot_id": { "type": [ "string", "null" ] }, "vendor_connection_id": { "type": [ "string", "null" ] } }, "required": [ "id", "name", "description", "method", "url", "params", "is_active", "created_at" ], "additionalProperties": false }, "Webhook": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "url": { "type": "string" }, "events": { "type": "array", "items": { "type": "string" } }, "is_active": { "type": "boolean" }, "last_status": { "type": [ "number", "null" ] }, "last_triggered_at": { "type": [ "string", "null" ] }, "created_at": { "type": "string", "format": "date-time" }, "secret": { "type": "string" }, "tenant_id": { "type": "string", "format": "uuid" }, "chatbot_id": { "type": [ "string", "null" ] }, "vendor_connection_id": { "type": [ "string", "null" ] } }, "required": [ "id", "url", "events", "is_active", "last_status", "last_triggered_at", "created_at" ], "additionalProperties": false }, "Channel": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "phone_number": { "type": [ "string", "null" ] }, "display_name": { "type": [ "string", "null" ] }, "status": { "type": "string" }, "ai_enabled": { "type": "boolean" }, "show_on_widget": { "type": "boolean" } }, "required": [ "id", "phone_number", "display_name", "status", "ai_enabled", "show_on_widget" ], "additionalProperties": false }, "InboxThread": { "type": "object", "properties": { "conversation_id": { "type": "string", "format": "uuid" }, "session_id": { "type": "string" }, "subject": { "type": "string" }, "visitor": { "type": "string" }, "last_at": { "type": "string", "format": "date-time" }, "inbound_count": { "type": "number" }, "outbound_count": { "type": "number" } }, "required": [ "conversation_id", "session_id", "subject", "visitor", "last_at", "inbound_count", "outbound_count" ], "additionalProperties": false }, "Analytics": { "type": "object", "properties": { "daily": { "type": "array", "items": { "type": "object", "properties": { "day": { "type": "string" }, "user_messages": { "type": "number" }, "assistant_messages": { "type": "number" } }, "required": [ "day", "user_messages", "assistant_messages" ], "additionalProperties": false } }, "top_questions": { "type": "array", "items": { "type": "object", "properties": { "question": { "type": "string" }, "times": { "type": "number" } }, "required": [ "question", "times" ], "additionalProperties": false } }, "unanswered": { "type": "object", "properties": { "count": { "type": "number" }, "total": { "type": "number" }, "rate": { "type": "number" } }, "required": [ "count", "total", "rate" ], "additionalProperties": false }, "feedback": { "type": "object", "properties": { "positive": { "type": "number" }, "negative": { "type": "number" } }, "required": [ "positive", "negative" ], "additionalProperties": false }, "sessions": { "type": "object", "properties": { "total": { "type": "number" }, "avg_messages": { "type": "number" } }, "required": [ "total", "avg_messages" ], "additionalProperties": false } }, "required": [ "daily", "top_questions", "unanswered", "feedback", "sessions" ], "additionalProperties": false }, "Summary": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "period_start": { "type": "string", "format": "date-time" }, "period_end": { "type": "string", "format": "date-time" }, "message_count": { "type": "number" }, "session_count": { "type": "number" }, "insights": { "type": "object", "properties": { "topQuestions": { "type": "array", "items": { "type": "string" } }, "problems": { "type": "array", "items": { "type": "string" } }, "praise": { "type": "array", "items": { "type": "string" } } }, "required": [ "topQuestions", "problems", "praise" ], "additionalProperties": false } }, "required": [ "id", "period_start", "period_end", "message_count", "session_count", "insights" ], "additionalProperties": false }, "SummarySettings": { "anyOf": [ { "type": "object", "properties": { "intervalDays": { "type": "integer", "enum": [ 30, 90 ] }, "nextDueAt": { "type": "string", "format": "date-time" }, "lastSyncedAt": { "type": [ "string", "null" ] } }, "required": [ "intervalDays", "nextDueAt", "lastSyncedAt" ], "additionalProperties": false }, { "type": "null" } ] }, "Plan": { "type": "object", "properties": { "id": { "type": "string" }, "message_limit": { "type": "number" }, "pages_limit": { "type": "number" }, "voice_minutes_limit": { "type": "number" }, "api_access": { "type": "boolean" }, "webhooks": { "type": "boolean" }, "auto_sync_interval": { "type": [ "string", "null" ] } }, "additionalProperties": true, "description": "Resolved bot tier catalog with enterprise feature restrictions; -1 means unlimited." } } SERVER JAVASCRIPT EXAMPLE (Node.js; adapt to your backend session store) import { randomBytes, createHash, timingSafeEqual } from 'node:crypto'; const base = 'https://api.dev-moncha.com/vendor/v1'; async function moncha(path, body, key) { const r = await fetch(base + path, { method: body ? 'POST' : 'GET', headers: { Authorization: 'Bearer ' + process.env.MONCHA_VENDOR_API_KEY, 'X-Vendor-Tenant-Id': process.env.MONCHA_VENDOR_TENANT_ID, 'Content-Type': 'application/json', ...(key ? {'Idempotency-Key': key} : {}) }, ...(body ? {body: JSON.stringify(body)} : {}) }); const result = await r.json(); if (!r.ok) throw new Error(result.error?.code); return result; } // Start: use your authenticated local user; never accept an arbitrary customer ID from the browser. const verifier = randomBytes(32).toString('base64url'); const state = randomBytes(32).toString('hex'); const challenge = createHash('sha256').update(verifier).digest('base64url'); // Store {state, verifier, localUserId, expiresAt} in your server-side session. const session = await moncha('/connect/sessions', {external_customer_id: localUserId, redirect_uri: CALLBACK, state, code_challenge: challenge}); // Return only session.authorization_url to your frontend. // Callback: require the same signed-in local session; check state using timingSafeEqual and delete it after use. const connection = await moncha('/connect/exchange', {code: callbackCode, code_verifier: stored.verifier, redirect_uri: CALLBACK}); // Persist connection.connection_id under stored.localUserId. const bot = await moncha('/customers/' + connection.connection_id + '/bots', {name:'Support',plan_tier:'growth'}, 'create-support-v1'); const training = await moncha('/bots/' + bot.data.bot_id + '/knowledge/ingest', {documents:[{title:'Website',url:'https://example.com'}]}, 'train-website-v1'); const progress = await moncha('/bots/' + bot.data.bot_id + '/training/' + training.data.training_id); // Poll at poll_after_seconds until completed/partial_failure/failed. // Use bot.data.install.tenantId and .chatbotId in the existing MonCha widget installation. BROWSER EXAMPLE (no MonCha secret) // Call from the Connect button click so browsers allow the popup. const popup = window.open('about:blank', 'moncha-connect', 'width=520,height=760'); const response = await fetch('/your-backend/moncha/connect', {method:'POST'}); const {authorization_url} = await response.json(); if (popup) popup.location = authorization_url; else window.location.assign(authorization_url); // Your callback page may close this popup after its backend completes exchange. // Poll your own backend connection status; do not use wildcard postMessage origins. CALLBACK VALIDATION (server-side; run BEFORE exchanging the code) // stored is read from the same authenticated backend session used at connect. const received = Buffer.from(callbackState || '', 'utf8'); const expected = Buffer.from(stored.state, 'utf8'); if (stored.localUserId !== authenticatedLocalUser.id || stored.expiresAt <= Date.now() || received.length !== expected.length || !timingSafeEqual(received, expected)) { throw new Error('Invalid or expired MonCha callback'); } // Atomically consume this backend session's connect attempt. Reject duplicates. // An access_denied callback ends this attempt without calling exchange. // Never log the callback code, verifier, MonCha key or authorization session URL. RESPONSE EXAMPLES 201 bot creation: {"data":{"bot_id":"11111111-1111-4111-8111-111111111111","customer_tenant_id":"22222222-2222-4222-8222-222222222222","connection_id":"33333333-3333-4333-8333-333333333333","plan_tier":"growth","funding":"vendor","install":{"tenantId":"22222222-2222-4222-8222-222222222222","chatbotId":"11111111-1111-4111-8111-111111111111"}}} 202 training accepted: {"data":{"training_id":"44444444-4444-4444-8444-444444444444","status":"queued","total":1}} 409 no available tier slot: {"error":{"code":"slot_unavailable","message":"slot unavailable"}} 401 missing or wrong credential type: {"error":{"code":"invalid_vendor_credentials","message":"invalid_vendor_credentials"},"request_id":"example"} PERSONAL PAYMENT The vendor does not initiate customer billing. Direct your customer to their MonCha dashboard's Sponsored bots page. MonCha shows a funding preview and uses the customer's normal authenticated checkout. Existing paid customers first use an available plan/add-on slot; if none is available, purchase an extra bot in Billing and retry Continue with my plan. The payment redirect alone does not activate the bot. Verified Stripe state and the recorded funding intent control finalization. Refresh payment status if a webhook is delayed. No vendor key can access these personal billing endpoints.