# Create a tool

Create a validated webhook or Google Sheets tool for a workspace.

## Endpoint

`POST /api/v1/workspaces/{workspace_id}/tools`

## Authentication

| Class | Authentication | Minimum role | API key |
| --- | --- | --- | --- |
| dashboard-session-api | Dashboard session cookie; mutations also require X-CX-Request: 1. | admin | Not allowed |

## Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `workspace_id` | path | `string` | Yes | — |

## Request body

### application/json request schema

| Field path | Type | Reference | Format | Nullable | Required | Enum | Description |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `$` | `object` | PermintaanWebhook or PermintaanGoogleSheets | — | No | Yes | — | — |

Discriminator: `type`.

#### Permintaan Webhook variant (`type = "webhook"`)

| Field path | Type | Reference | Format | Nullable | Required | Enum | Description |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `$` | `object` | PermintaanWebhook | — | No | Yes | — | — |
| `config` | `object` | KonfigWebhook | — | No | Yes | — | — |
| `config.assignments` | `array<Assignment>` | — | — | No | No | — | — |
| `config.assignments[]` | `object` | Assignment | — | No | Yes | — | Petik satu nilai dari balasan tool dan simpan sebagai variabel percakapan. Nama field ikut ElevenLabs (`DynamicVariableAssignment`). `source` mereka cuma punya satu nilai (`response`), jadi tidak ikut disalin — field dengan satu nilai yang mungkin adalah field yang tidak perlu ada. |
| `config.assignments[].dynamic_variable` | `string` | — | — | No | Yes | — | — |
| `config.assignments[].value_path` | `string` | — | — | No | Yes | — | — |
| `config.auth` | `object or null` | Auth | — | Yes | No | — | Rahasianya tidak pernah ditulis di sini — yang disimpan NAMA secret. Brankasnya sudah ada (`core/secrets.py`, AES-256-GCM per workspace), jadi tidak ada sumber daya "auth connection" baru seperti punya ElevenLabs. |
| `config.auth.body_name` | `string or null` | — | — | Yes | No | — | — |
| `config.auth.header_name` | `string or null` | — | — | Yes | No | — | — |
| `config.auth.secret_name` | `string` | — | — | No | Yes | — | — |
| `config.auth.type` | `string` | — | — | No | Yes | "bearer", "basic", "header", "body" | — |
| `config.body_fields` | `object` | — | — | No | No | — | — |
| `config.method` | `string` | — | — | No | No | "GET", "POST", "PUT", "PATCH", "DELETE" | — |
| `config.parameters` | `object` | — | — | No | No | — | — |
| `config.request_headers` | `object` | — | — | No | No | — | — |
| `config.response_mocks` | `array<MockResponse>` | — | — | No | No | — | — |
| `config.response_mocks[]` | `object` | MockResponse | — | No | Yes | — | — |
| `config.response_mocks[].is_error` | `boolean` | — | — | No | No | — | — |
| `config.response_mocks[].mock_result` | `string` | — | — | No | Yes | — | — |
| `config.response_mocks[].parameter_conditions` | `array<ParameterCondition>` | — | — | No | No | — | — |
| `config.response_mocks[].parameter_conditions[]` | `object` | ParameterCondition | — | No | Yes | — | — |
| `config.response_mocks[].parameter_conditions[].eval` | `object` | AnythingEval or ExactEval or RegexEval or LlmEval | — | No | Yes | — | — |
| `config.response_mocks[].parameter_conditions[].path` | `string` | — | — | No | Yes | — | — |
| `config.timeout_secs` | `number` | — | — | No | No | — | — |
| `config.url` | `string` | — | — | No | Yes | — | — |
| `description` | `string` | — | — | No | Yes | — | — |
| `name` | `string` | — | — | No | Yes | — | — |
| `type` | `string` | — | — | No | No | "webhook" | — |

#### Permintaan Google Sheets variant (`type = "google_sheets"`)

| Field path | Type | Reference | Format | Nullable | Required | Enum | Description |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `$` | `object` | PermintaanGoogleSheets | — | No | Yes | — | — |
| `config` | `object` | KonfigGoogleRead or KonfigGoogleAppend | — | No | Yes | — | — |
| `description` | `string` | — | — | No | Yes | — | — |
| `name` | `string` | — | — | No | Yes | — | — |
| `type` | `string` | — | — | No | Yes | "google_sheets" | — |

## Example

```bash
curl --request POST 'https://operade.ai/api/v1/workspaces/ws_demo/tools' \
  --cookie 'cx_session=YOUR_SESSION_COOKIE' \
  --header 'X-CX-Request: 1' \
  --header 'Content-Type: application/json' \
  --data '{"type":"google_sheets","name":"find_customer","description":"Search one customer by ID.","config":{"config_version":1,"action":"read","grant_id":"gsgrant_example","sheet_id":0,"sheet_title":"Customers","range_a1":"\u0027Customers\u0027!A1:D100","search_columns":["customer_id"],"return_columns":["name","status"],"match":"exact","limit":1}}'
```

## Responses

| Status | Description | Content |
| --- | --- | --- |
| 4XX | Handled client error | application/json: `ErrorResponse` |
| 201 | Successful Response | application/json: `object` |

### 4XX application/json response schema

| Field path | Type | Reference | Format | Nullable | Required | Enum | Description |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `$` | `object` | ErrorResponse | — | No | Yes | — | — |
| `error` | `object` | ErrorItem | — | No | Yes | — | — |
| `error.code` | `string` | — | — | No | Yes | — | — |
| `error.details` | `object` | — | — | No | No | — | — |
| `error.message` | `string` | — | — | No | Yes | — | — |

### 201 application/json response schema

| Field path | Type | Reference | Format | Nullable | Required | Enum | Description |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `$` | `object` | WebhookTool or GoogleSheetsToolAdmin | — | No | Yes | — | — |

Discriminator: `type`.

#### Webhook Tool variant (`type = "webhook"`)

| Field path | Type | Reference | Format | Nullable | Required | Enum | Description |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `$` | `object` | WebhookTool | — | No | Yes | — | — |
| `config` | `object` | KonfigWebhook | — | No | Yes | — | — |
| `config.assignments` | `array<Assignment>` | — | — | No | No | — | — |
| `config.assignments[]` | `object` | Assignment | — | No | Yes | — | Petik satu nilai dari balasan tool dan simpan sebagai variabel percakapan. Nama field ikut ElevenLabs (`DynamicVariableAssignment`). `source` mereka cuma punya satu nilai (`response`), jadi tidak ikut disalin — field dengan satu nilai yang mungkin adalah field yang tidak perlu ada. |
| `config.assignments[].dynamic_variable` | `string` | — | — | No | Yes | — | — |
| `config.assignments[].value_path` | `string` | — | — | No | Yes | — | — |
| `config.auth` | `object or null` | Auth | — | Yes | No | — | Rahasianya tidak pernah ditulis di sini — yang disimpan NAMA secret. Brankasnya sudah ada (`core/secrets.py`, AES-256-GCM per workspace), jadi tidak ada sumber daya "auth connection" baru seperti punya ElevenLabs. |
| `config.auth.body_name` | `string or null` | — | — | Yes | No | — | — |
| `config.auth.header_name` | `string or null` | — | — | Yes | No | — | — |
| `config.auth.secret_name` | `string` | — | — | No | Yes | — | — |
| `config.auth.type` | `string` | — | — | No | Yes | "bearer", "basic", "header", "body" | — |
| `config.body_fields` | `object` | — | — | No | No | — | — |
| `config.method` | `string` | — | — | No | No | "GET", "POST", "PUT", "PATCH", "DELETE" | — |
| `config.parameters` | `object` | — | — | No | No | — | — |
| `config.request_headers` | `object` | — | — | No | No | — | — |
| `config.response_mocks` | `array<MockResponse>` | — | — | No | No | — | — |
| `config.response_mocks[]` | `object` | MockResponse | — | No | Yes | — | — |
| `config.response_mocks[].is_error` | `boolean` | — | — | No | No | — | — |
| `config.response_mocks[].mock_result` | `string` | — | — | No | Yes | — | — |
| `config.response_mocks[].parameter_conditions` | `array<ParameterCondition>` | — | — | No | No | — | — |
| `config.response_mocks[].parameter_conditions[]` | `object` | ParameterCondition | — | No | Yes | — | — |
| `config.response_mocks[].parameter_conditions[].eval` | `object` | AnythingEval or ExactEval or RegexEval or LlmEval | — | No | Yes | — | — |
| `config.response_mocks[].parameter_conditions[].path` | `string` | — | — | No | Yes | — | — |
| `config.timeout_secs` | `number` | — | — | No | No | — | — |
| `config.url` | `string` | — | — | No | Yes | — | — |
| `created_at` | `string` | — | date-time | No | Yes | — | — |
| `created_by` | `string` | — | — | No | Yes | — | — |
| `description` | `string` | — | — | No | Yes | — | — |
| `id` | `string` | — | — | No | Yes | — | — |
| `name` | `string` | — | — | No | Yes | — | — |
| `type` | `string` | — | — | No | Yes | "webhook" | — |
| `updated_at` | `string` | — | date-time | No | Yes | — | — |

#### Google Sheets Tool Admin variant (`type = "google_sheets"`)

| Field path | Type | Reference | Format | Nullable | Required | Enum | Description |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `$` | `object` | GoogleSheetsToolAdmin | — | No | Yes | — | — |
| `capability_locked_at` | `string or null` | — | date-time | Yes | Yes | — | — |
| `capability_summary` | `string` | — | — | No | Yes | — | — |
| `config` | `object` | KonfigGoogleRead or KonfigGoogleAppend | — | No | Yes | — | — |
| `created_at` | `string` | — | date-time | No | Yes | — | — |
| `created_by` | `string` | — | — | No | Yes | — | — |
| `description` | `string` | — | — | No | Yes | — | — |
| `id` | `string` | — | — | No | Yes | — | — |
| `name` | `string` | — | — | No | Yes | — | — |
| `status` | `string` | — | — | No | Yes | "ready", "unavailable" | — |
| `type` | `string` | — | — | No | Yes | "google_sheets" | — |
| `updated_at` | `string` | — | date-time | No | Yes | — | — |

### Response example

```json
{
  "id": "string",
  "name": "string",
  "description": "string",
  "type": "webhook",
  "config": {
    "url": "string"
  },
  "created_by": "string",
  "created_at": "2026-01-01T00:00:00Z",
  "updated_at": "2026-01-01T00:00:00Z"
}
```

## Errors

Reviewed common and route-declared errors are summarized here; the list is not exhaustive. Start with the [distributed error index](/docs/reference/api/errors) to find each code's canonical domain page.

| Status | Code | Description |
| --- | --- | --- |
| 401 | `unauthenticated` | Authentication is missing, invalid, or revoked. |
| 403 | `forbidden` | The authenticated caller does not have access to this operation. |
| 404 | `not_found` | The requested workspace resource was not found. |
| 422 | `validation_failed` | The request did not pass field or business validation. |

## Related operations

- [Clone a tool](/docs/api-reference/tools/clone-tool) — `POST /api/v1/workspaces/{workspace_id}/tools/{tool_id}/clone`
- [Delete a tool](/docs/api-reference/tools/delete-tool) — `DELETE /api/v1/workspaces/{workspace_id}/tools/{tool_id}`
- [Get a tool](/docs/api-reference/tools/get-tool) — `GET /api/v1/workspaces/{workspace_id}/tools/{tool_id}`
