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:
POST https://mcp.aisar.app/mcp/companyAuthentication 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:
Authorization: Bearer 123|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxTransport 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.
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
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)
{
"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.
| Tool | Purpose |
|---|---|
list_docs | List available documents |
list_doc_sections | A document's section headings |
get_doc | A whole document, or one section |
search_docs | Full-text search across the docs |
list_webhook_events | The platform's webhook event catalogue |
get_webhook_payload_spec | The exact payload spec for one event |
lookup_error_code | Explanation 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.
| Tool | Arguments |
|---|---|
get_platform_status | component? — narrow to one component's signal plus only the incidents/maintenances affecting it |
get_platform_status_history | component? — up to 90 days of history for one component instead of all |
list_platform_incidents | days? (default and max 90) |
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
| Tool | Arguments |
|---|---|
whoami | — |
list_channels | status?, type?, page, per_page |
list_funnels | — |
list_deals | funnel_id?, stage_id?, status?, search?, page |
get_deal | deal_id |
search_contacts | query, page |
get_contact | contact_id |
list_conversations | status?, channel_id?, assigned_to_me?, search?, page |
get_conversation | conversation_id |
search_messages | q, conversation_id? (omit to search the whole company), per_page? (max 50), page? — rate-limited |
list_templates | channel_id?, language?, sendable_only? |
list_broadcasts / get_broadcast | —, broadcast_id |
get_dashboard_summary | period? (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.
| Tool | Arguments |
|---|---|
send_message | conversation_id, text |
send_template_message | channel_id, contact_id\|conversation_id, template_id, variables? |
create_contact / update_contact | contact fields |
add_internal_note | conversation_id, text |
assign_conversation | conversation_id, user_id\|team_id |
close_conversation | conversation_id |
create_deal / update_deal | deal fields (stage_id must belong to the funnel) |
move_deal_stage | deal_id, stage_id |
create_deal_note | deal_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
| Tool | Arguments |
|---|---|
list_integrations | — |
create_webhook_integration | url, events[], signing_secret? |
test_webhook | integration_id\|url |
get_webhook_deliveries | integration_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.
| Tool | Arguments |
|---|---|
list_ai_agents | status? (active\|inactive\|suspended) |
get_ai_agent | instance_id |
create_ai_agent | name, 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_agent | instance_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_agent | instance_id |
list_knowledge_documents | instance_id? |
create_knowledge_document | instance_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_document | instance_id, document_id |
get_voice_agent | instance_id |
update_voice_agent | instance_id + voice fields (language, voice_id, greeting, follow-up, escalation, TTS tuning, voice_tools) — only the fields given are changed |
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_toolsis the full set of tools for the agent (values are checked against the registry, an unknown key is a 422).disabled_toolsis 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 toolshttp_request,block_contact,delete_messagesandsend_stickerare not shown in the cabinet and stay on until explicitly switched off viadisabled_toolsortools_config.<tool>.enabled = false. The catalog of all tools with metadata is served byGET /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}. Withregex: truethe 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,headersandquery(objects, up to 32 pairs),body_type(none,json,form,raw),body,auth(type:none,bearer,basic,api_key_header,api_key_query;secret_idmust be a secret of your own company),variables(up to 32 items{name, description?, required?}),response(extract_path,max_lengthup to 50000),timeout_ms(from 500 up to the 30,000 ceiling),retries(0–2),allowed_domains(up to 32; must be non-empty whenurlis set). Credentials must not be written inline intourl,headersorquery: a parameter name containingauthorization,token,key,secret,passwordorcookie, or a value starting withBearer,BasicorToken, returns 422 — keep the key in secrets and select it inauth.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) andassignment_method(team,round_robin,least_busy). - API key and fallback.
api_key_idmust be a key of your own company and of the same provider as the agent (provider), otherwise 422 onapi_key_id. For an agent on its own key, the fallback model is allowed only with the same provider: afallback_providerof another provider or anyfallback_tierreturns 422 (setfallback_modelof the same provider instead). - HTTP tool test and draft chat.
POST /v1/ai-agents/tools/http-testis a trial call of the configured request: limiterai-http-test(10 per minute per user and 60 per minute per company), retries are disabled (retriesis always 0), addresses in internal and service networks are rejected (private, reserved, loopback, CGNAT100.64.0.0/10, NAT64, 6to4);auth.secret_idmust be a secret of your own company.POST /v1/ai-agents/draft/chatis a chat with an agent draft: limiterai-sandbox(30 per minute per user and 120 per minute per company for session callers);api_key_idmust belong to the company and match the requestprovider, otherwise 422. - Knowledge base. A document in the response carries an
error_codefield (nulluntil a fetch fails). For a document withsource_type: "url"and nocontent, the page is downloaded in the background, and on failureerror_codetakes 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 returns429with aRetry-Afterheader; 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_aiandsplit_long_replies(boolean);signature(up to 120 characters);first_message(up to 1000 characters). Pacing and media:reply_delay(instant|default|human, defaultdefault),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 hasvision). The pre-existingschedule,respect_holidays,working_hours_modeandoperator_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 fromname,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,10or15(if no human answered within that time, anai_handoff_waitingnotification is sent).copilot.enabled(boolean) turns on "Suggest reply"; with the copilot off a suggestion request returns403withcode: copilot_disabled. The copilot's own monthly cap is the instance fieldcopilot_monthly_budget_cents(integer ≥ 0;null— the copilot spends the agent's budget); the response also carriescurrent_month_spent_centsandcopilot_month_spent_cents(read-only). - Channels, budget, review (v2).
channel_modes— an object{ "<channel_id>": mode }, where mode isalways,after_hoursorteam_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 responsechannel_modesis always an object.attach_to—{new, reopened, with_assignee}(boolean) andgroups(off|mentioned): which dialogs the agent joins on its own.on_budget_exhausted—handoff(default: hand the dialog to people) orsilent;budget_warning_pct— only80(once per agent per month anai_agent_budget_warningnotification is sent);fallback_tier— as before.review.enabled(boolean) turns on the nightly review of the agent (new agents on the platform key gettrueby 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) ordraft(edits accumulate in a draft and go live on publish). Only a user with theai_review.managepermission (administrator only) may changepublish_modeandreview.enabled: otherwise403withcode: 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 exactmodel) and returns it. The dictionary isGET /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_tieris a 422, and a catalogmodelof a different provider than the key is a 422 onmodel(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 asnapshotfield (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 theai_review.managepermission (otherwise 403), limited to 30 per minute:POST …/publish(optionalforce: true; with no draft — 422 withcode: no_draft; publishing also shares theai-tests-runlimiter),DELETE …/draft(discard the draft),POST …/versions/{n}/restore(creates a new version from the chosen one). The instance carriespublished_version,has_draft, and inpublish_mode: draftalsodraft_configanddraft_updated_at. A foreign instance or version is a 404. - Uploading and editing documents.
POST /v1/ai-agents/instances/{i}/documents/upload(multipart, fieldfile, 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-DDornull),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: trueis allowed only for documents of kindfaq,note,company_profile,text— otherwise 422. Creation (POST …/documents) gainedkind(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_scheduleandfetch_exclude. Ceilings: 200 documents and 20 sites per agent, 5,000,000 characters per company (otherwise 422). A document in the response carrieskind(filefor an upload),origin,valid_until,mime,size_bytes,last_fetched_at,fetch_schedule,fetch_exclude,pages_count,content_chars;contentitself is returned for text kinds only. - New document `error_code` values. Added to the ones above:
robots_disallowed(the site forbids crawling inrobots.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.updatedonprivate-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— additionallyfallback_action(queue|close|auto_reply: what to do when nobody in the target team is online;auto_replysends the customerfallback_message, leaving the dialog open and out of the queue) andfallback_message(up to 1000 characters; falls back tohandoff_messagewhen 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,30or60),horizon_days(1–60),min_lead_minutes(0–1440),services(up to 50 objects{name, duration_minutes}, duration 5–720).find_free_slots(argumentsservice?,date_from?,date_to?,assignee_hint?) returns the specialists' free windows;book_appointment(slot_startas ISO-8601 with an offset andassignee_idexactly as listed byfind_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 showssource: aiandduration_minutes(see the "Deals" group). - AI employees dashboard (v2).
GET /v1/ai-agents/dashboard?days=7|30|90(AI agent view permission;days1 to 90, default 7; days are calendar days in the company timezone, today included) andGET /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:valueandprevious(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 foranswered,after_hours,resolved_without_humans,booked,saved_minutes(nullwhen the earlier value was 0);quality_weekly[]per ISO week -{week_start, enquiries, resolved_share, accuracy, csat, fixes}(shares in % ornull;fixesis 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_amountsis an array[{currency, amount_cents}]per currency (the currency can benull),deal_amount_centsis the sum only when there is a single currency, otherwisenull. 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 hasattention[]({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) andagents_total.overviewhas insteadcalls,bookings_by_type,rating {share, answered}andrecent[]. 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 = trueand 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 ofqueued|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}};PUTon the same path (ai_review.manage, administrator only, full token) accepts any of the three boolean fields and returns the same object;platform_enabled=falseis 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) returns202 {"data": {run_id, run_date, status: "queued"}}. Limit: 3 runs per day per agent and 30 per day per company, otherwise429withcode: too_many_attemptsand aRetry-Afterheader. Refusals:422with acodesaying why the agent is not reviewed (review_off,not_business,not_allowlisted,byok_agent,agent_off,company_off);409isdaily_budget_exhaustedorcompany_share_exhausted(today's review budget is spent) oralready_running(a run is already queued or in progress); another company's agent is404. 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}}:countsis the number of issues per status (honouringinstance_id/severity/typebut notstatus),metais{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:reopenedfirst, then severity, number of dialogs and recency.GET /v1/ai-agents/issues/{id}is the card withexamples,simulationandevents; another company's issue is404. 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 anonymisedtitleand counters:texts_visible=false,examples_hidden=true,summary,fix_payload,reject_reasonandsimulationarenull, and examples arrive as{visible: false}. For aneeds_inputissue whose tool has to be enabled by hand,action_hint: "enable_tool_manually"andtool_keyare returned. Decisions (ai_review.manage, full token, 60 per minute) each return the issue card:POST /issues/{id}/acceptapplies the fix (sendacknowledge_customer_content: truewhen the fix may contain customer text, otherwise422 customer_content_ack_required);PATCH /issues/{id}/fix(fix_payloadrequired,fix_typeoptional) edits the fix;POST /issues/{id}/reject(reason, 1-500 characters);POST /issues/{id}/answer(text, 1-4000 characters; forneeds_input);POST /issues/{id}/rollbackundoes an applied fix;POST /issues/{id}/propose(permissionai_agent.update) suggests a decision to administrators and sends them anai_review_proposednotification.POST /issues/bulk-acceptwithids(1-50) accepts in bulk only knowledge-base answers without customer text and returns{results: [{id, accepted, code?, message?, http_status?}], accepted, failed}:200if at least one was accepted, otherwise422. Refusal codes{"data": {message, code}}:409wrong_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;422bulk_not_allowed,customer_content,customer_content_ack_required. - Review on the dashboard.
GET /v1/ai-agents/dashboardandGET /v1/ai-agents/instances/{i}/overviewread 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) andlast_review, the latest run{run_date, finished_at, status, error_code, conversations_checked, issues_found}ornull. Rows ofagents[]on the dashboard (andGET /v1/ai-agents/dashboard/agents) gainawaiting_issues(a number) andaccuracy(% of answers without errors in the latest week that has checked answers, ornull). - Sandbox "Ask as a customer" (v2).
POST /v1/ai-agents/instances/{i}/sandbox/chat(AI agent update permission; limiterai-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(publishedordraft, defaultpublished),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 inwould_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),502with 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-chatnow returns the same codes (422, fieldcode) 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 forrule). Response:{target, document_id}forfaq(the knowledge document is live at once, documents are not versioned),{target, rule_id, draft, published_version}forrule(goes through the regular update: in draft mode the rule lands in the draft; at most 30 rules, otherwise 422 onexpected),{target, test_id}fortest. - 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(questionup to 1000,expectedup to 2000 - the criterion of a correct answer,channel_type?; 201),PATCH .../tests/{test},DELETE .../tests/{test}, going over 100 is a 422 onquestion;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; withoutexpectedthe masked text of the answer itself becomes the criterion; 422 codes incode: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?-publishedordraft, by default the draft when there is one; limiterai-tests-run, 6 per hour per agent) starts a background run and answers202with the run object; 422no_tests, 409run_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 ispassed,failedorerror(the cause is inreason, for examplebudget_exceeded,sandbox_daily_budget_exceeded,timeoutor a provider code). The end of a run arrives as the Reverb event.ai.tests.finished. Publish gate: when tests exist,POST .../versions/publishrequires a finished run for exactly this draft; with no suitable run the server starts one itself and answers409withcode: "tests_running", and the publish must be repeated after it finishes; if the run has failures it answers409withcode: "tests_failed". Both responses carryrun_id, total, passed, failed. To publish despite failures passforce: 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(goodorbad, 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).DELETEon the same path removes the rating:{message_id, verdict: null}. Messages in the conversation list and card carrymy_ai_rating(good,badornull) - 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;days1-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).
| Tool | Arguments |
|---|---|
create_external_action | name, 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_action | id + 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_actions | status? (draft\|active\|disabled) |
get_external_action | id |
delete_external_action | id (soft delete — bindings to agents are removed, call history is preserved) |
bind_external_action | instance_id, external_action_id, enabled? (default true), hint?, confirmation_required? (chat only) |
unbind_external_action | instance_id, external_action_id |
test_external_action | id, parameters? (sample values; context bindings get sample stand-ins — see the callout below) |
create_ai_agent_secret | name, 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_secret | id, 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_secret | id (refuses if the secret is still used by any action — their slugs are listed in the error text) |
list_external_action_calls | action_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.
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".
| Tool | Arguments and response |
|---|---|
get_call_scheme | scheme_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_scheme | scheme_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_channels | scheme_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_messageandsend_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_schemeandset_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_pageis 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
| Symptom | Cause / fix |
|---|---|
401 Unauthorized | The token is missing or invalid. |
403 Forbidden | The 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 exists | The 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. |