Custom functions
Read and replace the functions an agent can call on your API during a conversation.
A custom function lets an agent call your HTTPS endpoint during a conversation and use the result. An agent can have up to 8. For a step-by-step setup, see Give agents custom functions.
A missing or invalid key returns 401. :id must be the agent's UUID, or the route returns 400. An agent in another account returns 404.
A body or :id that fails validation returns 400 with the code VALIDATION_ERROR, and details.issues lists the problems. A rejected address or header returns the same code with the reason in error. See Errors.
The save route accepts a body up to 2 MB. Every other route has a 256 KB limit.
A key with write, delete or admin permission qualifies. A key whose owner has the viewer role gets 403 on both routes, whatever its permissions.
| Endpoint | Request |
|---|---|
| Get custom functions | GET/v1/agents/:id/custom-tools |
| Replace custom functions | PUT/v1/agents/:id/custom-tools |
The custom functions object
Attributes
versionstring | nullrequirednull until the first save. Send it as expectedVersion on your next save.syncStatusstringrequiredtoolsarrayrequiredThe function object
All fields are required when you save, and other fields are rejected.
Attributes
idstringrequirednamestringrequireddescriptionstringrequiredurlstringrequiredmethodstringrequiredGET, POST, PUT, PATCH or DELETE.timeoutSecondsintegerrequiredmaxRetriesintegerrequiredGET.headersarrayrequiredqueryParametersarrayrequiredname of 1 to 128 characters and a value of up to 4096. Names must be unique.fieldsarrayrequiredmessagestringrequiredenabledbooleanrequiredfields[]
namestringrequiredconstructor and prototype are reserved.typestringrequiredstring, number, integer or boolean.descriptionstringrequiredrequiredbooleanrequiredFor GET functions, a query parameter can't share a name with a field.
Header values
Header values are encrypted when saved and never returned. Responses show each stored header as { "name": "Authorization", "value": null, "hasValue": true }.
| To | Send |
|---|---|
| Add a header or change its value | { "name": "Authorization", "value": "Bearer YOUR_TOKEN" } |
| Keep a stored value | { "name": "Authorization", "value": null } |
| Remove a header | Leave it out of the list. |
Header names must be valid HTTP header names of up to 128 characters. They are unique regardless of case, and matched to stored values the same way. Values must be 1 to 4096 characters and can't contain line breaks or null characters.
Changing a function's endpoint to a different host requires a new value for every header. This stops stored credentials going to another service.
You can't set these headers, because Synthesys manages them: Host, Content-Length, Connection, Transfer-Encoding, Content-Type, Accept-Encoding, Cookie, Set-Cookie, Proxy-Authorization, Idempotency-Key and anything starting with X-Synthesys-. Values you save for Accept and User-Agent are replaced when the request is sent.
Get custom functions
/v1/agents/:id/custom-toolswrite keyReturns the agent's functions with header values masked. An agent with no saved functions returns version: null, syncStatus: "synced" and an empty tools list.
| Status | When |
|---|---|
| 403 | The key lacks write permission, or its owner has the viewer role. |
| 404 | The agent doesn't exist or belongs to another account. |
curl https://backend.synthesys.app/api/v1/agents/3fa85f64-5717-4562-b3fc-2c963f66afa6/custom-tools \
-H "SYNTH_API_KEY: YOUR_API_KEY"{
"success": true,
"data": {
"version": "8c4e2a17-6f93-4b05-a2d8-1e7c9b3f5d60",
"syncStatus": "synced",
"tools": [
{
"id": "7f3c1e52-9a4b-4d6e-8c21-5b0e7d3a1f64",
"name": "check_order_status",
"description": "Look up an order's delivery status when the caller asks about an order.",
"url": "https://example.com/api/orders/status",
"method": "GET",
"timeoutSeconds": 10,
"maxRetries": 1,
"headers": [
{ "name": "Authorization", "value": null, "hasValue": true }
],
"queryParameters": [
{ "name": "source", "value": "voice-agent" }
],
"fields": [
{
"name": "order_number",
"type": "string",
"description": "The order number the caller gives.",
"required": true
}
],
"message": "One moment while I check that order.",
"enabled": true
}
]
}
}Replace custom functions
/v1/agents/:id/custom-toolswrite keyReplaces the agent's whole list and applies it to the agent. Any function you leave out is removed, and an empty tools list removes them all. Returns the same shape as Get custom functions.
Body
expectedVersionstring | nullrequirednull on the first save. After that, the version from your latest response.toolsarrayrequiredThe address of every function must pass the format rules. Enabled functions with a new address, and functions you enable, must also resolve only to public internet addresses.
| Status | When |
|---|---|
| 400 | The body is invalid, an address fails its checks, a new header has value: null, or a host changed while a header has value: null. |
| 403 | The key lacks write permission, or its owner has the viewer role. |
| 404 | The agent doesn't exist or belongs to another account. |
| 409 | expectedVersion is out of date. Read the list again and retry. |
curl -X PUT https://backend.synthesys.app/api/v1/agents/3fa85f64-5717-4562-b3fc-2c963f66afa6/custom-tools \
-H "SYNTH_API_KEY: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"expectedVersion": null,
"tools": [
{
"id": "7f3c1e52-9a4b-4d6e-8c21-5b0e7d3a1f64",
"name": "check_order_status",
"description": "Look up the delivery status when the caller asks about an order.",
"url": "https://example.com/api/orders/status",
"method": "GET",
"timeoutSeconds": 10,
"maxRetries": 1,
"headers": [{ "name": "Authorization", "value": "Bearer YOUR_TOKEN" }],
"queryParameters": [{ "name": "source", "value": "voice-agent" }],
"fields": [
{
"name": "order_number",
"type": "string",
"description": "The order number the caller gives.",
"required": true
}
],
"message": "One moment while I check that order.",
"enabled": true
}
]
}'{
"success": true,
"data": {
"version": "8c4e2a17-6f93-4b05-a2d8-1e7c9b3f5d60",
"syncStatus": "synced",
"tools": [
{
"id": "7f3c1e52-9a4b-4d6e-8c21-5b0e7d3a1f64",
"name": "check_order_status",
"description": "Look up the delivery status when the caller asks about an order.",
"url": "https://example.com/api/orders/status",
"method": "GET",
"timeoutSeconds": 10,
"maxRetries": 1,
"headers": [
{ "name": "Authorization", "value": null, "hasValue": true }
],
"queryParameters": [
{ "name": "source", "value": "voice-agent" }
],
"fields": [
{
"name": "order_number",
"type": "string",
"description": "The order number the caller gives.",
"required": true
}
],
"message": "One moment while I check that order.",
"enabled": true
}
]
}
}{
"success": false,
"error": "Tools changed. Reload before saving.",
"code": "CONFLICT",
"requestId": "0f6b2c9e-1d47-4a83-b5e2-7c3a9d1f4e60",
"timestamp": "2026-10-31T12:00:00.000Z"
}Sync status
A save applies the list to the agent in the same request, so there is no separate sync route. If that step fails, the save is kept, the route still returns 200, and syncStatus is pending.
While it is pending, all of the agent's custom functions fail on calls. Check syncStatus after every save. To retry, send the same list again with the latest version, using value: null for stored headers.
What your endpoint receives
When the agent uses a function, Synthesys checks its arguments against fields. Unknown fields, missing required fields and wrong types are rejected without calling your endpoint. Arguments are limited to 16 KB.
| Method | Where arguments go |
|---|---|
GET | Added to the query string, with queryParameters. |
POST, PUT, PATCH, DELETE | Sent as a JSON body. queryParameters are added to the address. |
Requests include your saved headers, plus these:
| Header | Value |
|---|---|
Idempotency-Key | A hash unique to this function call. It stays the same across retries. |
Content-Type | application/json |
Accept | application/json, text/plain |
Accept-Encoding | identity |
User-Agent | Synthesys-Custom-Tool/1.0 |
How your response is handled
Return a 2xx status with a JSON or plain text body of up to 16 KB. Don't compress the response. The agent receives the status and the parsed body.
Any other status counts as a failure, and the agent is told the status code but not the body. Redirects are not followed. A body that is too large, or a compressed response, also counts as a failure.
Requests must finish within timeoutSeconds, including retries. Only GET functions retry, up to maxRetries times, after a network failure or 5xx response. Other methods are never retried, because a write may already have taken effect.
Each attempt checks that the address still resolves only to public internet addresses.