Skip to content
AISARAISAR

MCP server

Connect an AI agent to your AISAR workspace over the Model Context Protocol.

AISAR exposes a Model Context Protocol server — this lets any MCP-capable AI client (Claude Code, claude.ai, Cursor, and others) work with your workspace through self-describing tools, without hand-writing an integration against every REST endpoint.

Endpoint and authentication

The server speaks Streamable HTTP:

text
POST https://mcp.aisar.app/mcp/company

Authentication uses the same company API token as the REST API (created in the cabinet: Settings → Systems → API, requires the Business plan), sent in the Authorization header:

text
Authorization: Bearer 123|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
The server only accepts service-account tokens — the same tokens the REST API uses. Mobile session, embed-widget, and browser-session tokens are rejected, and so (since 2026-09-28) are narrow tokens of the service account itself with a restricted ability set — the company's full API token is required. An active company subscription is also required.

Transport is Bearer-only — there is no cookie/session path and no OAuth flow. A request without a valid token gets 401; a token of the wrong class gets 401/403.

Since 2026-09-30 this server is business-companies only: a family (Kids) company's service account gets 403 {"message": "...", "error": "company_type_forbidden"} at the transport level, before the request reaches any tool — the whole tool surface (CRM, messaging, broadcasts, AI agents) is business-shaped, and previously a family was only kept out by its plan lacking the api feature.

Connecting a client

Claude Code

bash
claude mcp add --transport http aisar https://mcp.aisar.app/mcp/company \
  --header "Authorization: Bearer YOUR_API_TOKEN"

claude.ai

Settings → Connectors → Add custom connector, URL https://mcp.aisar.app/mcp/company, header Authorization: Bearer YOUR_API_TOKEN.

Generic MCP client (e.g. Cursor)

json
{
  "mcpServers": {
    "aisar": {
      "url": "https://mcp.aisar.app/mcp/company",
      "headers": {
        "Authorization": "Bearer YOUR_API_TOKEN"
      }
    }
  }
}

After connecting, call the whoami tool first — it confirms which company the token maps to.

Server tools

Documentation

These read the platform's live documentation straight from the server — answers are always in sync with the latest deploy.

ToolPurpose
list_docsList available documents
list_doc_sectionsA document's section headings
get_docA whole document, or one section
search_docsFull-text search across the docs
list_webhook_eventsThe platform's webhook event catalogue
get_webhook_payload_specThe exact payload spec for one event
lookup_error_codeExplanation of a WhatsApp Cloud (Meta) error code, e.g. 131047

Platform status

Reflect the public AISAR status (status.aisar.app) into MCP — the same platform-wide data as the status REST group. Not scoped to any company or partner: no company-scope, only an explicit component/days argument where relevant. These three tools are registered identically on the partner MCP server (https://mcp.aisar.app/mcp/partner, authenticated with a partner Bearer token) — the rest of that server's tools (access to the partner's own companies) are out of scope for this table, which otherwise documents the company server's tools only.

ToolArguments
get_platform_statuscomponent? — narrow to one component's signal plus only the incidents/maintenances affecting it
get_platform_status_historycomponent? — up to 90 days of history for one component instead of all
list_platform_incidentsdays? (default and max 90)
If the status mirror has no data yet (no successful pull from the external status-page node), get_platform_status/get_platform_status_history return a tool error rather than a fabricated "healthy" status — the same rule the /v1/public/status/* REST endpoints follow.

Reads

ToolArguments
whoami—
list_channelsstatus?, type?, page, per_page
list_funnels—
list_dealsfunnel_id?, stage_id?, status?, search?, page
get_dealdeal_id
search_contactsquery, page
get_contactcontact_id
list_conversationsstatus?, channel_id?, assigned_to_me?, search?, page
get_conversationconversation_id
search_messagesq, conversation_id? (omit to search the whole company), per_page? (max 50), page? — rate-limited
list_templateschannel_id?, language?, sendable_only?
list_broadcasts / get_broadcast—, broadcast_id
get_dashboard_summaryperiod? (today/7d/30d)

Every search_messages hit carries message_id, conversation_id, direction, message_type, created_at, snippet, contact_name and channel_type — direction and message_type match the messages returned by get_conversation. The search covers the text of every message in the conversation, so it also finds internal notes: they come back with message_type: "note" and direction: "system". The customer never saw a note — tell such hits apart from the correspondence (inbound — from the customer, outbound — to the customer) and never quote them as something the customer said or was told.

Every response uses an explicit field allow-list — channel credentials, provider keys, integration secrets, tokens and passwords are never returned.

Writes

Every write tool enforces the matching permission, is scoped to the token's company, and is audited.

ToolArguments
send_messageconversation_id, text
send_template_messagechannel_id, contact_id\|conversation_id, template_id, variables?
create_contact / update_contactcontact fields
add_internal_noteconversation_id, text
assign_conversationconversation_id, user_id\|team_id
close_conversationconversation_id
create_deal / update_dealdeal fields (stage_id must belong to the funnel)
move_deal_stagedeal_id, stage_id
create_deal_notedeal_id, text

Leads in "Unsorted" (auto-leads nobody has accepted yet, see the "Deals" REST group) are not returned by list_deals: their number comes in a separate unsorted_count field next to pagination (for that funnel when funnel_id is given). update_deal with status/funnel_id/stage_id and move_deal_stage answer such a lead with a tool error — the lead has to be accepted first (POST /v1/deals/{deal}/accept).

Webhook self-service

ToolArguments
list_integrations—
create_webhook_integrationurl, events[], signing_secret?
test_webhookintegration_id\|url
get_webhook_deliveriesintegration_id

The webhook URL passes the same SSRF guard as the REST integration-creation endpoint (private/loopback/link-local/metadata addresses are refused, including DNS-rebinding and encoded-IP forms); there is a per-company cap on the number of integrations; event names are validated against the platform's event catalogue.

AI agents & voice

Managing a company's AI agents (including the voice profile) is the same functionality available on the "AI agents" tab in the cabinet. list_ai_agents/get_ai_agent/create_ai_agent/update_ai_agent/activate_ai_agent/deactivate_ai_agent manage the text/chat side of an agent (prompt, enabled tools, guardrails, budgets); list_knowledge_documents/create_knowledge_document/delete_knowledge_document manage its knowledge base; get_voice_agent/update_voice_agent manage the optional voice profile. As with every tool on this server, the company id is never passed as an argument — the scope is always "your own company" from the token.

ToolArguments
list_ai_agentsstatus? (active\|inactive\|suspended)
get_ai_agentinstance_id
create_ai_agentname, provider, model, config (config.system_prompt required), optional template_id, description, api_key_id, budget limits. api_key_id and config.tools_config.transfer_to_human|assign_to_team.settings.target_team_id must belong to your own company (the same applies to update_ai_agent and the REST form)
update_ai_agentinstance_id, same fields as create_ai_agent, partial — only the fields given are changed, config is deep-merged onto the existing one
activate_ai_agent / deactivate_ai_agentinstance_id
list_knowledge_documentsinstance_id?
create_knowledge_documentinstance_id, title, source_type (text\|url), content (up to 200,000 characters)\|source_url (http/https only, up to 2048 characters; rate-limited — a URL document is fetched at most 10 times per hour per company, then an error is returned)
delete_knowledge_documentinstance_id, document_id
get_voice_agentinstance_id
update_voice_agentinstance_id + voice fields (language, voice_id, greeting, follow-up, escalation, TTS tuning, voice_tools) — only the fields given are changed
The voice tools (get_voice_agent, update_voice_agent) run three checks in order: the company is in the voice AI pilot → it has the ai_agent.* permission → the plan has the voice_ai feature (Business and up). A company outside the pilot gets a plain error message rather than data — that is expected behaviour, not a bug.

In update_voice_agent, the voice_tools field covers only voice-exclusive settings with no text-agent equivalent — currently the single key history ({enabled: bool}, reading back the chat tail during a call). Every other voice agent tool (CRM, messaging, human transfer) is switched on through the same enabled_tools on update_ai_agent used for the text agent on the same instance_id.

Editing the system prompt (update_ai_agent) or the knowledge base (create_knowledge_document/delete_knowledge_document) automatically queues a resync with the voice vendor for agents that have a voice profile — no separate publish step is needed.

AI agent config: supported keys and rules

The list of config keys is closed and shared by every write surface (cabinet, REST, create_ai_agent, update_ai_agent): system_prompt, tone, language_mode, language, reply_length, max_response_length, confidence_threshold, guardrails_topics, timeout_message, working_hours_mode, respect_holidays, schedule, after_hours_action, after_hours_message, operator_presence, blacklist_patterns, escalate_on_low_confidence, escalation, max_messages_per_minute, fallback_provider, fallback_model, fallback_tier, tools, enabled_tools, disabled_tools, tools_config, and, since the "AI employees v2, Phase 1" release (2026-10-10), also rules, address_form, emoji, split_long_replies, signature, first_message, disclose_ai, reply_delay, typing_indicator, wait_for_followups, media, handoff, copilot, channel_modes, attach_to, on_budget_exhausted, budget_warning_pct, review, publish_mode. Any other key is silently discarded (the response is 200, nothing is stored).

  • Tools. enabled_tools is the full set of tools for the agent (values are checked against the registry, an unknown key is a 422). disabled_tools is a one-off "switch these off" instruction: it is accepted only on update, is never stored, and is ignored when an agent is created. The tools http_request, block_contact, delete_messages and send_sticker are not shown in the cabinet and stay on until explicitly switched off via disabled_tools or tools_config.<tool>.enabled = false. The catalog of all tools with metadata is served by GET /v1/ai-agents/tools/catalog (requires the AI agent view permission; response {"data": {"items": [...]}}, each item: key, group (knowledge, dialog, contact, crm, materials, external, other), risk (read, write, external, destructive), requires_confirmation_support, voice_allowed, cabinet_visible, configurable_settings; names and descriptions are not served, the frontend translates them by key).
  • `blacklist_patterns` — up to 50 entries; each is either a string or an object {"pattern": "...", "regex": true|false}. With regex: true the pattern must be a valid regular expression, otherwise 422.
  • `tools_config.http_request.settings` — fields: method (GET, POST, PUT, PATCH, DELETE), url (up to 2048 characters), description, headers and query (objects, up to 32 pairs), body_type (none, json, form, raw), body, auth (type: none, bearer, basic, api_key_header, api_key_query; secret_id must be a secret of your own company), variables (up to 32 items {name, description?, required?}), response (extract_path, max_length up to 50000), timeout_ms (from 500 up to the 30,000 ceiling), retries (0–2), allowed_domains (up to 32; must be non-empty when url is set). Credentials must not be written inline into url, headers or query: a parameter name containing authorization, token, key, secret, password or cookie, or a value starting with Bearer , Basic or Token , returns 422 — keep the key in secrets and select it in auth.secret_id.
  • Human handoff. tools_config.transfer_to_human.settings: target_team_id (a live team of your own company), handoff_message (up to 1000 characters). tools_config.assign_to_team.settings: target_team_id (same rule) and assignment_method (team, round_robin, least_busy).
  • API key and fallback. api_key_id must be a key of your own company and of the same provider as the agent (provider), otherwise 422 on api_key_id. For an agent on its own key, the fallback model is allowed only with the same provider: a fallback_provider of another provider or any fallback_tier returns 422 (set fallback_model of the same provider instead).
  • HTTP tool test and draft chat. POST /v1/ai-agents/tools/http-test is a trial call of the configured request: limiter ai-http-test (10 per minute per user and 60 per minute per company), retries are disabled (retries is always 0), addresses in internal and service networks are rejected (private, reserved, loopback, CGNAT 100.64.0.0/10, NAT64, 6to4); auth.secret_id must be a secret of your own company. POST /v1/ai-agents/draft/chat is a chat with an agent draft: limiter ai-sandbox (30 per minute per user and 120 per minute per company for session callers); api_key_id must belong to the company and match the request provider, otherwise 422.
  • Knowledge base. A document in the response carries an error_code field (null until a fetch fails). For a document with source_type: "url" and no content, the page is downloaded in the background, and on failure error_code takes one of: blocked_url (the address is forbidden by the security policy), too_large, too_many_redirects, bad_content_type, unsupported_encoding, http_error, timeout, empty_content, fetch_failed. When the URL fetch limit (10 per hour per company by default) is exhausted, creating the document returns 429 with a Retry-After header; over MCP it is an error message.
  • Style and delivery (v2). rules — up to 30 rule objects {text (required, up to 300 characters), id?, origin? (manual|template|review), created_at?}; address_form (formal|informal|mirror); emoji (none|rare|free); disclose_ai and split_long_replies (boolean); signature (up to 120 characters); first_message (up to 1000 characters). Pacing and media: reply_delay (instant|default|human, default default), typing_indicator, wait_for_followups (boolean), media — {voice_messages, images, reply_with_voice} (boolean; voice messages are transcribed, photos reach the model only if it has vision). The pre-existing schedule, respect_holidays, working_hours_mode and operator_presence (silent|pause|both) work as before.
  • Handoff and copilot (v2). handoff — {collect_fields, summary_for_operator, notify_after_minutes}: collect_fields — up to 3 values from name, phone, preferred_time (the agent asks the customer for them before handing over), summary_for_operator (boolean — a short brief for the operator), notify_after_minutes — 5, 10 or 15 (if no human answered within that time, an ai_handoff_waiting notification is sent). copilot.enabled (boolean) turns on "Suggest reply"; with the copilot off a suggestion request returns 403 with code: copilot_disabled. The copilot's own monthly cap is the instance field copilot_monthly_budget_cents (integer ≥ 0; null — the copilot spends the agent's budget); the response also carries current_month_spent_cents and copilot_month_spent_cents (read-only).
  • Channels, budget, review (v2). channel_modes — an object { "<channel_id>": mode }, where mode is always, after_hours or team_silent:N (N from 1 to 240 minutes: the agent answers when the team has been silent for N minutes); every key must be a channel id of your own company, otherwise 422; the field is prohibited for agent templates. In the response channel_modes is always an object. attach_to — {new, reopened, with_assignee} (boolean) and groups (off|mentioned): which dialogs the agent joins on its own. on_budget_exhausted — handoff (default: hand the dialog to people) or silent; budget_warning_pct — only 80 (once per agent per month an ai_agent_budget_warning notification is sent); fallback_tier — as before. review.enabled (boolean) turns on the nightly review of the agent (new agents on the platform key get true by default; agents on the company's own key (BYOK) are never switched on: their dialogs must not go to another provider). publish_mode — immediate (default: edits go live at once) or draft (edits accumulate in a draft and go live on publish). Only a user with the ai_review.manage permission (administrator only) may change publish_mode and review.enabled: otherwise 403 with code: ai_review_manage_required (over MCP the tool refuses); re-sending the stored value is not a change.
  • Model: `model_tier` and own key. An instance accepts model_tier (fast, standard, max; null — an exact model) and returns it. The dictionary is GET /v1/ai-agents/model-tiers (requires the AI agent view permission; {"data": {"tiers": [...], "models": [...]}}): tiers[] — tier, surface, provider, model, multiplier (tier → the platform's exact model), models[] — the catalog of exact models: id, provider, label_key, tier, vision. An agent on its own key (api_key_id) has no tiers: model_tier is a 422, and a catalog model of a different provider than the key is a 422 on model (the data would otherwise go to the wrong provider).
  • Versions and draft. GET /v1/ai-agents/instances/{i}/versions — the history (20 per page, newest first; {"data": {"items": [...], "meta": {current_page, last_page, per_page, total}}}; item: version, source, source_ref, summary, created_by {id, name}, created_at); GET …/versions/{n} — one version with a snapshot field (a number, not an id); GET …/draft/diff — {has_draft, draft_updated_at, draft_updated_by, published_version, changes}. Reads need the AI agent view permission; writes need a full token and the ai_review.manage permission (otherwise 403), limited to 30 per minute: POST …/publish (optional force: true; with no draft — 422 with code: no_draft; publishing also shares the ai-tests-run limiter), DELETE …/draft (discard the draft), POST …/versions/{n}/restore (creates a new version from the chosen one). The instance carries published_version, has_draft, and in publish_mode: draft also draft_config and draft_updated_at. A foreign instance or version is a 404.
  • Uploading and editing documents. POST /v1/ai-agents/instances/{i}/documents/upload (multipart, field file, AI agent create permission, 201): TXT, MD, CSV, DOCX, PDF (detected from the content, not the extension; otherwise 422), up to 10 MB, PDF up to 200 pages and 200,000 characters; the file is kept in private storage and parsed in the background; limited to 30 uploads per hour per company. PATCH …/documents/{document} (AI agent update permission): title, inline_in_prompt, valid_until (YYYY-MM-DD or null), content (up to 200,000 characters, text kinds only), fetch_schedule (manual|daily|weekly, a crawled site only), fetch_exclude (up to 20 masks of up to 200 characters). inline_in_prompt: true is allowed only for documents of kind faq, note, company_profile, text — otherwise 422. Creation (POST …/documents) gained kind (text|faq|note|company_profile), valid_until, crawl (boolean: crawl the whole site from the link, up to 200 pages and 600,000 characters, with a pause between requests, 1000 pages per day per company), fetch_schedule and fetch_exclude. Ceilings: 200 documents and 20 sites per agent, 5,000,000 characters per company (otherwise 422). A document in the response carries kind (file for an upload), origin, valid_until, mime, size_bytes, last_fetched_at, fetch_schedule, fetch_exclude, pages_count, content_chars; content itself is returned for text kinds only.
  • New document `error_code` values. Added to the ones above: robots_disallowed (the site forbids crawling in robots.txt), daily_limit (the daily page ceiling is exhausted), quota_exceeded (the company knowledge-base ceiling), and for uploaded files — file_missing, corrupt_file, password_protected, unsupported_file, too_large, timeout, empty_content. A change of the processing state (status, error_code, chunks_count) arrives in real time as the Reverb event .ai-knowledge-document.updated on private-company.{companyId}: payload {document_id, company_id, instance_id, status, error_code, chunks_count} — never the document text (see "Realtime events").
  • Tool settings (v2). tools_config.<tool>.settings: create_deal — funnel_id, default_stage_id (the stage must belong to the funnel); move_deal_stage — funnel_id, allowed_stage_ids (up to 100, all from that funnel); create_deal_task — default_due_hours (1–168), target_team_id; send_template_message — allowed_template_ids (up to 100 templates of your own company); transfer_to_human — additionally fallback_action (queue|close|auto_reply: what to do when nobody in the target team is online; auto_reply sends the customer fallback_message, leaving the dialog open and out of the queue) and fallback_message (up to 1000 characters; falls back to handoff_message when empty). All ids are checked against your company (otherwise 422); they are prohibited for agent templates.
  • Appointment booking: `find_free_slots` and `book_appointment`. Two new tools sharing the settings tools_config.book_appointment.settings: allowed_user_ids (up to 50 company members), default_duration_minutes (15–240), slot_step_minutes (15, 30 or 60), horizon_days (1–60), min_lead_minutes (0–1440), services (up to 50 objects {name, duration_minutes}, duration 5–720). find_free_slots (arguments service?, date_from?, date_to?, assignee_hint?) returns the specialists' free windows; book_appointment (slot_start as ISO-8601 with an offset and assignee_id exactly as listed by find_free_slots, service?, note?) creates a meeting task on the dialog's deal: a taken or off-schedule time is rejected, and a customer may hold only a limited number of upcoming appointments. Over REST such a task shows source: ai and duration_minutes (see the "Deals" group).
  • AI employees dashboard (v2). GET /v1/ai-agents/dashboard?days=7|30|90 (AI agent view permission; days 1 to 90, default 7; days are calendar days in the company timezone, today included) and GET /v1/ai-agents/instances/{i}/overview?days=... return a shared set of fields. The older counters are kept (period_days, conversations, autonomous, autonomous_share, escalated, by_day, escalation_reasons, bookings). Added: value and previous (the same block for the previous period of equal length) - {answered, after_hours, resolved_without_humans, resolved_share (%|null), booked, booked_deals, booked_tasks, deal_amounts, deal_amount_cents, saved_minutes, saved_basis, first_response_ms, team_first_response_ms}; delta - the change in whole percent for answered, after_hours, resolved_without_humans, booked, saved_minutes (null when the earlier value was 0); quality_weekly[] per ISO week - {week_start, enquiries, resolved_share, accuracy, csat, fixes} (shares in % or null; fixes is empty for now); handoff_reasons[] - up to 6 reasons for handing over to a human {reason, count, share}; awaiting_issues (questions of the nightly review waiting for a decision; see the separate "Nightly review" item), phase (empty - no agents, first_days - the oldest agent is under 7 days old, working), now_leading - how many conversations the agent is leading right now. Money: deal_amounts is an array [{currency, amount_cents}] per currency (the currency can be null), deal_amount_cents is the sum only when there is a single currency, otherwise null. Deal amounts are returned only to a user who may view deals: without that permission `value.deal_amounts` and `value.deal_amount_cents` (and the same in `previous`) are `null`. Only the company dashboard has attention[] ({kind: "escalates_often", instance_id, agent, share} - an agent handed over more than half of at least 10 conversations), agents[] (up to 8 agents: instance_id, name, conversations, calls, autonomous_share, resolved_without_humans, escalated, now_leading) and agents_total. overview has instead calls, bookings_by_type, rating {share, answered} and recent[]. Figures are recomputed hourly and cached for 5 minutes. GET /v1/ai-agents/dashboard/agents?days=... returns every agent of the company, including those with no traffic: {"data": {"items": [...same fields as agents[]...], "total": N}}, without the cap of 8.
  • Nightly quality review (v2). Once a day (at 03:00 company-local time) an AI reviewer reads the agent's dialogs of the last 24 hours: a sample of up to 50 dialogs per agent and 200 per company, prioritising answers an operator marked "wrong", then escalations and dialogs with a negative customer rating; deleted or hidden dialogs, blocked or hidden contacts and archived or terminal-status channels are never read. Texts are masked first (phones, e-mails, card and IBAN numbers, account numbers). An agent is reviewed only if it is active, on the platform key, config.review.enabled = true and the company has the review on; Kids companies do not get it. The result is "issues": a type (price_error, fact_error, missing_knowledge, missed_booking, unnecessary_handoff, rule_violation, customer_left), a severity (high, medium, low) and a status: awaiting_decision, needs_input (a person's answer is needed), verifying (the fix is applied, a 7-day check is running), resolved, reopened (the problem came back), rejected, rolled_back. A run has a status of queued|running|finished|deferred|failed. Company settings: GET /v1/ai-agents/review-settings (AI agent view permission) returns {"data": {enabled, digest_email, digest_whatsapp, platform_enabled}}; PUT on the same path (ai_review.manage, administrator only, full token) accepts any of the three boolean fields and returns the same object; platform_enabled=false is the platform-wide kill switch, nothing runs while it is off. The "Check now" button: POST /v1/ai-agents/instances/{i}/review/run-now (ai_review.manage, empty body) returns 202 {"data": {run_id, run_date, status: "queued"}}. Limit: 3 runs per day per agent and 30 per day per company, otherwise 429 with code: too_many_attempts and a Retry-After header. Refusals: 422 with a code saying why the agent is not reviewed (review_off, not_business, not_allowlisted, byok_agent, agent_off, company_off); 409 is daily_budget_exhausted or company_share_exhausted (today's review budget is spent) or already_running (a run is already queued or in progress); another company's agent is 404. Every review endpoint is business-only (Kids gets 403).
  • Review issues and decisions (v2). GET /v1/ai-agents/issues (AI agent view permission) returns {"data": {items, counts, meta}}: counts is the number of issues per status (honouring instance_id/severity/type but not status), meta is {current_page, last_page, per_page, total}. Filters: status (comma-separated list, e.g. awaiting_decision,reopened), q (title search, up to 100 characters), instance_id, severity, type, per_page (10, 20 or 50; default 20), page; order: reopened first, then severity, number of dialogs and recency. GET /v1/ai-agents/issues/{id} is the card with examples, simulation and events; another company's issue is 404. Issue fields: id, instance_id, instance_name, type, severity, status, title, summary, examples_count, conversations_count, examples_hidden, texts_visible, fix_type (kb_answer|instruction_edit|rule_add|handoff_rule|enable_tool|config_change), action_hint, tool_key, fix_payload, contains_customer_supplied_content, proposed_by/at, decided_by/at, reject_reason, applied_version, verify_until, resolved_at, first_found_at, last_found_at. Text visibility: a viewer without access to the channel of any source message sees only the anonymised title and counters: texts_visible=false, examples_hidden=true, summary, fix_payload, reject_reason and simulation are null, and examples arrive as {visible: false}. For a needs_input issue whose tool has to be enabled by hand, action_hint: "enable_tool_manually" and tool_key are returned. Decisions (ai_review.manage, full token, 60 per minute) each return the issue card: POST /issues/{id}/accept applies the fix (send acknowledge_customer_content: true when the fix may contain customer text, otherwise 422 customer_content_ack_required); PATCH /issues/{id}/fix (fix_payload required, fix_type optional) edits the fix; POST /issues/{id}/reject (reason, 1-500 characters); POST /issues/{id}/answer (text, 1-4000 characters; for needs_input); POST /issues/{id}/rollback undoes an applied fix; POST /issues/{id}/propose (permission ai_agent.update) suggests a decision to administrators and sends them an ai_review_proposed notification. POST /issues/bulk-accept with ids (1-50) accepts in bulk only knowledge-base answers without customer text and returns {results: [{id, accepted, code?, message?, http_status?}], accepted, failed}: 200 if at least one was accepted, otherwise 422. Refusal codes {"data": {message, code}}: 409 wrong_status, not_awaiting_decision, draft_pending (publish or discard the draft first), nothing_to_roll_back, newer_versions (the config changed after the fix; undo it through the version history), no_previous_version; 422 bulk_not_allowed, customer_content, customer_content_ack_required.
  • Review on the dashboard. GET /v1/ai-agents/dashboard and GET /v1/ai-agents/instances/{i}/overview read these live (not from the cache): awaiting_issues (up to 5 issues in the card format, honouring text visibility), awaiting_total (how many wait for a person in total: awaiting_decision, needs_input, reopened) and last_review, the latest run {run_date, finished_at, status, error_code, conversations_checked, issues_found} or null. Rows of agents[] on the dashboard (and GET /v1/ai-agents/dashboard/agents) gain awaiting_issues (a number) and accuracy (% of answers without errors in the latest week that has checked answers, or null).
  • Sandbox "Ask as a customer" (v2). POST /v1/ai-agents/instances/{i}/sandbox/chat (AI agent update permission; limiter ai-sandbox: 30 per minute per user and 120 per company, 20 per minute per token for a personal access token) is a trial answer of the agent that is never sent to a customer. Body: message (required, up to 4000), version (published or draft, default published), history[] (up to 20 turns {role: user|assistant, content <=2000}, earliest first), contact_id (a contact of your company; the contact view permission is required too), channel_type (up to 40 characters). Only three tools really execute: knowledge base search, reading the contact card and finding free slots. Every other tool (creating deals and tasks, booking, human handoff, HTTP requests, external actions and any future tool) is replaced by a stub: nothing is created or sent, no approvals appear, and the tool calls are listed in would_do. Response: {reply, sources[{document, fragment, similarity}], rules_applied[], would_do[{tool, summary, arguments}], latency_ms, cost, version}. Errors (body {"data": {"message", "code"}}): 404 not_found (a foreign agent), 404 contact_not_found, 422 budget_exceeded (the monthly budget of the agent is used up), 422 sandbox_daily_budget_exceeded (the platform daily ceiling for sandbox turns and test runs of the company is used up), 502 with a provider code (provider_auth, model_unavailable, provider_rate_limited, provider_quota, provider_timeout, provider_unavailable, provider_error; the raw provider text is never returned). POST /v1/ai-agents/draft-chat now returns the same codes (422, field code) instead of the raw provider message. POST .../sandbox/feedback (201) turns a "right / wrong" verdict into stored knowledge: target (faq, rule, test), question (up to 1000), expected (up to 2000, up to 300 for rule). Response: {target, document_id} for faq (the knowledge document is live at once, documents are not versioned), {target, rule_id, draft, published_version} for rule (goes through the regular update: in draft mode the rule lands in the draft; at most 30 rules, otherwise 422 on expected), {target, test_id} for test.
  • Test questions and the publish gate (v2). GET /v1/ai-agents/instances/{i}/tests (AI agent view permission) returns {"data": {"items": [...], "meta": {total, limit}}}, a ceiling of 100 questions per agent. Item: id, question, expected, channel_type, origin (manual|conversation|sandbox|review), source_conversation_id (null when the conversation is not visible to the caller), last_status, last_answer, last_reason, last_run_at, created_at. Writing needs the AI agent update permission: POST .../tests (question up to 1000, expected up to 2000 - the criterion of a correct answer, channel_type?; 201), PATCH .../tests/{test}, DELETE .../tests/{test}, going over 100 is a 422 on question; POST .../tests/from-conversation (conversation_id, message_id - an answer of this agent, expected?; 201): the customer question is taken from the conversation with personal data masked; without expected the masked text of the answer itself becomes the criterion; 422 codes in code: not_agent_answer (the message is not an answer of this agent), no_question (no text question from the customer precedes the answer), 404 - the conversation or message is not accessible. POST .../tests/run (version? - published or draft, by default the draft when there is one; limiter ai-tests-run, 6 per hour per agent) starts a background run and answers 202 with the run object; 422 no_tests, 409 run_in_progress (+ run_id). GET .../test-runs/{run}: {id, instance_id, trigger (manual|publish), target, status (queued|running|finished|failed), config_version, total, passed, failed, cost_cents, error_code, results[], started_at, finished_at, created_at}; a run hanging for more than 30 minutes is treated as dead. A question result status is passed, failed or error (the cause is in reason, for example budget_exceeded, sandbox_daily_budget_exceeded, timeout or a provider code). The end of a run arrives as the Reverb event .ai.tests.finished. Publish gate: when tests exist, POST .../versions/publish requires a finished run for exactly this draft; with no suitable run the server starts one itself and answers 409 with code: "tests_running", and the publish must be repeated after it finishes; if the run has failures it answers 409 with code: "tests_failed". Both responses carry run_id, total, passed, failed. To publish despite failures pass force: true. An agent with no tests has no gate.
  • AI answer ratings (v2). POST /v1/conversations/{c}/messages/{m}/ai-rating (conversation view permission; limiter 60 per minute per user) is a personal rating of an agent answer: verdict (good or bad, required), comment? (up to 1000). Repeating the call updates the rating; response {message_id, verdict, comment}. Only a message sent by an AI agent can be rated (otherwise 422), in a conversation the user can see (otherwise 404). DELETE on the same path removes the rating: {message_id, verdict: null}. Messages in the conversation list and card carry my_ai_rating (good, bad or null) - the rating of the current user only; the field is absent from WebSocket broadcasts. GET /v1/ai-agents/instances/{i}/ratings/summary?days= (AI agent view permission; days 1-90, default 7) returns {days, good, bad}.

Named external actions

A named external action is a saved recipe for calling one operation on one external HTTP API (host, path, method, auth, response handling) with typed parameters the AI agent fills in itself when it calls the action by name. create_external_action/update_external_action/list_external_actions/get_external_action/delete_external_action manage the action itself; bind_external_action/unbind_external_action attach it to a specific agent instance — creating an action does not, by itself, let any agent call it; test_external_action sends one real test call with sample parameter values. create_ai_agent_secret/list_ai_agent_secrets create and list the credentials an action authenticates with: the secret value is write-only and is never returned anywhere, the response carries only an id (for secret_id) and a masked hint — creating a secret used to require leaving MCP for REST, now the whole "secret → action → test → bind" path stays inside MCP. update_ai_agent_secret updates a secret in place, keeping its id — this is the rotation path: secret is optional, sending it replaces the value and recomputes the masked hint, omitting it changes only the name, header_name/query_param, or allowed_domains, and the value is still never returned either way; the response carries a rotated flag showing whether the value actually changed. delete_ai_agent_secret deletes a secret but refuses while any action still references it — listing their slugs in the error — because external_actions.secret_id is a plain reference, and deleting the secret out from under an action would leave it looking configured while failing every call with an authentication error that explains nothing; the actions need to be pointed at another secret or removed first. In practice that means a compromised key gets rotated with update_ai_agent_secret, not deleted and recreated — the actions bound to it keep working throughout. list_external_action_calls reads the call log with filters by action, outcome, and transport — without it there was no way to find out why an agent-visible call failed. Thirteen tools in total. Whether a call is a write is derived by the server from the HTTP method and is never accepted as an input — force_write may only ratchet a GET action UP to write status, never downgrade a POST/PUT/PATCH/DELETE to a read. All of this requires the ai_action.manage permission, which by default is granted only to the company's admin role — DIFFERENT from ai_agent.update, holding one does not imply the other. Not available for companies on the Kids product. Calling an action works over both transports — from a live chat and during a voice call (governed by the available_in field, see the table below).

ToolArguments
create_external_actionname, model_description (≥20 chars — how the model decides WHEN to call it), method, base_url (scheme+host only, no {{...}}), optional path_template/query_template/header_template, body_type+body_template, parameters[] (what the model fills in), context_bindings[] (what the server fills in), secret_id+auth_type, response_config, timeout_ms, retries, available_in, force_write, log_response, optional slug (immutable after creation)
update_external_actionid + any subset of create_external_action's fields (partial — only the fields given are changed), plus status (draft\|active\|disabled); slug cannot be changed
list_external_actionsstatus? (draft\|active\|disabled)
get_external_actionid
delete_external_actionid (soft delete — bindings to agents are removed, call history is preserved)
bind_external_actioninstance_id, external_action_id, enabled? (default true), hint?, confirmation_required? (chat only)
unbind_external_actioninstance_id, external_action_id
test_external_actionid, parameters? (sample values; context bindings get sample stand-ins — see the callout below)
create_ai_agent_secretname, auth_type (bearer\|basic\|api_key_header\|api_key_query), secret (the value — write-only, never returned), header_name\|query_param (for api_key_*), allowed_domains[] (effectively required — a secret with an empty list fails validation for every action)
list_ai_agent_secrets— (your company's secrets only; the value is never returned, only preview_hint)
update_ai_agent_secretid, optional name, secret (the new value — this is the rotation; omit to leave the current value untouched), header_name\|query_param, allowed_domains[] (replaces the current list wholesale)
delete_ai_agent_secretid (refuses if the secret is still used by any action — their slugs are listed in the error text)
list_external_action_callsaction_id?, outcome? (ok\|refused\|budget_exceeded\|rate_limited\|http_error\|timeout\|blocked), transport? (chat\|voice\|test), per_page?, page?

base_url is scheme+host only, no {{...}} (the action's destination never depends on the model); path_template/query_template/header_template/body_template are templates with {{name}} substitutions. Headers get a tighter rule: header_template may only reference names from context_bindings (what the server fills in), never parameters (what the model fills in) — a model-supplied value can never physically reach an HTTP header, and Authorization/Host/Cookie plus a few other names are reserved outright. A secret passed as secret_id must have a non-empty allowed_domains covering base_url's host — this is checked already at create_external_action/update_external_action, not only at call time.

Action lifecycle: create_external_action creates it in draft status → test_external_action verifies the call with real sample parameters (works in any status, including draft) → update_external_action sets it to active → bind_external_action attaches it to an agent. test_external_action is rate-limited (see "Limits & behaviour") and NEVER accepts or returns the raw response body — only model_sees (what the model would see) and a masked request_preview of what was sent. This is deliberate: a saved base_url can be edited, so it is auditable but not trusted — returning the raw body would recreate exactly the SSRF read-oracle this feature guards against elsewhere. Context bindings a dry run has no way to resolve (contact.*, conversation.id, call.id — there is no counterparty and no call yet) are filled with recognizably fake stand-ins: a phone from the +44 7700 900123 range reserved for examples, an address on example.com, the nil call UUID. A green test means the wiring is right, not that those values were real — a live conversation carries the actual contact and call. Two consequences follow. First, {{contact.phone}} only works if contact.phone is declared in the action's context_bindings; undeclared, the template is refused every time, in the test and on the call alike, and a refusal in the test reads all too easily as "substitution never works". Second, a test run sends a REAL request, so a write action creates a real record on your side — one you may need to delete.

Telephony: call schemes and lookups

Tools for managing call routing. All of them require the company_telephony.manage permission and work within the token's company; writes are audited. Recommended order: the lookups (list_teams, list_telephony_operators, list_audio_prompts, list_telephony_lines), then get_call_scheme, your edit and update_call_scheme, then set_call_scheme_channels. Schemes are listed by list_call_schemes. The graph and its ceilings are described in the "Calls" REST group, section "Call schemes".

ToolArguments and response
get_call_schemescheme_id. The full graph of a scheme (nodes, edges, bound channels), updated_at (pass it back as base_updated_at), a reference of what the nodes point at (teams, operators, prompts, voice agents; a foreign or missing id lands under missing) and a cheat sheet of node types, branches, data keys and size ceilings
update_call_schemescheme_id, nodes[], edges[], optional name, comment, timezone, base_updated_at, confirm_live_edit. REPLACES the whole graph with the same validation as the editor; nodes missing from the body are deleted, so start from get_call_scheme. Node ids from get_call_scheme are kept and calls in progress are not dropped. A scheme with status active changes live routing and needs confirm_live_edit=true. If the scheme changed after base_updated_at, the answer is a conflict with the current updated_at: re-read, merge your changes and retry. The status is not changed here (use set_call_scheme_status). References to unusable operators, teams or prompts come back in warnings
set_call_scheme_channelsscheme_id, channel_ids[] (up to 100). The EXACT set of channels of an inbound scheme, not a delta: listed ones are bound (taken over from another scheme), missing ones are unbound and ring operators directly, an empty list unbinds all. For an active scheme routing changes immediately. Outbound schemes are not bound to channels. warnings lists channels with calling disabled and channels taken over from another scheme
list_telephony_lines— The company lines (SIP and numbers able to take calls): channel_id, name, provider_key, number, state (up, down, connecting, unknown), degraded, registration_status, checked_at, last_call_at, scheme_id. registration_status is a SNAPSHOT of the last check taken at checked_at and is not refreshed automatically: for a SIP line with registration it is registered, unregistered or rejected, for an IP-mode line reachable or unreachable (the line still works, it just cannot be probed). A fresh check exists only in the cabinet
list_teams— The company teams with their members (user_id, name, telephony_enabled; a member without a telephony account cannot be rung). Team ids go into members.teams of ring_group and queue nodes
list_telephony_operators— Operators with a telephony account: user_id, name, availability, status (free, on_call, acw, away, offline), devices (registration of the browser softphone and an external SIP phone, read live; if the registry is unreachable the check is skipped) and team_ids. user_ids go into members.operators
list_audio_prompts— The company audio prompts (id, name, status). The id goes into prompt_id, greeting_id, hold_prompt, ringback and moh_prompt_id; only prompts with status ready are played. Uploading audio over MCP is not available

Limits & behaviour

  • Rate limits (per token, per company): search_messages — 30/min, send_message and send_template_message — 60/min, create_webhook_integration — 10/min, create_knowledge_document — 20/min, named external action calls (chat and voice combined) — 60/min per company, test_external_action — a separate 10/min, documentation tools — 120/min, telephony: scheme reads and lookups — 120/min, update_call_scheme and set_call_scheme_channels — 30/min, list_telephony_operators — 30/min.
  • External-action call ceilings (independent of the rate limit above): at most 3 calls per model conversation turn, 20 per conversation per hour, 5 per voice call — bounding the blast radius of a looping call or a successful prompt injection.
  • Pagination: per_page is capped at 50.
  • No bulk send: messaging tools only reach an existing conversation or a single contact — there is no mass-broadcast surface here (use the Broadcasts feature in the cabinet for that).
  • Untrusted content: message bodies, contact names and notes returned by these tools are third-party data, not instructions for the model.

Troubleshooting

SymptomCause / fix
401 UnauthorizedThe token is missing or invalid.
403 ForbiddenThe token is not a full service-account API token (e.g. a mobile/embed token, or a narrow token of the service account), or the subscription is inactive / the api feature is off.
A tool returns "not found" for an id that existsThe id belongs to a different company — tenancy is enforced on every argument.
"Rate limit exceeded"A per-tool limit was exceeded (see "Limits & behaviour") — retry shortly.