> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.v2.rasen.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.v2.rasen.ai/_mcp/server.

# Add a tool to the draft

POST https://api.v2.rasen.ai/v1/agents/{agent_id}/tools
Content-Type: application/json

Validates definition and authorized references. Does not invoke a webhook or remote MCP tool. All tools execute unattended after publication. Reserved names end\_call, transfer and transfer\_to\_number are refused with invalid\_request on every write/validation; use default\_tools. Secret headers pin an exact secret version; to adopt a rotated version, supply the new \{secret\_id, version} here and publish. An archived agent is refused with agent\_archived. tool\_name\_conflict: another tool in this draft has the same name. conflict with details.reason=name\_taken: the secret name of an inline credential is taken. resource\_key\_conflict: the resource\_key of an inline credential is taken. Inline credential material also requires secrets.manage, because it creates a secret; a \{secret\_id, version} reference also requires secrets.read.

Reference: https://docs.v2.rasen.ai/api-reference/agents/tools/create

## Authentication

- `Authorization` header (bearer token, required) — Create and manage workspace-owned keys through the existing app session routes, outside this public API. Creator and manager identities are audit actors only. Key validity follows workspace policy, expiry and explicit revocation, independently of creator membership. Do not include a workspace-selection header.

## Request

### Path parameters

- `agent_id` (string, required) — Resource identity within the authenticated workspace.

### Headers

- `If-Match` (string, required) — Strong ETag from the resource being changed. Draft/tool/variable/publication operations use the complete agent draft ETag; number operations use the number ETag; MCP update uses its own ETag. Never retry a stale edit by blindly using a fresh ETag. Agent identity edits use the identity ETag; knowledge-source reingest uses the source ETag. A stale revision returns 409: draft_conflict for agent, draft, variable, tool, MCP server and publication-token checks; conflict with details.reason=stale_revision for phone numbers and knowledge sources. A missing header returns 428 revision_required. Same-intent replay uses the original If-Match and returns its receipt before checking current revision.
- `Idempotency-Key` (string, required) — Stable key per logical intent, 1–255 printable characters. Persist it before sending. Retry the same key/body after an uncertain result for 24 hours. A changed body conflicts. Beyond retention, reconcile the resource before retrying; this is not permanent deduplication. Current authorization is rechecked on replay. Evaluation order: authenticate and check current authorization; look up intent; replay its stored receipt before checking the current revision; only a new intent evaluates If-Match and executes. Fingerprint includes method, path, body and original If-Match. A changed request returns idempotency_key_reused. The server stores only a keyed (HMAC) fingerprint, and no error or response ever returns a fingerprint or hash of a request. In-flight intent returns 409 idempotency_in_progress with Retry-After; retry the same intent after reconciliation. Receipts describe that attempt, not current resource state. A replay returns the original status code and body, with the response header Idempotent-Replayed: true.

### Body (application/json)

This endpoint expects a ToolWrite.

- `ToolWrite`
  - `kind`: `webhook` (webhook)
    - `definition` (WebhookDefinition, required) — Public HTTPS destination; platform egress policy rejects private/metadata addresses and unsafe redirects at write and execution. No credentials in URL.
    - `description` (string, required)
    - `name` (string, required)
    - `retry_posture` (enum, required)
      - Allowed values: `none`, `automatic`
    - `side_effect` (enum, required)
      - Allowed values: `none`, `idempotent`, `unknown`
    - `deadline_ms` (integer, optional, default: 8000)
    - `effect_id_name` (string, optional)
    - `effect_id_placement` (enum, optional)
      - Allowed values: `header`, `body`
    - `max_response_bytes` (integer, optional, default: 32768)
    - `wait_for_response` (boolean, optional, default: true)
  - `kind`: `mcp` (mcp)
    - `definition` (McpDefinition, required)
    - `description` (string, required)
    - `name` (string, required)
    - `retry_posture` (enum, required)
      - Allowed values: `none`, `automatic`
    - `side_effect` (enum, required)
      - Allowed values: `none`, `idempotent`, `unknown`
    - `deadline_ms` (integer, optional, default: 8000)
    - `max_response_bytes` (integer, optional, default: 32768)
    - `wait_for_response` (boolean, optional, default: true)

## Response

### 201

Add a tool to the draft.

- `id` (string, required) — Opaque tool identifier. Do not parse its contents.
- `agent_id` (string, required) — Opaque agent identifier. Do not parse its contents.
- `revision` (string, required)
- `configuration` (ToolConfigurationSafe, required)
- `created_at` (datetime, required)

## Errors

### 400 Bad Request Error

Invalid input or cursor. Correct the request.

- `error` (ErrorResponseError, required) — Only code and message are required. Rasen sends details, request_id and retry on every error, but clients must parse an error that lacks them, so a typed error is never lost to a parsing failure.

### 401 Unauthorized Error

Invalid, expired or revoked API key. Creator departure or membership changes do not invalidate a workspace key.

- `error` (ErrorResponseError, required) — Only code and message are required. Rasen sends details, request_id and retry on every error, but clients must parse an error that lacks them, so a typed error is never lost to a parsing failure.

### 403 Forbidden Error

Operation not allowed by current credential or workspace access.

- `error` (ErrorResponseError, required) — Only code and message are required. Rasen sends details, request_id and retry on every error, but clients must parse an error that lacks them, so a typed error is never lost to a parsing failure.

### 404 Not Found Error

Resource absent or not visible in this workspace.

- `error` (ErrorResponseError, required) — Only code and message are required. Rasen sends details, request_id and retry on every error, but clients must parse an error that lacks them, so a typed error is never lost to a parsing failure.

### 409 Conflict Error

Conflict. Not retryable: read the current state, fix the request and send a new request. The one exception is idempotency_in_progress, which carries Retry-After: wait, then resend the identical request with the same Idempotency-Key. The operation lists its codes in x-error-codes; branch on error.code and, for conflict, error.details.reason.

- `error` (ErrorResponseError, required) — Only code and message are required. Rasen sends details, request_id and retry on every error, but clients must parse an error that lacks them, so a typed error is never lost to a parsing failure.

### 413 Content Too Large Error

Body or upload exceeds its allowed limit.

- `error` (ErrorResponseError, required) — Only code and message are required. Rasen sends details, request_id and retry on every error, but clients must parse an error that lacks them, so a typed error is never lost to a parsing failure.

### 422 Unprocessable Entity Error

The request is well formed but refused, for example a destination that is not a permitted public address.

- `error` (ErrorResponseError, required) — Only code and message are required. Rasen sends details, request_id and retry on every error, but clients must parse an error that lacks them, so a typed error is never lost to a parsing failure.

### 428 Precondition Error

Required If-Match revision is absent.

- `error` (ErrorResponseError, required) — Only code and message are required. Rasen sends details, request_id and retry on every error, but clients must parse an error that lacks them, so a typed error is never lost to a parsing failure.

### 429 Too Many Requests Error

Rate limited. Respect Retry-After and retry the same intent where permitted.

- `error` (ErrorResponseError, required) — Only code and message are required. Rasen sends details, request_id and retry on every error, but clients must parse an error that lacks them, so a typed error is never lost to a parsing failure.

### 500 Internal Server Error

Internal error. The effect may have happened. Retry the same request with the same Idempotency-Key; read the resource before creating a new intent. For an operation that takes an Idempotency-Key, a client retry with the same key is safe: the server replays the stored outcome if the first attempt took effect; if the first attempt had no effect, the retry performs the operation once. Never retry with a new key.

- `error` (ErrorResponseError, required) — Only code and message are required. Rasen sends details, request_id and retry on every error, but clients must parse an error that lacks them, so a typed error is never lost to a parsing failure.

### 503 Service Unavailable Error

Service unavailable. Respect Retry-After where present. For an operation that takes an Idempotency-Key, a client retry with the same key is safe: the server replays the stored outcome if the first attempt took effect; if the first attempt had no effect, the retry performs the operation once. Never retry with a new key.

- `error` (ErrorResponseError, required) — Only code and message are required. Rasen sends details, request_id and retry on every error, but clients must parse an error that lacks them, so a typed error is never lost to a parsing failure.

## Types

### WebhookDefinition

Public HTTPS destination; platform egress policy rejects private/metadata addresses and unsafe redirects at write and execution. No credentials in URL.

- `url` (string, required)
- `method` (enum, required)
  - Allowed values: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`
- `headers` (list of ToolHeader, optional)
- `parameters` (list of ToolParameter, optional)
- `content_type` (enum, optional, default: application/json)
  - Allowed values: `application/json`, `application/x-www-form-urlencoded`

### McpDefinition

- `server_id` (string, required) — Opaque mcp identifier. Do not parse its contents.
- `remote_tool_name` (string, required)
- `schema_digest` (string, required)

### ToolConfigurationSafe

- `kind`: `webhook` (webhook)
  - `deadline_ms` (integer, required, nullable, default: 8000)
  - `definition` (WebhookDefinitionSafe, required) — Public HTTPS destination; platform egress policy rejects private/metadata addresses and unsafe redirects at write and execution. No credentials in URL.
  - `description` (string, required)
  - `effect_id_name` (string, required, nullable)
  - `effect_id_placement` (string, required, nullable) — Open response vocabulary. Preserve unknown values; never assume they mean success.
  - `max_response_bytes` (integer, required, nullable, default: 32768)
  - `name` (string, required)
  - `retry_posture` (string, required) — Open response vocabulary. Preserve unknown values; never assume they mean success.
  - `side_effect` (string, required) — Open response vocabulary. Preserve unknown values; never assume they mean success.
  - `wait_for_response` (boolean, required, nullable, default: true)
- `kind`: `mcp` (mcp)
  - `deadline_ms` (integer, required, nullable, default: 8000)
  - `definition` (McpDefinitionRead, required)
  - `description` (string, required)
  - `max_response_bytes` (integer, required, nullable, default: 32768)
  - `name` (string, required)
  - `retry_posture` (string, required) — Open response vocabulary. Preserve unknown values; never assume they mean success.
  - `side_effect` (string, required) — Open response vocabulary. Preserve unknown values; never assume they mean success.
  - `wait_for_response` (boolean, required, nullable, default: true)

### ErrorResponseError

Only code and message are required. Rasen sends details, request_id and retry on every error, but clients must parse an error that lacks them, so a typed error is never lost to a parsing failure.

- `code` (string, required) — Stable code. Branch on code (and details.reason for conflict), never on message. Each code has one HTTP status; see components.x-error-catalog.
- `message` (string, required) — Safe description; no provider payload or submitted secrets.
- `details` (map from string to any, optional) — Safe code-specific details. The public fields for each code are listed in components.x-error-catalog. Details never contain a request fingerprint, a hash of a request, secret material or another tenant's data. forbidden may include required_capabilities (a list).
- `request_id` (string, optional) — Same value as the X-Request-Id header. Quote it to support.
- `retry` (string, optional) — Retry class of this code: no (fix the request or state first), yes (retry the same request and the same Idempotency-Key), retry_after (wait for Retry-After, then retry the same request). Open response vocabulary. Preserve unknown values; never assume they mean success.

### ToolHeader

secret requires credential; dynamic_var requires variable. Credential may be a safe existing reference or inline write-only material. On full tool replacement, a secret header with the same case-insensitive name can omit credential to retain its binding. New secret headers require a credential. Header omission removes that binding from the draft. Values never return.

- `name` (string, required)
- `value_type` (enum, required)
  - Allowed values: `secret`, `dynamic_var`
- `credential` (CredentialInput, optional) — Exactly one of: an existing reference \{secret\_id, version} to an active version of a secret with the matching purpose, or new write-only inline material. No mixed shape. A reference to a revoked, deleted or wrong-purpose version refuses with credential\_unavailable. References pin the exact version and never follow rotation.
- `variable` (string, optional)

### ToolParameter

- `name` (string, required)
- `location` (enum, required)
  - Allowed values: `path`, `query`, `body`
- `source` (enum, required)
  - Allowed values: `model`, `constant`, `dynamic_variable`
- `schema` (ToolParameterSchema, required)
- `value` (ToolParameterValue, optional) — Constant scalar or dynamic variable name; omit for model parameters. Null constant is not accepted.
- `required` (boolean, optional, default: false)

### WebhookDefinitionSafe

Public HTTPS destination; platform egress policy rejects private/metadata addresses and unsafe redirects at write and execution. No credentials in URL.

- `url` (string, required)
- `method` (string, required) — Open response vocabulary. Preserve unknown values; never assume they mean success.
- `headers` (list of ToolHeaderSafe, required, nullable)
- `parameters` (list of ToolParameterRead, required, nullable)
- `content_type` (string, required, nullable, default: application/json) — Open response vocabulary. Preserve unknown values; never assume they mean success.

### McpDefinitionRead

- `server_id` (string, required) — Opaque mcp identifier. Do not parse its contents.
- `remote_tool_name` (string, required)
- `schema_digest` (string, required)

### CredentialInput

Exactly one of: an existing reference \{secret\_id, version} to an active version of a secret with the matching purpose, or new write-only inline material. No mixed shape. A reference to a revoked, deleted or wrong-purpose version refuses with credential\_unavailable. References pin the exact version and never follow rotation.

### ToolParameterSchema

- `type` (enum, required)
  - Allowed values: `string`, `number`, `integer`, `boolean`
- `enum` (list of ToolParameterSchemaEnumItems, optional)
- `description` (string, optional)
- `minimum` (double, optional)
- `maximum` (double, optional)

### ToolParameterValue

Constant scalar or dynamic variable name; omit for model parameters. Null constant is not accepted.

### ToolHeaderSafe

A secret header returns only its authorized credential reference; never inline material.

- `name` (string, required)
- `value_type` (string, required) — Open response vocabulary. Preserve unknown values; never assume they mean success.
- `credential` (SecretReferenceRead, required, nullable) — Pinned reference to one exact credential version. Never follows rotation: to adopt a new version, replace the tool or MCP server with the new version, then publish each affected agent. No secret material in responses.
- `variable` (string, required, nullable)

### ToolParameterRead

- `name` (string, required)
- `location` (string, required) — Open response vocabulary. Preserve unknown values; never assume they mean success.
- `source` (string, required) — Open response vocabulary. Preserve unknown values; never assume they mean success.
- `value` (ToolParameterReadValue, required, nullable) — Constant scalar or dynamic variable name; omit for model parameters. Null constant is not accepted.
- `required` (boolean, required, nullable, default: false)
- `schema` (ToolParameterReadSchema, required)

### SecretReference

Pinned reference to one exact credential version. Never follows rotation: to adopt a new version, replace the tool or MCP server with the new version, then publish each affected agent. No secret material in responses.

- `secret_id` (string, required) — Opaque sec identifier. Do not parse its contents.
- `version` (integer, required)

### InlineCredential

Write-only credential material for one-off setup. Never returned, logged or included in diffs. It creates a new secret at version 1 (purpose from the consumer) and binds that exact version; the secret then appears in GET /secrets. For declarative or repeated setup, do not re-send inline material: look up or create the secret with POST /secrets and a resource\_key, then send a \{secret\_id, version} reference. Each inline apply creates another secret; with a resource\_key that exists it returns 409 resource\_key\_conflict with details.existing\_id instead.

- `value` (string, required)
- `material_kind` (enum, required) — A descriptive label that does not change how the value is sent. Rasen sends the stored value unchanged as the header value for a webhook secret header, or as "Bearer \<value>" for an MCP server with auth=bearer, or as the header value for auth=secret\_header. For basic\_auth, store the complete header value, for example "Basic \<base64 of user:password>", and use a secret header named Authorization. No kind and auth-mode pair is refused.
  - Allowed values: `api_key`, `bearer_token`, `basic_auth`
- `name` (string, optional) — Optional secret handle for the created secret.
- `resource_key` (string, optional) — Optional caller-chosen resource key: an opaque external identifier that matches your own record (for example a CRM ID, a configuration-file ID or a UUID) to this resource. Rasen still generates the canonical id; use id in paths and references. Case-sensitive, compared exactly, never normalized or parsed. 1-128 characters from A-Z a-z 0-9 . _ : ~ - and starting with a letter or digit; UUIDs and ULIDs are valid. Unique per resource type within one workspace; separate environment workspaces can reuse the same key. Set only at creation and immutable afterwards. A create with a key that already exists returns 409 resource_key_conflict with details.existing_id; it never updates or returns the existing resource. Not an Idempotency-Key: that header identifies one request intent for 24 hours, and a same-intent replay returns its original receipt before the resource-key check runs. Uniqueness includes archived resources. A key becomes free again only after its resource is permanently deleted or erased (a deleted secret or a deleted knowledge source); archive never frees it.

### ToolParameterSchemaEnumItems

### SecretReferenceRead

Pinned reference to one exact credential version. Never follows rotation: to adopt a new version, replace the tool or MCP server with the new version, then publish each affected agent. No secret material in responses.

- `secret_id` (string, required) — Opaque sec identifier. Do not parse its contents.
- `version` (integer, required)

### ToolParameterReadValue

Constant scalar or dynamic variable name; omit for model parameters. Null constant is not accepted.

### ToolParameterReadSchema

- `type` (string, required) — Open response vocabulary. Preserve unknown values; never assume they mean success.
- `enum` (list of ToolParameterReadSchemaEnumItems, required, nullable)
- `description` (string, required, nullable)
- `minimum` (double, required, nullable)
- `maximum` (double, required, nullable)

### ToolParameterReadSchemaEnumItems

## Examples

**Request**

```json
{
  "kind": "webhook",
  "definition": {
    "url": "https://api.example.com/orders/{order_id}",
    "method": "GET",
    "headers": [
      {
        "name": "Authorization",
        "value_type": "secret",
        "credential": {
          "secret_id": "sec_01j00000000000000000000000",
          "version": 2
        }
      }
    ],
    "parameters": [
      {
        "name": "order_id",
        "location": "path",
        "source": "model",
        "schema": {
          "type": "string",
          "description": "Order ID said by the caller."
        },
        "required": true
      }
    ]
  },
  "description": "Look up an order by its ID.",
  "name": "lookup_order",
  "retry_posture": "none",
  "side_effect": "none"
}
```

**Response**

```json
{
  "id": "tool_01j00000000000000000000000",
  "agent_id": "agent_01j00000000000000000000000",
  "revision": "example",
  "configuration": {
    "kind": "webhook",
    "deadline_ms": 8000,
    "definition": {
      "url": "https://api.example.com/orders",
      "method": "GET",
      "headers": [
        {
          "name": "Authorization",
          "value_type": "secret",
          "credential": {
            "secret_id": "sec_01j00000000000000000000000",
            "version": 1
          },
          "variable": "customer_name"
        }
      ],
      "parameters": [
        {
          "name": "lookup_order",
          "location": "path",
          "source": "model",
          "value": "example",
          "required": false,
          "schema": {
            "type": "string",
            "enum": [
              "example"
            ],
            "description": "example",
            "minimum": 1,
            "maximum": 1
          }
        }
      ],
      "content_type": "application/json"
    },
    "description": "example",
    "effect_id_name": "example",
    "effect_id_placement": "header",
    "max_response_bytes": 32768,
    "name": "lookup_order",
    "retry_posture": "none",
    "side_effect": "none",
    "wait_for_response": true
  },
  "created_at": "2026-09-25T10:00:00Z"
}
```

**SDK Code**

```python agents_tools_create_example
import requests

url = "https://api.v2.rasen.ai/v1/agents/agent_id/tools"

payload = {
    "kind": "webhook",
    "definition": {
        "url": "https://api.example.com/orders/{order_id}",
        "method": "GET",
        "headers": [
            {
                "name": "Authorization",
                "value_type": "secret",
                "credential": {
                    "secret_id": "sec_01j00000000000000000000000",
                    "version": 2
                }
            }
        ],
        "parameters": [
            {
                "name": "order_id",
                "location": "path",
                "source": "model",
                "schema": {
                    "type": "string",
                    "description": "Order ID said by the caller."
                },
                "required": True
            }
        ]
    },
    "description": "Look up an order by its ID.",
    "name": "lookup_order",
    "retry_posture": "none",
    "side_effect": "none"
}
headers = {
    "Idempotency-Key": "intent-appointment-001",
    "If-Match": "\"draft-17\"",
    "Authorization": "Bearer <api_key>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript agents_tools_create_example
const url = 'https://api.v2.rasen.ai/v1/agents/agent_id/tools';
const options = {
  method: 'POST',
  headers: {
    'Idempotency-Key': 'intent-appointment-001',
    'If-Match': '"draft-17"',
    Authorization: 'Bearer <api_key>',
    'Content-Type': 'application/json'
  },
  body: '{"kind":"webhook","definition":{"url":"https://api.example.com/orders/{order_id}","method":"GET","headers":[{"name":"Authorization","value_type":"secret","credential":{"secret_id":"sec_01j00000000000000000000000","version":2}}],"parameters":[{"name":"order_id","location":"path","source":"model","schema":{"type":"string","description":"Order ID said by the caller."},"required":true}]},"description":"Look up an order by its ID.","name":"lookup_order","retry_posture":"none","side_effect":"none"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```ruby agents_tools_create_example
require 'uri'
require 'net/http'

url = URI("https://api.v2.rasen.ai/v1/agents/agent_id/tools")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Idempotency-Key"] = 'intent-appointment-001'
request["If-Match"] = '"draft-17"'
request["Authorization"] = 'Bearer <api_key>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"kind\": \"webhook\",\n  \"definition\": {\n    \"url\": \"https://api.example.com/orders/{order_id}\",\n    \"method\": \"GET\",\n    \"headers\": [\n      {\n        \"name\": \"Authorization\",\n        \"value_type\": \"secret\",\n        \"credential\": {\n          \"secret_id\": \"sec_01j00000000000000000000000\",\n          \"version\": 2\n        }\n      }\n    ],\n    \"parameters\": [\n      {\n        \"name\": \"order_id\",\n        \"location\": \"path\",\n        \"source\": \"model\",\n        \"schema\": {\n          \"type\": \"string\",\n          \"description\": \"Order ID said by the caller.\"\n        },\n        \"required\": true\n      }\n    ]\n  },\n  \"description\": \"Look up an order by its ID.\",\n  \"name\": \"lookup_order\",\n  \"retry_posture\": \"none\",\n  \"side_effect\": \"none\"\n}"

response = http.request(request)
puts response.read_body
```

```java agents_tools_create_example
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.v2.rasen.ai/v1/agents/agent_id/tools")
  .header("Idempotency-Key", "intent-appointment-001")
  .header("If-Match", "\"draft-17\"")
  .header("Authorization", "Bearer <api_key>")
  .header("Content-Type", "application/json")
  .body("{\n  \"kind\": \"webhook\",\n  \"definition\": {\n    \"url\": \"https://api.example.com/orders/{order_id}\",\n    \"method\": \"GET\",\n    \"headers\": [\n      {\n        \"name\": \"Authorization\",\n        \"value_type\": \"secret\",\n        \"credential\": {\n          \"secret_id\": \"sec_01j00000000000000000000000\",\n          \"version\": 2\n        }\n      }\n    ],\n    \"parameters\": [\n      {\n        \"name\": \"order_id\",\n        \"location\": \"path\",\n        \"source\": \"model\",\n        \"schema\": {\n          \"type\": \"string\",\n          \"description\": \"Order ID said by the caller.\"\n        },\n        \"required\": true\n      }\n    ]\n  },\n  \"description\": \"Look up an order by its ID.\",\n  \"name\": \"lookup_order\",\n  \"retry_posture\": \"none\",\n  \"side_effect\": \"none\"\n}")
  .asString();
```

```php agents_tools_create_example
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.v2.rasen.ai/v1/agents/agent_id/tools', [
  'body' => '{
  "kind": "webhook",
  "definition": {
    "url": "https://api.example.com/orders/{order_id}",
    "method": "GET",
    "headers": [
      {
        "name": "Authorization",
        "value_type": "secret",
        "credential": {
          "secret_id": "sec_01j00000000000000000000000",
          "version": 2
        }
      }
    ],
    "parameters": [
      {
        "name": "order_id",
        "location": "path",
        "source": "model",
        "schema": {
          "type": "string",
          "description": "Order ID said by the caller."
        },
        "required": true
      }
    ]
  },
  "description": "Look up an order by its ID.",
  "name": "lookup_order",
  "retry_posture": "none",
  "side_effect": "none"
}',
  'headers' => [
    'Authorization' => 'Bearer <api_key>',
    'Content-Type' => 'application/json',
    'Idempotency-Key' => 'intent-appointment-001',
    'If-Match' => '"draft-17"',
  ],
]);

echo $response->getBody();
```

```csharp agents_tools_create_example
using RestSharp;

var client = new RestClient("https://api.v2.rasen.ai/v1/agents/agent_id/tools");
var request = new RestRequest(Method.POST);
request.AddHeader("Idempotency-Key", "intent-appointment-001");
request.AddHeader("If-Match", "\"draft-17\"");
request.AddHeader("Authorization", "Bearer <api_key>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"kind\": \"webhook\",\n  \"definition\": {\n    \"url\": \"https://api.example.com/orders/{order_id}\",\n    \"method\": \"GET\",\n    \"headers\": [\n      {\n        \"name\": \"Authorization\",\n        \"value_type\": \"secret\",\n        \"credential\": {\n          \"secret_id\": \"sec_01j00000000000000000000000\",\n          \"version\": 2\n        }\n      }\n    ],\n    \"parameters\": [\n      {\n        \"name\": \"order_id\",\n        \"location\": \"path\",\n        \"source\": \"model\",\n        \"schema\": {\n          \"type\": \"string\",\n          \"description\": \"Order ID said by the caller.\"\n        },\n        \"required\": true\n      }\n    ]\n  },\n  \"description\": \"Look up an order by its ID.\",\n  \"name\": \"lookup_order\",\n  \"retry_posture\": \"none\",\n  \"side_effect\": \"none\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift agents_tools_create_example
import Foundation

let headers = [
  "Idempotency-Key": "intent-appointment-001",
  "If-Match": "\"draft-17\"",
  "Authorization": "Bearer <api_key>",
  "Content-Type": "application/json"
]
let parameters = [
  "kind": "webhook",
  "definition": [
    "url": "https://api.example.com/orders/{order_id}",
    "method": "GET",
    "headers": [
      [
        "name": "Authorization",
        "value_type": "secret",
        "credential": [
          "secret_id": "sec_01j00000000000000000000000",
          "version": 2
        ]
      ]
    ],
    "parameters": [
      [
        "name": "order_id",
        "location": "path",
        "source": "model",
        "schema": [
          "type": "string",
          "description": "Order ID said by the caller."
        ],
        "required": true
      ]
    ]
  ],
  "description": "Look up an order by its ID.",
  "name": "lookup_order",
  "retry_posture": "none",
  "side_effect": "none"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.v2.rasen.ai/v1/agents/agent_id/tools")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```