Enterprise Vendor API
Offer customer-owned MonCha bots from your enterprise application: connect, create, train and manage.
On this page
Offer MonCha bots from your own application. Any enterprise with API Vendor enabled can connect customers, create customer-owned bots, configure them, and train their knowledge bases through a separate vendor API.
Enable and set up your application
API Vendor is an optional enterprise feature, off by default. Ask your MonCha administrator to enable it on your contract. After successful payment, an enterprise owner or administrator can open Dashboard → API Vendor to configure the application name, logo, allowed origins and exact callback URLs, generate a key, and view connected customers and sponsored bots.
The reference is public. Credentials and enterprise setup remain in the authenticated paid portal. The portal also provides vendor-api.txt; it is not attached to the invitation email.
Connect → create → train → install
- Your customer clicks Connect MonCha in your application. Your backend creates a connection session with their local customer ID, a random state value and a PKCE S256 challenge.
- Open the returned authorization URL in a browser popup or redirect. The MonCha × your application screen lets the customer sign in or sign up and approve access.
- Your backend verifies callback state against the same signed-in customer session, then exchanges the single-use code with its PKCE verifier. Store the returned connection ID against that customer.
- Send the bot name and Starter, Growth or Scale tier. MonCha checks your enterprise capacity and creates the bot in the customer’s personal MonCha account.
- Configure bot settings, widget and allowed domains. Submit URLs, text or files, then poll the returned training ID to distinguish ready, processing and failed documents.
- Use the returned customer tenant ID and bot ID to install the widget. The enterprise tenant ID is used for backend authentication.
Backend authentication
Keep the key and enterprise tenant ID in server secrets. Every vendor request requires both headers below. Browser code calls your backend; it must never receive the vendor key. Access also requires the customer’s consent and the requested scope.
POST https://api.dev-moncha.com/vendor/v1/customers/CONNECTION_ID/bots
Authorization: Bearer YOUR_VENDOR_SERVER_SECRET
X-Vendor-Tenant-Id: YOUR_ENTERPRISE_TENANT_ID
Content-Type: application/json
Idempotency-Key: customer-support-bot-001
{"name":"Support","plan_tier":"growth","language":"en"}Use a stable idempotency key when retrying bot creation or ingestion. Do not reuse it with a different payload. Rotate credentials in the enterprise portal. These credentials are separate from workspace API and MCP tokens.
Ownership, slots and payment
- Customers can edit their sponsored bots directly in MonCha. Your enterprise can manage only bots sponsored through its customer connections.
- Enterprise-owned and sponsored bots share the contract’s Starter, Growth and Scale slots. Each bot has its own tier quota. Deleting a sponsored bot returns its slot once.
- Enterprise deactivation stops sponsored service while retaining the bot’s data and slot. A lapsed monthly or annual contract also stops funded service.
- Disconnection revokes vendor access and ends sponsorship. Disconnection, deletion and completed personal payment takeover release the sponsored slot.
- Customers can continue with normal MonCha billing through Sponsored bots in their dashboard. A completed takeover keeps their bot ID and data and removes vendor access. Payment confirmation, rather than the checkout redirect alone, activates service.
Existing direct MonCha signup and enterprise workspace flows continue as before. A new customer account created through vendor authorization starts without the direct signup trial. MonCha’s internal administration records the referring enterprise.
Supported operations and boundaries
The reference covers bot settings, widget appearance, domains, installation, usage, knowledge discovery and ingestion, document updates and retraining, training status, analytics, leads, conversations, tickets, bookings, contacts, summaries, existing inbox threads, functions, webhooks, provider imports and bring-your-own WhatsApp connections. Tier and contract entitlements still apply.
Notion and Google imports need separate provider consent. Personal billing, account passwords, team administration, contract changes and new phone-number, inbox, branding or add-on purchases are outside the vendor API. Training uses polling. Existing event webhooks use best-effort delivery without durable retries.
API reference and downloads
Contract version: 2026-09-26. All endpoint paths below use /vendor/v1. Download the complete text guide or OpenAPI 3.1 specification for schemas, success codes, error types and integration examples.
GET /account
Enterprise name and vendor tenant ID
Scope: customers:read
Request
none
Response
{ tenant_id: UUID, name: string }GET /slots
Contract capacity and occupied slots by tier
Scope: bots:read
Request
none
Response
{ data: { starter: Slot, growth: Slot, scale: Slot } }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 }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 consentGET /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 }GET /customers/{id}
Read your connected customer
Scope: customers:read
Request
none
Response
{ data: Customer }DELETE /customers/{id}/connection
Disconnect; revoke access and stop sponsorship
Scope: connect:write
Request
none
Response
{ revoked: true }GET /customers/{id}/bots
List that customer’s bots sponsored by you (up to 100)
Scope: bots:read
Request
none
Response
{ data: Bot[] }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 requiredResponse
{ data: { bot_id: UUID, customer_tenant_id: UUID, connection_id: UUID, plan_tier: Tier, funding: vendor, install: { tenantId: UUID, chatbotId: UUID } } }GET /bots/{botId}
Read a sponsored bot
Scope: bots:read
Request
none
Response
{ data: Bot }PATCH /bots/{botId}
Edit bot name/publication/autosync
Scope: bots:write
Request
{ name?: string, is_active?: boolean, auto_sync_enabled?: boolean }Response
{ data: Bot }DELETE /bots/{botId}
Delete bot; return its tier slot exactly once
Scope: bots:write
Request
none
Response
{ deleted: UUID }POST /bots/{botId}/deactivate
Stop sponsored live service; retain slot and data
Scope: bots:write
Request
none
Response
{ ok: true }POST /bots/{botId}/reactivate
Resume with active payment, consent and slot
Scope: bots:write
Request
none
Response
{ ok: true }PATCH /bots/{botId}/plan
Change tier atomically; retain used quota
Scope: bots:write
Request
{ plan_tier: Tier }Response
{ ok: true }GET /bots/{botId}/settings
Read bot behavior settings
Scope: bots:read
Request
none
Response
{ settings: BotSettings }PUT /bots/{botId}/settings
Save bot behavior settings
Scope: bots:write
Request
BotSettings (same schema as MonCha bot settings)
Response
{ settings: BotSettings }GET /bots/{botId}/widget
Read widget appearance
Scope: bots:read
Request
none
Response
{ config: WidgetConfig }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 }GET /bots/{botId}/domains
Read allowed embed hostnames
Scope: bots:read
Request
none
Response
{ domains: string[] }PUT /bots/{botId}/domains
Set allowed embed domains; empty means unrestricted
Scope: bots:write
Request
{ domains: string[0..20] }Response
{ domains: string[] }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 }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 } }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 requiredResponse
{ data: { training_id: UUID, status: queued, total: number } }POST /bots/{botId}/knowledge/ingest-bulk
Queue up to 100 selected documents
Scope: kb:write
Request
{ documents: DocumentInput[1..100] }; Idempotency-Key requiredResponse
{ data: { training_id: UUID, status: queued, total: number } }GET /bots/{botId}/knowledge
List knowledge documents and status
Scope: kb:read
Request
none
Response
{ data: KnowledgeDocument[] }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 }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 }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 } }GET /bots/{botId}/knowledge/{documentId}
Read one document, its content and current status
Scope: kb:read
Request
none
Response
{ data: KnowledgeDocument }DELETE /bots/{botId}/knowledge/{documentId}
Delete one document and return its page usage
Scope: kb:write
Request
none
Response
{ deleted: UUID }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 } }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 } }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 }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 }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 }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 }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 }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 }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 }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 }[] }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[] }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[] }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 }GET /bots/{botId}/tickets/{id}/messages
Read a ticket thread belonging to this bot
Scope: operations:read
Request
none
Response
{ data: TicketMessage[] }PATCH /bots/{botId}/tickets/{id}
Update ticket status
Scope: operations:write
Request
{ status: open | in_progress | resolved | closed }Response
{ data: Ticket }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 }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 }PATCH /bots/{botId}/summaries/settings
Set the bot’s summary cadence
Scope: operations:write
Request
{ interval_days: 30 | 90 }Response
{ ok: true }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 }GET /bots/{botId}/functions
List only functions created through this vendor connection; secrets are omitted
Scope: integrations:manage
Request
none
Response
{ data: Function[] }DELETE /bots/{botId}/functions/{id}
Delete a vendor-owned functions resource
Scope: integrations:manage
Request
none
Response
{ deleted: UUID }GET /bots/{botId}/webhooks
List only webhooks created through this vendor connection; secrets are omitted
Scope: integrations:manage
Request
none
Response
{ data: Webhook[] }DELETE /bots/{botId}/webhooks/{id}
Delete a vendor-owned webhooks resource
Scope: integrations:manage
Request
none
Response
{ deleted: UUID }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<string,string>, params: Record<string,string> }Response
{ data: Function }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 } }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 }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 }[] }DELETE /bots/{botId}/integrations/{provider}
Delete this scoped provider grant; imported documents are retained
Scope: integrations:manage
Request
none
Response
{ disconnected: true }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 requiredResponse
{ data: { training_id: UUID, status: queued, total: number } }GET /bots/{botId}/channels/whatsapp
Read this vendor’s bring-your-own WhatsApp connection
Scope: integrations:manage
Request
none
Response
{ data: Channel | null }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 }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 } }PATCH /bots/{botId}/channels/whatsapp
Configure the vendor-owned channel
Scope: integrations:manage
Request
{ aiEnabled?: boolean, showOnWidget?: boolean }Response
{ ok: true }DELETE /bots/{botId}/channels/whatsapp
Disconnect the vendor-owned channel
Scope: integrations:manage
Request
none
Response
{ disconnected: true }Complete flow, types, errors and backend/browser examples
MonCha API Vendor — 2026-09-26
Base URL: https://api.moncha.co/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_<secret> and X-Vendor-Tenant-Id: <enterprise tenant UUID>.
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<string,string>, params: Record<string,string> }
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.moncha.co/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.moncha.co/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.