UTCP gateway
UTCP (Universal Tool Call Protocol) is a simple way for software to find and call tools over ordinary web requests, with no special library. A UTCP gateway gives a program, a script or a partner’s system one address where it can read the list of your tools and run them. For example, a logistics partner’s system can look up an order’s status in yours without you building an integration for them.

Create the gateway
- Open Gateways and click Create gateway.
- Under Protocol, pick UTCP.
- Under Tools, pick a whole API or single tools. A draft tool can’t be offered until it is active.
- Give it a Name and choose Who can use it. Advanced changes the path and who can manage it.
- Click Create gateway. For a partner or another outside system, choose Outside, protected and configure its keys, usernames or signed tokens on the page that opens.
Change which tools it offers under Tool scoping on the gateway’s page, and who may call it under Who can use it. Hand the address and a key to whoever builds the other side; the Integrations tab lists the addresses they need.

For developers
Your UTCP endpoint follows this pattern:
https://api.almyty.com/{org}/{gateway-slug}There is no /utcp path segment. Every gateway is served from its own
{org}/{gateway-slug} URL, and on a UTCP gateway /manual, /execute and
/.well-known/utcp are the paths under it. A UTCP gateway serves UTCP only: the same tools
over MCP or as Agent Skills need a gateway of that protocol.
Endpoints
UTCP is a manual-driven protocol: you fetch one manual that describes every tool and how
to call it. There is no /health on the gateway path (platform health lives at /health
on the API root).
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /.well-known/utcp | public | Discovery: where the manual and execute URLs are, and what auth the gateway wants |
GET | /manual | required | The UTCP manual: every tool on this gateway, with its call template |
POST | /execute | required | Run a tool through almyty (proxy mode, an almyty extension) |
POST | /execute/{toolId} | required | Run one tool, arguments as query string or JSON body: the address a hand-made tool’s call template names |
Any other sub-path returns 404 Unknown UTCP action.
Discovery
curl https://api.almyty.com/acme/tools-api/.well-known/utcp{
"utcp_version": "1.0.0",
"manual_version": "<gateway-uuid>:2026-01-01T12:00:00.000Z",
"manual_url": "https://api.almyty.com/acme/tools-api/manual",
"execute_url": "https://api.almyty.com/acme/tools-api/execute",
"server": {
"name": "almyty",
"version": "1.0.0",
"description": "almyty UTCP gateway"
}
}If the gateway has auth configured, an auth field describes it (one object, or an array
when several methods are active).
Manual
The manual is snake_case throughout: UTCP SDKs parse against these exact field names.
Each tool carries an inline tool_call_template pointing at the upstream API, so a UTCP
client can call the tool directly without proxying through almyty.
curl https://api.almyty.com/acme/tools-api/manual \
-H "x-api-key: your-gateway-key"{
"utcp_version": "1.0.0",
"manual_version": "<gateway-uuid>:2026-01-01T12:00:00.000Z",
"tools": [
{
"name": "get_users",
"description": "Retrieve a list of users with pagination",
"inputs": {
"type": "object",
"properties": {
"page": { "type": "integer" },
"limit": { "type": "integer" }
}
},
"outputs": { "type": "object" },
"tags": ["openapi", "auto-generated"],
"tool_call_template": {
"call_template_type": "http",
"url": "https://api.example.com/users",
"http_method": "GET",
"content_type": "application/json",
"headers": {}
}
}
]
}Secrets are never inlined: an API key, password, or client secret in the call template’s
auth block is replaced with a {{...}} placeholder, because the manual is served to
anyone with gateway access.
The manual lists every tool the gateway shares, the same tools MCP tools/list and the
Skills list show. What differs is where a tool’s call template points:
- A tool generated from an API operation points at that API, as above.
- A tool made by hand (HTTP, JavaScript, GraphQL, LLM) runs on almyty: a JavaScript or LLM tool has no address of its own, and an HTTP or GraphQL tool carries stored auth, a body template or a GraphQL document that only almyty applies. Its template points at the tool’s own execute address on this gateway, with the gateway’s auth:
{
"call_template_type": "http",
"url": "https://api.almyty.com/acme/tools-api/execute/d3b0...-tool-uuid",
"http_method": "POST",
"content_type": "application/json",
"auth": { "auth_type": "api_key", "api_key": "{{GATEWAY_..._API_KEY}}", "var_name": "x-api-key", "location": "header" }
}A UTCP client sends the tool’s arguments to that address as query parameters; almyty reads each one back to the type the tool’s input schema declares. A JSON body with the arguments works too, and wins where both name the same argument.
The manual is cached for five minutes per gateway.
Execute
/execute is an almyty extension, not part of the UTCP spec. It runs the tool
server-side, which means almyty supplies the API’s stored credentials and enforces the
gateway’s rate limits and per-tool security policy. toolId is the tool’s almyty id, the
UUID in the tool’s dashboard URL (/tools/{id}).
curl -X POST https://api.almyty.com/acme/tools-api/execute \
-H "x-api-key: your-gateway-key" \
-H "Content-Type: application/json" \
-d '{
"toolId": "d3b0...-tool-uuid",
"parameters": { "page": 1, "limit": 10 }
}'An optional options object takes timeout, retries, and skipCache. The response
always carries success and metadata:
{
"success": true,
"data": {
"users": [
{ "id": "1", "name": "Alice", "email": "alice@example.com" }
],
"total": 42
},
"metadata": {
"executionTime": 234,
"toolId": "d3b0...-tool-uuid",
"requestId": "utcp-...",
"timestamp": "2026-01-01T12:00:00.000Z",
"cached": false,
"retryCount": 0
}
}A failure returns "success": false with an error object instead of data.
Connect from code
Python
import requests
base = "https://api.almyty.com/acme/tools-api"
headers = {"x-api-key": "your-gateway-key"}
manual = requests.get(f"{base}/manual", headers=headers).json()
tools = manual["tools"]
result = requests.post(
f"{base}/execute",
headers=headers,
json={"toolId": "d3b0...-tool-uuid", "parameters": {"page": 1, "limit": 10}},
).json()["data"]JavaScript
const base = "https://api.almyty.com/acme/tools-api";
const headers = { "x-api-key": "your-gateway-key" };
const { tools } = await fetch(`${base}/manual`, { headers }).then(r => r.json());
const { data } = await fetch(`${base}/execute`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ toolId: "d3b0...-tool-uuid", parameters: { page: 1, limit: 10 } }),
}).then(r => r.json());Error codes
/execute answers with HTTP 200 and "success": false, and the code is in error.code:
| Code | Description |
|---|---|
EXECUTION_ERROR | The tool ran and failed (upstream error, validation, timeout) |
INTERNAL_ERROR | The call could not be dispatched at all |
Everything before the tool runs is an ordinary HTTP error:
| HTTP Status | Description |
|---|---|
| 401 | Missing or invalid authentication |
| 404 | Unknown UTCP action (a sub-path other than manual, execute, or .well-known/utcp) |
| 429 | Gateway rate limit exceeded; the body carries code, and retryAfter when known |
See Gateway Authentication for the auth methods you can put in front of the endpoint.