> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nedzo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom actions

> Create custom actions that call your own endpoints, test them, and attach them to agents.

<Note>
  These endpoints are for the VIP plan only. Other plans get a `403`. See [Plan access](/api-reference/introduction#plan-access).
</Note>

A custom action calls your endpoint during a conversation. It belongs to the workspace, so one action can serve many agents. See [Actions](/agents/actions) for the request your endpoint gets.

| Method | Path | What it does |
| - | - | - |
| `GET` | `/v1/custom-actions` | List the workspace's custom actions |
| `POST` | `/v1/custom-actions` | Create one. Returns `201`. |
| `GET` | `/v1/custom-actions/{id}` | Get one |
| `PATCH` | `/v1/custom-actions/{id}` | Update one |
| `DELETE` | `/v1/custom-actions/{id}` | Delete one. Every agent stops using it. Returns `204`. |
| `POST` | `/v1/custom-actions/{id}/test` | Call the endpoint once and see what it answers |
| `GET` | `/v1/agents/{agentId}/custom-actions` | List the custom actions an agent uses |
| `POST` | `/v1/agents/{agentId}/custom-actions` | Attach one: send `{ "customActionId": "..." }`. Returns `201`. |
| `DELETE` | `/v1/agents/{agentId}/custom-actions/{customActionId}` | Detach one. The action stays in the workspace. Returns `204`. |

An account API key sends `workspaceId` in the body on create, and in the query on list.

## Fields

<ParamField body="name" type="string" required>The action's name.</ParamField>
<ParamField body="condition" type="string">When the agent should use it.</ParamField>
<ParamField body="method" type="string" required>`GET`, `POST`, `PUT`, `PATCH` or `DELETE`.</ParamField>
<ParamField body="url" type="string" required>Your endpoint. It must be a public address. We refuse private and internal addresses.</ParamField>
<ParamField body="parameters" type="object[]">What the agent fills in from the conversation. Each has `name`, `description`, `type` (`string`, `number` or `boolean`) and `required`.</ParamField>
<ParamField body="isActive" type="boolean">Whether agents can use it.</ParamField>
<ParamField body="headers" type="object">Headers to send with every call. On update this replaces all headers. A name may only use letters, digits and `!#$%&'*+-.^_`|\~`, a value can't hold a line break, and you can't set `Host`, `Content-Length`, `Transfer-Encoding`or`Connection`.</ParamField> <ParamField body="authType" type="string">`bearer`, `api\_key`or`basic`. Send `null`on update to remove the auth.</ParamField> <ParamField body="authValue" type="string">The secret for`authType`. Needed when you set `authType\` for the first time.</ParamField>

<Warning>
  `authValue` and header values are write only. We never send them back. A response shows `hasAuth` and the header names.
</Warning>

If you change `url` to another address (a different host, http or https, or port), send `authValue` and `headers` again in the same request, or set `authType` to `null` and `headers` to `{}` to remove them. The saved ones stay with the old address, and we refuse the change with a `400`.

```json Response theme={null}
{
  "id": "5a3c1e2b-8d7f-4b6a-9c0e-1f2a3b4c5d6e",
  "workspaceId": "789e4567-e89b-12d3-a456-426614174000",
  "name": "Check zip code",
  "condition": "The caller gives a zip code",
  "method": "POST",
  "url": "https://example.com/coverage",
  "parameters": [{ "name": "zip", "type": "string", "required": true }],
  "isActive": true,
  "authType": "bearer",
  "hasAuth": true,
  "headerNames": ["X-Team"],
  "createdAt": "2026-10-11T10:00:00Z",
  "updatedAt": "2026-10-11T10:00:00Z"
}
```

## Test a custom action

`POST /v1/custom-actions/{id}/test`

<ParamField body="arguments" type="object">The values the agent would fill in, like `{ "zip": "94107" }`.</ParamField>

We call your endpoint once with the saved URL, headers and auth, the same way a live call would. We wait up to 30 seconds and follow up to 5 redirects. A redirect to a private or internal address is refused. When a redirect goes to another address (a different host, http or https, or port), we do not send your auth there. Your other headers go with it. You can test 10 times a minute per workspace.

```json Response theme={null}
{
  "success": true,
  "message": "Endpoint responded with 200 OK",
  "status": 200,
  "statusText": "OK",
  "body": "{\"covered\":true}",
  "durationMs": 182,
  "error": null
}
```

`body` holds up to 4,000 characters of the answer. When the call fails, `success` is `false` and `error` says why.

## Errors

| Status | When |
| - | - |
| `400` | A field is missing or wrong, the URL is not a public address, `authType` came without `authValue`, a header is not allowed, or `url` moved to another address without the auth and headers. |
| `404` | The action or agent is not in your API key's scope, or the action is in another workspace than the agent. |
| `409` | The action is already attached to the agent. |
| `429` | More than 10 tests in a minute. Wait a minute. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.