> 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.

# Delete a tool from the draft

DELETE https://api.v2.rasen.ai/v1/agents/{agent_id}/tools/{tool_id}

Draft detachment only. Existing publications continue to use their frozen tool until a new publication is activated. Live tool disable (descriptor revocation) stays in the app; credential revocation is revokeSecretVersion. An archived agent is refused with agent_archived.

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

## 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.
- `tool_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.

## Response

### 200

Remove a tool from the draft.

- `agent_id` (string, required) — Opaque agent identifier. Do not parse its contents.
- `revision` (string, required) — Opaque publication token; equals the ETag. The ETag is the publication token. It covers the draft revision, the revision of each bound tool, the revision of each referenced MCP server, each pinned secret version, the direct agent library membership and source state/revision, and the default-tool policy. A change to any of them, including an MCP server edit shared by several agents, changes the token.
- `prompt` (string, required)
- `voice` (VoiceSelectionRead, required)
- `settings` (Settings, required) — Effective settings after defaults resolve. Not a PATCH input.
- `variables` (list of Variable, required)
- `tool_ids` (list of string, required)
- `retrieval_snapshot_id` (string, required, nullable) — Retained compatibility value. Publication does not read it.
- `fallback_source_ids` (list of string, required) — Retained compatibility values. Publication does not read them.
- `referenced_variables` (list of string, required) — Discovered prompt references, not a guarantee of required-input validation. Undeclared per-call variables remain accepted.
- `updated_at` (datetime, required)
- `default_tools` (DefaultTools, required) — Effective built-in policies. end_call is enabled by default for every agent, including agents created before this field existed; only an explicit end_call.enabled=false policy turns it off. transfer starts disabled with a null destination. Publication freezes the policy; rollback restores it subject to current revocation. Clients must accept additional policy keys.

## 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.

### 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

### VoiceSelectionRead

- `voice_id` (string, required, nullable) — Choose from GET /voices. Null explicitly resets to the supported default.

### Settings

Effective settings after defaults resolve. Not a PATCH input.

- `purpose` (string, required, nullable)
- `goals` (list of string, required)
- `call_direction` (string, required, nullable) — Open response vocabulary. Preserve unknown values.
- `grammatical_gender` (string, required, nullable) — Open response vocabulary. Preserve unknown values.
- `additional_context` (string, required, nullable)
- `language` (string, required) — Agent language. Known values are en, hi and hinglish. Open response vocabulary: preserve unknown values; never assume they mean success.
- `agent_name` (string, required, nullable)
- `first_message` (string, required)
- `timezone` (string, required) — IANA timezone.
- `max_call_duration_seconds` (integer, required, nullable) — Platform default on create/reset: 1800; omission in PATCH keeps the existing value.
- `inactivity_timeout_seconds` (integer, required, nullable) — Platform default on create/reset: 30; omission in PATCH keeps the existing value.
- `user_turn_timeout_ms` (integer, required, nullable)
- `ivr_hangup` (boolean, required)
- `voicemail_detection` (boolean, required)
- `dtmf_enabled` (boolean, required)
- `dtmf_timeout_seconds` (integer, required)
- `dtmf_digit_limit` (integer, required)
- `dtmf_termination_key` (string, required, nullable)
- `bg_audio` (string, required, nullable) — Open response vocabulary. Preserve unknown values; never assume they mean success.
- `bg_volume` (integer, required)
- `extraction_fields` (list of ExtractionFieldRead, required)

### Variable

Literal prompt default, not a credential. Runtime call variables can override defaults. Use tool secrets for credentials; never place a credential in a prompt variable. All reads and diffs, including draft, variable list and publication history, return pii/confidential values as null with a redacted marker. Null without that marker means an actual null. Credentials are never returned.

- `key` (string, required)
- `value` (string, required, nullable)
- `description` (string, required, nullable)
- `sensitivity` (string, required) — Open response vocabulary. Preserve unknown values; never assume they mean success.
- `redacted` (boolean, required)

### DefaultTools

Effective built-in policies. end_call is enabled by default for every agent, including agents created before this field existed; only an explicit end_call.enabled=false policy turns it off. transfer starts disabled with a null destination. Publication freezes the policy; rollback restores it subject to current revocation. Clients must accept additional policy keys.

- `end_call` (EndCallPolicyRead, required) — Only enable or disable. Name, prompt, description and parameters are not editable. Independent duration limits and external hangup still apply. Default enabled=true; only an explicit false disables it.
- `transfer` (TransferPolicyRead, required) — Enabled requires a destination_number. Disabled may retain a destination for later use. Fixed E164 transfer only in this version.

### 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.

### ExtractionFieldRead

Names are unique. choices is required only for selector. required is an extraction instruction, not a guarantee the caller supplies the fact. Missing evidence must not invent a value.

- `name` (string, required)
- `type` (string, required) — Open response vocabulary. Preserve unknown values; never assume they mean success.
- `description` (string, required, nullable)
- `required` (boolean, required, nullable)
- `choices` (list of string, required, nullable)

### EndCallPolicyRead

Only enable or disable. Name, prompt, description and parameters are not editable. Independent duration limits and external hangup still apply. Default enabled=true; only an explicit false disables it.

- `enabled` (boolean, required)

### TransferPolicyRead

Enabled requires a destination_number. Disabled may retain a destination for later use. Fixed E164 transfer only in this version.

- `enabled` (boolean, required)
- `destination_number` (string, required, nullable)

## Examples

**Response**

```json
{
  "agent_id": "agent_01j00000000000000000000000",
  "revision": "draft-17",
  "prompt": "example",
  "voice": {
    "voice_id": "voice_01j00000000000000000000000"
  },
  "settings": {
    "purpose": null,
    "goals": [],
    "call_direction": null,
    "grammatical_gender": null,
    "additional_context": null,
    "language": "en",
    "agent_name": "example",
    "first_message": "example",
    "timezone": "Asia/Kolkata",
    "max_call_duration_seconds": 60,
    "inactivity_timeout_seconds": 1,
    "user_turn_timeout_ms": 250,
    "ivr_hangup": true,
    "voicemail_detection": true,
    "dtmf_enabled": true,
    "dtmf_timeout_seconds": 1,
    "dtmf_digit_limit": 1,
    "dtmf_termination_key": "#",
    "bg_audio": "office",
    "bg_volume": 0,
    "extraction_fields": [
      {
        "name": "appointment_confirmed",
        "type": "text",
        "description": "example",
        "required": true,
        "choices": [
          "example"
        ]
      }
    ]
  },
  "variables": [
    {
      "key": "customer_name",
      "value": "example",
      "description": "example",
      "sensitivity": "public",
      "redacted": true
    }
  ],
  "tool_ids": [
    "example"
  ],
  "retrieval_snapshot_id": "retrieval_snapshot_01j00000000000000000000000",
  "fallback_source_ids": [
    "example"
  ],
  "referenced_variables": [
    "example"
  ],
  "updated_at": "2026-09-25T10:06:00Z",
  "default_tools": {
    "end_call": {
      "enabled": true
    },
    "transfer": {
      "enabled": true,
      "destination_number": "+12025550123"
    }
  }
}
```

**SDK Code**

```python agents_tools_delete_example
import requests

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

headers = {
    "Idempotency-Key": "intent-appointment-001",
    "If-Match": "\"draft-17\"",
    "Authorization": "Bearer <api_key>"
}

response = requests.delete(url, headers=headers)

print(response.json())
```

```javascript agents_tools_delete_example
const url = 'https://api.v2.rasen.ai/v1/agents/agent_id/tools/tool_id';
const options = {
  method: 'DELETE',
  headers: {
    'Idempotency-Key': 'intent-appointment-001',
    'If-Match': '"draft-17"',
    Authorization: 'Bearer <api_key>'
  }
};

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

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

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

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

request = Net::HTTP::Delete.new(url)
request["Idempotency-Key"] = 'intent-appointment-001'
request["If-Match"] = '"draft-17"'
request["Authorization"] = 'Bearer <api_key>'

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

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

HttpResponse<String> response = Unirest.delete("https://api.v2.rasen.ai/v1/agents/agent_id/tools/tool_id")
  .header("Idempotency-Key", "intent-appointment-001")
  .header("If-Match", "\"draft-17\"")
  .header("Authorization", "Bearer <api_key>")
  .asString();
```

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

$client = new \GuzzleHttp\Client();

$response = $client->request('DELETE', 'https://api.v2.rasen.ai/v1/agents/agent_id/tools/tool_id', [
  'headers' => [
    'Authorization' => 'Bearer <api_key>',
    'Idempotency-Key' => 'intent-appointment-001',
    'If-Match' => '"draft-17"',
  ],
]);

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

```csharp agents_tools_delete_example
using RestSharp;

var client = new RestClient("https://api.v2.rasen.ai/v1/agents/agent_id/tools/tool_id");
var request = new RestRequest(Method.DELETE);
request.AddHeader("Idempotency-Key", "intent-appointment-001");
request.AddHeader("If-Match", "\"draft-17\"");
request.AddHeader("Authorization", "Bearer <api_key>");
IRestResponse response = client.Execute(request);
```

```swift agents_tools_delete_example
import Foundation

let headers = [
  "Idempotency-Key": "intent-appointment-001",
  "If-Match": "\"draft-17\"",
  "Authorization": "Bearer <api_key>"
]

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

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()
```