How it works
Your endpoint speaks the standard OpenAI chat completions protocol. The platform sendsPOST {base_url}/chat/completions with stream: true and reads the reply as server-sent events, so if your prototype already runs against another voice-agent platform through a custom LLM, the same server works here unchanged.
Each conversation turn, your endpoint receives the same fully assembled context a platform model would see. The messages array carries the system prompt with dynamic variables and session overrides applied, the complete conversation history, and any retrieved knowledge, and the agent’s tools are included in OpenAI function format. When your model returns tool_calls, the platform executes the tool and calls you again with the result. A few extra top-level fields ride each request body so your server can look up its own state:
Example request
Suppose the session was created from your backend withend_user_id: "user-42" and llm_extra_body: {"chat_id": "chat-9"}, the agent has one webhook tool lookup_order, and the user has just asked about an order. Your endpoint receives:
- The first
systemmessage is the assembled prompt: your agent’s system prompt with dynamic variables and overrides applied, plus the platform’s own context lines. A shortsystemline carrying the current time is inserted before the latest user turn on every request. messagescarries the full transcript so far. Speech recognition output arrives as plainusertext; your earlier replies come back asassistantmessages.toolsis present only when the agent has tools configured. Themodel,stream, andstream_optionsfields are fixed; notemperatureormax_tokensis sent, so apply your own defaults.- A tool whose parameters are validated arrives with
"strict": trueon itsfunction, and itsparametersin the shape OpenAI’s strict function calling requires: every property listed inrequired, the optional ones made nullable,additionalProperties: false, and keywords strict mode does not accept (minLength,maxLength,default) removed. Honor the flag if your model supports it, ignore it otherwise. The platform checks each call against the tool’s own schema before running it either way.
lookup_order tool call, the platform executes it and immediately sends the next request with the call and its result appended to messages, everything else unchanged:
delta.content chunks ending in finish_reason: "stop".
Requests authenticate with Authorization: Bearer <your API key>. If your endpoint needs other headers, add them with headers. The endpoint must use https on a publicly reachable host, and must support function calling if the agent has tools configured.
Point the agent at your endpoint
To set it up in the console:- Open the agent’s Configuration and click the Model selector.
- Choose + Custom LLM at the bottom of the menu.
- Enter the base URL, the model name and your API key.
- Click Run test. It sends one streaming turn with the agent’s prompt and tools, just like a live call.
- Click Save and use. The agent now uses your endpoint.
- Whether the endpoint was reached.
- Whether it streamed
delta.content. - Whether it accepted the tools.
- The time to first token.
llm.custom section on the agent’s configuration:
The console doesn’t show or edit headers, so set them through the API. Saving the endpoint in the console keeps them, and Run test sends them while the base URL matches the saved one.
Once published, every session of the agent generates on your endpoint, whether it starts from the web, a phone call, or a preview call in the Builder. Rotating the key or headers works the same way as any other config change. Patch the new values into the draft, then publish; sessions started after the publish use them. Send
{ "llm": { "custom": null } } to switch back to platform models.
Failure behavior
By default (llm.fallback at platform), a custom LLM never falls back to another model. A platform model answering with the wrong persona and no memory would be worse than a failed turn.
To opt in, set llm.fallback.mode to manual and list the models to try when your endpoint fails. Platform models and other endpoints both work. Your endpoint can also be a fallback entry for another agent. See LLM Fallback.
- Each request gets one retry and a 10 second response cap.
- When a generation still fails, the agent speaks a brief hold line and stays on the call.
- After three consecutive failed generations the agent apologizes, hangs up, and the session ends with
end_reason: llm_endpoint_failure(in the sessions API, the dashboard, and thecall.endedwebhook’sended_reason).
See what your endpoint returned
Every failed generation is recorded, whether or not the call ended because of it. You can find them in three places:- Conversations in the dashboard shows each failure in the transcript, where it happened.
- The sessions API returns them as
llm_endpoint_errors. - The
call.endedwebhook carries the same list.
For
http_error, response_body holds the start of your response body, up to 300 characters. Your API key is masked. connection_error and invalid_response also carry a reason, such as dns_failed or not_event_stream. The lines the agent spoke in place of a reply have fallback_reason: "llm_error".
Limitations
- Single Turn and Tool agent tests are not supported. A scripted test run refuses to execute rather than substitute a platform model for yours. Simulation tests run on your endpoint.
- The prompt-level safety guardrails still ride the assembled context, but your model decides whether to honor them.

