Use the OpenAPI contract to generate an integration client. The public API, dashboard and MCP management endpoint operate on the same workspace resources and permissions.
| Capability | Endpoint |
|---|---|
| Reusable tools | GET / POST /api/v1/voice/tools |
| Read / update / delete a tool | GET / PATCH / DELETE /api/v1/voice/tools/{id} |
| Preview or test a tool | POST /api/v1/voice/tools/{id}/test |
| Bind tools to an Agent | PUT /api/v1/voice/agents/{id}/tools |
| Knowledge bases | GET / POST /api/v1/voice/knowledge-bases |
| Read / update / delete a knowledge base | GET / PATCH / DELETE /api/v1/voice/knowledge-bases/{id} |
| Add / list documents | POST / GET /api/v1/voice/knowledge-bases/{id}/documents |
| Read / delete a document | GET / DELETE /api/v1/voice/knowledge-bases/{id}/documents/{doc_id} |
| Call-event subscriptions | GET / POST /api/v1/voice/webhooks |
| Read / update / delete a subscription | GET / PATCH / DELETE /api/v1/voice/webhooks/{id} |
| Send a signed test event | POST /api/v1/voice/webhooks/{id}/test |
| Discover remote MCP tools | POST /api/v1/voice/mcp/discover |
| MCP management endpoint | /api/v1/voice/mcp |
Register an HTTP tool that queries your CRM adapter:
{
"name": "lookup_customer",
"description": "Find a customer after confirming their customer ID.",
"kind": "http",
"url": "https://crm.example.com/customers/{{customer_id}}",
"method": "GET",
"read_only": true,
"credential_ref": "crm",
"input_schema": {
"type": "object",
"properties": { "customer_id": { "type": "string" } },
"required": ["customer_id"],
"additionalProperties": false
},
"response_path": "data"
}
POST /tools returns { "data": { "id": "TOOL_ID", ... } }. Bind its definition to an Agent with PUT /agents/AGENT_ID/tools and { "tool_ids": ["TOOL_ID"] }. This replaces the entire tool list with snapshots; rebind when a library tool changes. Deleting a library definition keeps existing snapshots. You can also read and replace inline definitions with the Agent API's tools field. Use analysis_config and widget_config to configure analysis and the widget through the Agent API.
A tool can use GET, POST, PUT, PATCH or DELETE. query and request_body accept nested JSON templates such as { "customer": { "id": "{{customer_id}}" } }. An entire placeholder preserves the original JSON type. URL placeholders are encoded and may only appear after a fixed public HTTPS host. Without request_body, non-GET tools receive { "arguments": {...}, "call": { "callId": "...", "agentId": "...", "toolCallId": "...", "idempotencyKey": "..." } }. response_path selects a field using dot notation. Simple parameters are still accepted; input_schema additionally supports nested objects, arrays, enums and numeric bounds. External schema references and regular expressions are not accepted.
static_args: values sent with every call whatever the model writes, such as a store id; they also fill {{name}} in the address and body.extract: [{name, path}] keeps fields of a function's answer (data.id) for later functions' {{name}}, so a lookup's customer_id can go into the order that follows without the model repeating it. A start lookup's fields are kept the same way.reject_when: up to 5 rules {on: caller|agent, pattern, negate}; while all hold of the last words, the function is not run and the agent gets reject_message.model_says: with each call the model writes one sentence that is said while the function runs, instead of the fixed speak_before.Connect an app under Integrations → Webhooks & notifications → Connected apps, then use "Add a ready-made function" on the agent's Knowledge & tools tab; no endpoint of your own is needed:
check_availability (free times on a date) and book_appointment (books the time the caller chose). Google Calendar times follow the time zone, open days, hours and appointment length you set; Cal.com uses the event type's own availability, and a fallback email you set when the caller gives none.save_to_sheet, which appends a row with the columns you list, plus the call's ID and time.notify_team, which posts a message the agent writes to a Slack channel (incoming webhook).The EvaSay control plane runs these functions with the account you connected; neither the worker nor the agent definition holds the app's credentials. Reading an agent through the API shows them as kind: "app" (with app, action, connection_id, app_config); send them back unchanged when replacing tools to keep them. Apps are connected on the dashboard only.
Set run_at_start: true on one of an agent's tools and it is called as each call starts, before the agent greets or answers. Parameters named caller_number, called_number, direction, channel or call_id are filled in for it. The fields it returns fill {{variables}} in first_message, system_prompt and idle_messages (fields one object down as {{customer.name}}), and the whole answer is given to the agent as background. A greeting with variables waits up to 3 seconds for the answer; anything not found is left blank. One tool per agent can run at the start.
Credentials are configured in the server environment for your workspace and destination; ask support@evasay.com to provision a credential_ref. Do not put API keys in Agent definitions, headers, prompts or tool parameters. Your adapter can keep CRM credentials in its own environment.
Preview a tool before making requests to the business system:
{
"arguments": { "customer_id": "customer-123" },
"dry_run": true,
"mock_result": { "name": "Alex", "order_status": "shipped" }
}
A preview validates parameters and shows the mapped request; it does not run a conversation or contact the business system. Set dry_run=false for a real execution. A live write test also requires confirmed=true and idempotency_key (8–128 letters, numbers, dots, underscores, colons or hyphens). Test responses include ok, status, duration_ms and a bounded result. They do not return credentials. Read and write tests require workspace write permission.
During a call, business requests carry a stable Idempotency-Key. Your business endpoint must honor it. Identical writes within a call are deduplicated, including operations with unknown outcomes after a timeout. A different intentional operation needs a distinct business argument, such as operation_id. EvaSay does not automatically replay writes. Tool execution events include status and duration; the conversation test feature remains a text simulation, not a tool-execution test.
POST /mcp/discover accepts { "url": "https://crm.example.com/mcp", "credential_ref": "crm" } and returns the server's tools and schemas. Register only the selected tool:
{
"name": "lookup_customer",
"description": "Find a CRM customer by confirmed ID.",
"kind": "mcp",
"url": "https://crm.example.com/mcp",
"mcp_tool": "get_customer",
"credential_ref": "crm",
"read_only": true,
"input_schema": {
"type": "object",
"properties": { "customer_id": { "type": "string" } },
"required": ["customer_id"]
}
}
Only registered tools become available to the Agent. Remote connections support Streamable HTTP with JSON or SSE responses to requests, public HTTPS destinations, and preconfigured credentials. Legacy SSE endpoints, OAuth negotiation, server-initiated sampling/elicitation and binary attachments are not supported. Discovery does not automatically grant access to all server tools. The remote tool schema must be inline and use the supported JSON Schema subset.