ConvoAI
Menu

REST API

API reference

Every endpoint of the v1 API. All requests use HTTPS and JSON, and authenticate with Authorization: Bearer YOUR_API_KEY.

Basics#

Base URLhttps://convoai.cloud/api/v1/
AuthBearer API key. See Authentication.
BodyJSON, with Content-Type: application/json on writes.
IDsIntegers. Timestamps are ISO 8601 with a UTC offset.
ListsPaginated, 50 per page: { count, next, previous, results }. See Pagination.
Trailing slashEvery path ends in /.
SpecOpenAPI 3.1 at /api/v1/openapi.json.

Paths below are relative to the base URL. An agent is visible to you if you own it or belong to its workspace.

Account

  • GET/me/Your account, plan and rate limit

Agents#

An agent is one configured AI assistant: its business details, persona, knowledge, channels and chat widget. New agents start as draft; train them, then activate.

  • GET/agents/List agents
  • POST/agents/Create an agent
  • GET/agents/{agent_id}/Retrieve full configuration
  • PATCH/agents/{agent_id}/Update configuration
  • DELETE/agents/{agent_id}/Delete an agent (204)
  • POST/agents/{agent_id}/train/Rebuild the prompt and mark ready
  • POST/agents/{agent_id}/activate/Set status to active
  • POST/agents/{agent_id}/pause/Set status to paused

The agent object

List responses return this summary. GET /agents/{agent_id}/, POST and PATCH return it plus grouped configuration: business, persona, messages, features, capabilities, sales, ai, training and keywords.

json
{
  "id": 42,
  "name": "My Sales Agent",
  "business_name": "Acme Dental",
  "phone_number": "+14155550123",
  "status": "active",            // draft | active | paused
  "training_status": "ready",    // untrained | ready
  "ai_enabled": true,
  "language": "en",
  "industry": "generic",
  "tone": "professional",
  "knowledge_count": 12,
  "widget": { … },               // see Chat widget
  "created_at": "2026-01-15T10:30:00+00:00",
  "updated_at": "2026-05-10T08:14:22+00:00"
}

POST /agents/ · PATCH /agents/{agent_id}/

FieldTypeRequiredDescription
namestringPOST onlyDisplay name, up to 100 characters.
industrystringOptionalIndustry template. On create it is applied first, so its defaults fill fields you don't send. Must be one of the model's choices.
tone · language · conversion_mode · ai_modelstringOptionalValidated against the allowed values; an invalid value returns a validation_error listing them.
business_name, business_description, website_url, business_hours, …stringOptionalBusiness details, persona, instructions, canned messages, branding and keyword fields. Strings are trimmed and truncated to each field's maximum length.
booking_enabled, lead_capture_enabled, capability_*, …booleanOptionalFeature and capability flags. Must be JSON true or false.
max_response_lengthintegerOptional50 to 4000.
response_delay_secondsintegerOptional0 to 10.
faqs · available_servicesarrayOptionalUp to 200 entries.

Unknown keys are ignored, so you can send back a document you just read. Changing anything other than branding regenerates the agent's system prompt. Creating an agent past your plan's agent limit returns 403 plan_limit.

POST /agents/{agent_id}/train/ response

json
{
  "id": 42,
  "training_status": "ready",
  "system_prompt": "…",
  "can_activate": true
}

Activate and pause return the agent summary.

Chat widget#

The website chatbox for an agent: its embed key, branding and a ready-made snippet to paste into a site.

  • GET/agents/{agent_id}/widget/Widget config and embed snippet
  • PATCH/agents/{agent_id}/widget/Update branding or enable/disable
  • POST/agents/{agent_id}/widget/rotate-key/Issue a new embed key
json
{
  "enabled": true,
  "embed_key": "3f6c2a9e-…",
  "brand_name": "Acme Dental",
  "logo_url": "",
  "avatar_url": "",
  "primary_color": "#2292A4",
  "secondary_color": "#D96C06",
  "button_text": "",
  "tagline": "",
  "welcome_message": "Hi! How can we help?",
  "script_url": "https://convoai.cloud/widget/convoai-widget.js",
  "loader_url": "https://convoai.cloud/widget/42/chat.js",
  "embed_snippet": "<script src=\"https://convoai.cloud/widget/convoai-widget.js\" data-agent=\"3f6c2a9e-…\" data-color=\"#2292A4\" defer></script>",
  "endpoints": {
    "config": "https://convoai.cloud/api/v1/widget/3f6c2a9e-…/config/",
    "send": "https://convoai.cloud/api/v1/widget/3f6c2a9e-…/send/",
    "history": "https://convoai.cloud/api/v1/widget/3f6c2a9e-…/history/"
  }
}

PATCH accepts the same keys it returns (enabled, brand_name, logo_url, avatar_url, primary_color, secondary_color, button_text, tagline, welcome_message). Colours must be six-digit hex like #2292A4.

Rotating the key breaks every snippet already deployed with the old key, immediately.

Chat#

Talk to the agent's AI directly: the same pipeline that answers on WhatsApp and the web widget, with intent detection, knowledge retrieval and memory. Use it to put your agent in mobile apps, backend services or any custom channel.

  • POST/agents/{agent_id}/chat/Send a message, get the AI reply
  • GET/agents/{agent_id}/chat/history/?session_id=Messages in a chat session

POST /agents/{agent_id}/chat/

FieldTypeRequiredDescription
messagestringRequiredThe user's message, up to 4,000 characters.
session_iduuidOptionalOmit on the first request and one is generated and returned. Send it back to continue the conversation.
contactobjectOptionalAttach the chat to a known contact: { name, email, phone }. With phone it joins that contact's record; with only email it matches by email.
contextstringOptionalExtra context added to the AI prompt (current page, cart contents, account status). Truncated to 2,000 characters.
bash
curl -X POST https://convoai.cloud/api/v1/agents/42/chat/ \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Do you have availability this Friday?",
    "contact": {"name": "Jane Doe", "email": "jane@example.com"},
    "context": "Visitor is on the pricing page."
  }'
json · 200 OK
{
  "reply": "Yes! We have openings Friday at 10am and 2pm…",
  "action": "respond",
  "intent": "booking_inquiry",
  "confidence": 0.94,
  "session_id": "7f0c9a4e-8f21-4b58-9d3a-1c2b3d4e5f60",
  "conversation_id": 1287,
  "contact_id": 553,
  "message_id": 98213,
  "inbound_message_id": 98212
}

If a person has taken over the conversation, the message is saved but the AI does not answer: the response has "reply": null and "action": "human_handling", and you can hand it back with POST /conversations/{id}/resume-ai/. If the AI pipeline fails, the API returns 502 server_error; retry the request. When the plan's AI replies for the period are used up (Free: 65 AI replies every 14 days, across all channels), the API returns 402 ai_quota_exceeded with resets_at: the message is saved to the inbox and no AI reply is generated.

The history endpoint returns { session_id, conversation_id, contact_id, messages } with up to the last 50 messages. It finds sessions by session_id; for a chat attached to a contact by phone, read the transcript with GET /conversations/{conversation_id}/messages/ instead.

Conversations#

A conversation is the thread between an agent and one contact on one channel, with its status and whether the AI or a person is handling it.

  • GET/conversations/List across your agents (?agent_id=, ?status=)
  • GET/agents/{agent_id}/conversations/List for one agent, with filters
  • GET/conversations/{id}/Retrieve a conversation
  • GET/conversations/{id}/messages/Messages, oldest first
  • POST/conversations/{id}/reply/Post a human reply
  • POST/conversations/{id}/resolve/Mark resolved
  • POST/conversations/{id}/handoff/Hand off to a human (AI stops replying)
  • POST/conversations/{id}/resume-ai/Give the conversation back to the AI

The conversation object

json
{
  "id": 1287,
  "agent_id": 42,
  "contact_id": 553,
  "contact_name": "Sofia Ramirez",
  "status": "open",          // open | resolved | pending | waiting_customer | waiting_business
                             // | booked | quoted | spam | closed
  "channel": "whatsapp",     // whatsapp | email | sms | instagram | messenger | webchat | api
  "ai_enabled": true,
  "takeover_mode": "ai",     // ai | human | supervisor
  "priority": "normal",      // low | normal | high | urgent
  "last_message_at": "2026-05-10T09:47:12+00:00",
  "created_at": "2026-05-10T09:00:00+00:00"
}

GET /agents/{agent_id}/conversations/ query parameters

FieldTypeRequiredDescription
statusstringOptionalFilter by status.
sinceISO 8601OptionalCreated at or after this time.
untilISO 8601OptionalCreated at or before this time.
lead_score_minintegerOptionalMinimum lead score of the contact (0 to 100).
pageintegerOptionalPage number. Default 1.

POST /conversations/{id}/reply/

Stores a human reply (sent_by_ai: false) in the conversation, whatever channel it started on, so a visitor in the web chatbox sees it through the widget. Body: { "message": "…" }, required, truncated to 4,000 characters. Returns the message object with 201.

handoff sets takeover_mode to human and ai_enabled to false; resume-ai reverses both. Each returns the updated conversation.

Messages#

Send outbound WhatsApp messages from your backend, or read stored messages. Messages must follow Meta's WhatsApp Business Policy: free-form text can only be sent inside the 24-hour customer service window; outside it, use an approved template.

  • POST/agents/{agent_id}/messages/Send a WhatsApp message
  • GET/messages/?conversation_id=List messages in a conversation
  • GET/messages/{id}/Retrieve a message
  • POST/messages/send/Legacy send endpoint

POST /agents/{agent_id}/messages/

FieldTypeRequiredDescription
tostringRequiredRecipient phone number, as stored on the contact (E.164, e.g. +14155559876).
typestringOptionaltext (default) or template.
textstringIf type is textMessage body.
template_namestringIf type is templateMeta-approved template name.
template_paramsstring[]OptionalValues for the template placeholders, in order.
language_codestringOptionalTemplate language. Default en_US.
json · template message
{
  "to": "+14155559876",
  "type": "template",
  "template_name": "appointment_reminder",
  "template_params": ["Sofia", "May 15", "10:00 AM"],
  "language_code": "en_US"
}
json · 201 Created
{
  "status": "queued",
  "agent_id": 42,
  "to": "+14155559876"
}
Opt-in required. Only message contacts who have opted in to hear from your business. Sending to a contact who opted out returns 403 contact_opted_out. If the agent is not connected to WhatsApp the API returns 422 unprocessable; if WhatsApp rejects the send, 502 server_error.

The message object

json
{
  "id": 98213,
  "conversation_id": 1287,
  "direction": "outbound",   // inbound | outbound
  "type": "text",
  "content": "Yes! We have openings Friday at 10am and 2pm…",
  "sent_by_ai": true,
  "status": "sent",
  "created_at": "2026-05-10T09:47:12+00:00"
}

The legacy POST /messages/send/ takes { "agent_id", "phone_number", "message" }, sends text only and returns { "status": "sent", "agent_id", "to" } with 200. Prefer the agent-scoped endpoint.

Contacts#

Contacts are the people who talk to your agents. ConvoAI creates one on first contact and keeps it up to date from conversations.

  • GET/contacts/List contacts (?agent_id=)
  • POST/contacts/Create or find by phone
  • GET/contacts/{id}/Retrieve a contact
  • PATCH/contacts/{id}/Update a contact
  • DELETE/contacts/{id}/Delete a contact (204)
  • POST/contacts/{id}/tags/Add tags
  • DELETE/contacts/{id}/tags/{tag_name}/Remove a tag
  • POST/contacts/{id}/opt-out/Mark as opted out

The contact object

json
{
  "id": 553,
  "agent_id": 42,
  "name": "Sofia Ramirez",
  "phone": "+14155559876",
  "email": "sofia@example.com",
  "stage": "lead",           // lead | prospect | customer | churned
  "lead_score": 91,
  "opted_in": true,
  "tags": ["hot-lead", "enterprise"],
  "created_at": "2026-04-01T14:00:00+00:00",
  "last_seen_at": "2026-05-10T09:47:12+00:00"
}

POST /contacts/

FieldTypeRequiredDescription
agent_idintegerRequiredThe agent the contact belongs to.
phonestringRequiredPhone number (also accepted as phone_number).
namestringOptionalDisplay name.
emailstringOptionalEmail address.
stagestringOptionalDefault lead.

If a contact with that phone already exists for the agent, it is returned with 200 (empty name and email are filled in); a new contact returns 201.

PATCH accepts name, email, stage, notes, budget and location (strings). Add tags with { "tags": ["vip", "spanish"] }; removing a tag that doesn't exist is a no-op. Tag and opt-out calls return the updated contact.

Knowledge base#

Knowledge documents are what the agent searches before it answers, so replies stay grounded in your content.

  • GET/agents/{agent_id}/knowledge/List documents
  • POST/agents/{agent_id}/knowledge/Create a document
  • GET/knowledge/{id}/Retrieve a document
  • PATCH/knowledge/{id}/Update (re-indexes when content changes)
  • DELETE/knowledge/{id}/Delete and remove from the index (204)
  • POST/knowledge/{id}/reindex/Force re-indexing

The knowledge document object

json
{
  "id": 311,
  "agent_id": 42,
  "title": "Pricing 2026",
  "doc_type": "text",        // text | faq | url | file
  "content": "Cleanings are $89…",
  "source_url": "",
  "is_active": true,
  "chunk_count": 4,
  "created_at": "2026-05-10T09:05:00+00:00",
  "updated_at": "2026-05-10T09:05:00+00:00"
}

POST /agents/{agent_id}/knowledge/

FieldTypeRequiredDescription
titlestringRequiredHuman-readable title.
doc_typestringOptionaltext (default), faq, url or file.
contentstringUnless doc_type is urlThe document text.
urlstringIf doc_type is urlPage address, stored as source_url.
is_activebooleanOptionalInclude in searches. Default true.

Indexing starts when a document is created. PATCH accepts title, content and is_active. reindex returns { "id", "reindexed", "chunk_count" }.

Appointments#

  • GET/appointments/List appointments (?agent_id=, ?status=)
json
{
  "id": 77,
  "agent_id": 42,
  "contact_id": 553,
  "contact_name": "Sofia Ramirez",
  "title": "New-patient exam",
  "start_time": "2026-05-15T10:00:00+00:00",
  "end_time": "2026-05-15T10:45:00+00:00",
  "status": "confirmed",     // pending | confirmed | cancelled | completed
  "notes": "",
  "created_at": "2026-05-10T09:20:00+00:00",
  "updated_at": "2026-05-10T09:20:00+00:00"
}

Results are ordered by start time, newest first.

Analytics#

Pre-aggregated metrics for one agent. Every endpoint takes optional since and until (ISO 8601) and defaults to the last 30 days.

  • GET/agents/{agent_id}/analytics/Summary for the period
  • GET/agents/{agent_id}/analytics/conversations/Conversations per day
  • GET/agents/{agent_id}/analytics/leads/Contacts by stage and temperature
  • GET/agents/{agent_id}/analytics/csat/Feedback, CSAT and SLA metrics
json · GET /analytics/
{
  "period": "2026-05-01 / 2026-05-31",
  "conversations_total": 1247,
  "ai_resolved_pct": 91.4,
  "handoff_pct": 8.6,
  "appointments_booked": 67,
  "messages_sent": 5832,
  "messages_received": 4109
}
json · GET /analytics/conversations/
{
  "agent_id": 42,
  "period": "2026-05-01 / 2026-05-31",
  "results": [ { "date": "2026-05-01", "count": 38 }, … ]
}
json · GET /analytics/leads/
{
  "agent_id": 42,
  "period": "2026-05-01 / 2026-05-31",
  "total_contacts": 310,
  "by_stage": { "lead": 240, "prospect": 45, "customer": 25 },
  "hot_leads": 52,           // lead score 70 and above
  "warm_leads": 98,          // 40 to 69
  "cold_leads": 160          // below 40
}

The CSAT endpoint also accepts days (1 to 365, default 30) and returns message_feedback (positive, negative, total, satisfaction_rate), csat and sla. Where enterprise analytics are not available it returns 501.

Webhooks#

  • GET/agents/{agent_id}/webhooks/List webhooks
  • POST/agents/{agent_id}/webhooks/Register a webhook
  • GET/agents/{agent_id}/webhooks/{webhook_id}/Retrieve a webhook
  • PATCH/agents/{agent_id}/webhooks/{webhook_id}/Update a webhook
  • DELETE/agents/{agent_id}/webhooks/{webhook_id}/Delete a webhook (204)
  • POST/agents/{agent_id}/webhooks/{webhook_id}/test/Send a test event

Payloads, signatures and retries are covered in the webhooks guide.

Agent resources#

Everything else an agent uses is plain CRUD under the agent. List endpoints are paginated (funnel steps come back all at once); POST creates (201), PATCH updates only the fields you send, DELETE returns 204. Unknown keys are ignored.

Path under /agents/{agent_id}/What it holdsRequired on create
products/Catalog items: name, description, price, currency, image_url, sku, is_activename, price
funnels/Conversation funnels: name, description, trigger_keyword, is_activename
funnels/{funnel_id}/steps/Funnel steps: order, step_type, content, save_response_to, delay_seconds. No order appends to the end.—
quick-answers/Saved replies: shortcut, title, contentshortcut, title, content
procedures/Step-by-step procedures the agent follows, with triggers, criteria and allowed actionsname
guidance/Guidance rules: category, title, content, priority, roles and stages they apply totitle, content
actions/Automated actions: action_type, trigger_on, config, conditions, cooldown and limitsname, action_type
roles/Role overrides keyed by role_key. POST upserts: one row per key.role_key
journey-stages/Journey stage instructions keyed by stage_key. POST upserts.stage_key

Every resource has GET, PATCH and DELETE on {path}{item_id}/, except funnel steps, which support PATCH and DELETE only.

E-commerce and channels

  • GET/agents/{agent_id}/ecommerce/Store configuration
  • PATCH/agents/{agent_id}/ecommerce/Update store configuration
  • GET/agents/{agent_id}/channels/Every channel and whether it is live
  • POST/agents/{agent_id}/channels/Connect or update one channel
  • DELETE/agents/{agent_id}/channels/{channel_type}/Disconnect a channel (204)
  • E-commerce credentials (Shopify, Shippo, Klaviyo, Stripe keys) are write-only: they are never returned. Enabling the store also turns on the agent's commerce features.
  • Credential values inside a channel's config come back as __set__; send __set__ back unchanged to keep the stored secret.
  • The web chatbox is configured through /agents/{agent_id}/widget/, not as a channel. WhatsApp's state reflects its Meta connection.

Public widget endpoints#

These power the embeddable chatbox. They take no API key: the agent is identified by its embed_key, and they allow cross-origin requests. You normally use them through the widget script rather than directly.

  • GET/widget/{embed_key}/config/Branding for the chatbox
  • POST/widget/{embed_key}/send/Send a visitor message, get the reply
  • GET/widget/{embed_key}/history/Last 50 messages of a session
  • POST/widget/{embed_key}/visitor/Attach a visitor's name and email

Errors#

Errors use standard HTTP status codes and a JSON body with error and detail. Field-specific validation errors also include field.

json · 400
{
  "error": "validation_error",
  "detail": "to is required.",
  "field": "to"
}
StatuserrorMeaning
400bad_requestBody is not valid JSON, or a value has the wrong type
400validation_errorA required field is missing or a value is out of range
401authentication_requiredMissing or malformed Authorization header
401invalid_api_keyKey not recognised or revoked
401api_key_expiredKey is older than 90 days; generate a new one
403forbiddenYour plan does not include API access
403plan_limitAgent limit reached for your plan
403contact_opted_outThe contact has opted out of messages
402ai_quota_exceededThe plan's AI replies for this period are used up (chat); see resets_at
404not_foundThe resource does not exist or is not in your workspace
422unprocessableAgent not connected to WhatsApp, or template sending unavailable
429rate_limitedToo many requests; see Retry-After
502server_errorWhatsApp send failed, or the AI could not generate a reply

A method an endpoint doesn't support returns 405 with an empty body.