---
title: "Create Tool"
method: POST
path: "/v1/agent/tools"
tags: ["Agent Tools"]
---

# Create Tool

`POST /v1/agent/tools`

## Request

- Base URL: `https://api.fish.audio`
- URL: `https://api.fish.audio/v1/agent/tools`
- Auth: HTTP bearer

Create a workspace tool. `tool_type: webhook` calls your HTTP endpoint
during conversations; `tool_type: client` is executed by your own frontend
through the SDK (no URL). Describe the call with `parameters` (a JSON
Schema object, checked at call time under `parameter_mode: strict`) or
the older `arguments` list (every argument a required string, unchecked).
Attach it to agents via the agent config's `tools.tool_ids`. Credential
headers (authorization_bearer / authorization_basic) are write-only and
read back as `has_secret`.

## Request body

- PublicAgentToolCreatePayload: Console payload minus the folder concept (folders are console-only).
  - `name` string, required
  - `description` string
  - `tool_type` 'webhook' | 'client'
  - `arguments` AgentWebhookArgumentPayload[]: Older shape of `parameters`: each entry becomes a required string parameter. Mutually exclusive with `parameters` in one request. Reads return both, `arguments` being the string parameters of the schema.
    - `name` string, required
    - `description` string
  - `parameters` object, nullable: Call parameters as a JSON Schema object (draft 2020-12): `type: object` (filled in when omitted), `properties`, `required`. Standard meaning throughout: `required` means the key must be present, `type: ["string", "null"]` allows null, `minLength: 1` makes a string non-empty, `enum` and `pattern` constrain the value, `default` fills a key the model left out. Mutually exclusive with `arguments` in one request. A tool created with `parameters` starts in `parameter_mode: strict`.
  - `parameter_mode` 'legacy' | 'strict', nullable: Whether the agent runtime checks each call's arguments against `parameters` before running the tool. `legacy` (the default for tools created with `arguments`, and every tool from before this field existed): no check, the model's arguments run as sent. `strict` (the default for tools created with `parameters`): a call that does not satisfy the schema is refused and the model is told to ask the caller for the missing information, and the LLM provider's constrained decoding is turned on where the model supports it (OpenAI models and a custom OpenAI-compatible endpoint receive the tool with `strict: true`, Gemini on the platform's native channel runs validated function calling). Changing `parameters` never changes the mode, and the mode can be changed on its own.
  - `method` 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
  - `url` string
  - `content_type` string
  - `body_template` string: Request body sent for non-GET calls. `{{argument}}`, `{{system.*}}` and custom session variable placeholders are filled in at call time; a missing value becomes empty. When the body's content type is JSON (the default `application/json`, any `+json` type, or a `Content-Type` header saying so), a placeholder inside a JSON string, such as `{"note": "{{note}}"}`, is JSON-escaped, so quotes, backslashes and newlines in the value keep the body valid JSON. A placeholder outside a string, such as `{"count": {{count}}}`, is inserted as is. Other content types are filled in as is.
  - `headers` AgentWebhookHeaderPayload[]
    - `name` string, required
    - `value` string, required
    - `kind` 'custom' | 'authorization_bearer' | 'authorization_basic'
  - `timeout_seconds` integer
  - `error_handling` 'passthrough' | 'hide'
  - `mock_responses` AgentWebhookMockResponsePayload[]
    - `name` string
    - `status_code` integer
    - `content_type` string
    - `body` string
  - `expects_response` boolean
  - `execution_mode` 'blocking' | 'fire_and_forget' | 'background'

## Response `201`

Document created, URL follows

- object
  - `tool_id` string, required
  - `workspace_id` string, required
  - `name` string, required
  - `description` string
  - `tool_type` 'webhook' | 'client', required
  - `method` 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', required
  - `url` string, required
  - `used_by` integer: How many agents reference this tool in their draft configuration.
  - `created_at` string, date-time, required
  - `updated_at` string, date-time, required
  - `arguments` AgentWebhookArgumentPayload[]: Older shape of `parameters`: each entry becomes a required string parameter. Mutually exclusive with `parameters` in one request. Reads return both, `arguments` being the string parameters of the schema.
    - `name` string, required
    - `description` string
  - `parameters` object, required: Call parameters as a JSON Schema object (draft 2020-12): `type: object` (filled in when omitted), `properties`, `required`. Standard meaning throughout: `required` means the key must be present, `type: ["string", "null"]` allows null, `minLength: 1` makes a string non-empty, `enum` and `pattern` constrain the value, `default` fills a key the model left out. Mutually exclusive with `arguments` in one request. A tool created with `parameters` starts in `parameter_mode: strict`.
  - `parameter_mode` 'legacy' | 'strict': Whether the agent runtime checks each call's arguments against `parameters` before running the tool. `legacy` (the default for tools created with `arguments`, and every tool from before this field existed): no check, the model's arguments run as sent. `strict` (the default for tools created with `parameters`): a call that does not satisfy the schema is refused and the model is told to ask the caller for the missing information, and the LLM provider's constrained decoding is turned on where the model supports it (OpenAI models and a custom OpenAI-compatible endpoint receive the tool with `strict: true`, Gemini on the platform's native channel runs validated function calling). Changing `parameters` never changes the mode, and the mode can be changed on its own.
  - `content_type` string, required
  - `body_template` string
  - `headers` PublicToolHeader[]
    - `name` string, required
    - `kind` 'custom' | 'authorization_bearer' | 'authorization_basic', required
    - `value` string, nullable: Header value; null for credential kinds (authorization_bearer / authorization_basic), which are write-only.
    - `has_secret` boolean: Whether a credential value is stored; the value itself is never returned.
  - `timeout_seconds` integer, required
  - `error_handling` 'passthrough' | 'hide', required
  - `mock_responses` AgentWebhookMockResponsePayload[]
    - `name` string
    - `status_code` integer
    - `content_type` string
    - `body` string
  - `expects_response` boolean
  - `execution_mode` 'blocking' | 'fire_and_forget' | 'background'

## Other responses

- `401`: No permission -- see authorization schemes
- `422`
- `503`: The server cannot process the request due to a high load

## Changes

- **2026-11-03** `42ac421f7a12`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/fish/apis/fishaudio-openapi/changes/v1/agent/tools/post.md)

---

[API](https://skmtc.dev/fish/apis/fishaudio-openapi.md) - [All operations](https://skmtc.dev/fish/apis/fishaudio-openapi/llms.txt) - [OpenAPI document](https://skmtc.dev/fish/apis/fishaudio-openapi/revisions/42ac421f7a12?raw)
