Custom tools
Create, update, test, and delete workspace HTTP custom tools.
Custom tools under /api/v1/custom-tools. Read-only GET routes accept a workspace, personal, or
Copilot key with current membership and return the workspace-safe.v1 projection for keys without
the workspace.secret.read capability. Mutations and tool tests require a workspace-scoped key. Missing or invalid token
→ 401 { "error": "Valid bearer API key is required" }; insufficient current authority → 403.
There is no Idempotency-Key handling. Lists return the full workspace array (no cursor pagination).
Endpoints
| Method | Path | Envelope |
|---|---|---|
| GET | /api/v1/custom-tools | { tools[] } |
| POST | /api/v1/custom-tools | 201 { tool } |
| GET | /api/v1/custom-tools/{id} | { tool } |
| PATCH | /api/v1/custom-tools/{id} | { tool } |
| DELETE | /api/v1/custom-tools/{id} | { deleted: true } |
| POST | /api/v1/custom-tools/{id}/test | { result } |
Unknown ids in the key's workspace → 404 { "error": "Custom tool not found." }. Session copies exist under /api/custom-tools/*.
Tool object
id, workspaceId, name, description, method (GET | POST | PUT | PATCH | DELETE), url, headers (string map), optional bodyTemplate, enabled, createdAt, updatedAt, optional lastUsedAt.
Templates may use {{input.path}} and {{credential.name}} / {{secret.name}} / {{secrets.name}}. Test resolves those names as workspace credential secrets.
Create
POST JSON (invalid JSON treated as {}):
| Field | Notes |
|---|---|
name | Required (trimmed) |
url | Required (trimmed) |
description | Optional |
method | Default POST if omitted. The route does not reject unknown method strings |
headers | Object or JSON string object. Arrays/null (except empty string → {}) → 400 "Headers must be a JSON object." |
bodyTemplate | Optional string |
Missing name or URL → 400 { "error": "Custom tool name and URL are required." }. Other create failures → 400 { "error": "<message>" }. Default enabled: true. Duplicate names are not upserted (unlike MCP servers).
Update, delete, test
PATCH accepts enabled, url, headers, bodyTemplate, description. Omitting a field leaves it unchanged. name and method cannot be changed here. Header parse errors and update failures → 400.
DELETE is hard-delete.
POST .../test body { input } (object or JSON object string; default {}). Disabled tool → 400 { "error": "Custom tool must be enabled before testing." }. Invalid input → 400. Successful HTTP (or template/missing-secret outcomes) return 200 { result } with ok, toolId, toolName, method, url, optional status / statusText / body / error, latencyMs, checkedAt. Fetch timeout default 15s. Missing required input paths or secrets set result.ok: false without a 4xx.
Known gaps
- No rename / method change on PATCH.
- Read-only routes preserve personal/Copilot compatibility; mutations and tests remain workspace-key-only.
- No pagination or
Idempotency-Key. - Invalid
methodis not validated before persist.
