Skip to Content
GatewaysUTCP gateway

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.

Gateway detail page

Create the gateway

  1. Open Gateways and click Create gateway.
  2. Under Protocol, pick UTCP.
  3. Under Tools, pick a whole API or single tools. A draft tool can’t be offered until it is active.
  4. Give it a Name and choose Who can use it. Advanced changes the path and who can manage it.
  5. 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.

Assigning tools on the gateway Tool scoping tab

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

MethodPathAuthDescription
GET/.well-known/utcppublicDiscovery: where the manual and execute URLs are, and what auth the gateway wants
GET/manualrequiredThe UTCP manual: every tool on this gateway, with its call template
POST/executerequiredRun a tool through almyty (proxy mode, an almyty extension)
POST/execute/{toolId}requiredRun 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:

CodeDescription
EXECUTION_ERRORThe tool ran and failed (upstream error, validation, timeout)
INTERNAL_ERRORThe call could not be dispatched at all

Everything before the tool runs is an ordinary HTTP error:

HTTP StatusDescription
401Missing or invalid authentication
404Unknown UTCP action (a sub-path other than manual, execute, or .well-known/utcp)
429Gateway 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.