WarmblyDocs

Agent tools (REST)

Use Warmbly's AI tool registry from any function-calling agent, no MCP client required. List the tools your credentials allow, export OpenAI and Hermes format manifests, and execute tools over plain HTTP.

Not every agent speaks MCP. Hermes-style models, OpenAI-compatible frameworks, LangChain agents, and plain scripts all do function calling over ordinary HTTP, so Warmbly exposes the same tool registry as the MCP server through two REST endpoints: one that lists the tools your credentials allow (in the manifest format your agent framework expects), and one that executes a tool.

The rules are identical to MCP: every tool is gated by its own permission bits, the list only ever shows what the caller may use, and send-class tools (anything that puts real mail on the wire) are never exposed. An agent wired through this surface can read, search, label, and draft, but a human always presses send.

  • List: GET https://api.warmbly.com/v1/ai/tools
  • Execute: POST https://api.warmbly.com/v1/ai/tools/{name}/call
  • Auth: an API key or OAuth access token as a bearer header, or a dashboard JWT. For keys, each tool checks its own scope from the permissions table; for JWT members, the matching organization permission.

List the tools

GET /ai/tools returns the catalog filtered to what the caller's credentials allow. Three formats:

formatShape
warmbly (default){ name, description, input_schema } per tool
openaiOpenAI function-calling objects: { type: "function", function: { name, description, parameters } }
hermes, functionsAliases of openai; the same JSON schemas drop into a Hermes <tools> block verbatim
curl -s "https://api.warmbly.com/v1/ai/tools?format=openai" \
  -H "Authorization: Bearer wmbly_..."
{
  "data": [
    {
      "type": "function",
      "function": {
        "name": "list_threads",
        "description": "List unified-inbox conversation threads...",
        "parameters": {
          "type": "object",
          "properties": {
            "subject": { "type": "string" },
            "folder": { "type": "string" },
            "unseen_only": { "type": "boolean" },
            "limit": { "type": "integer" }
          }
        }
      }
    }
  ]
}

Because the list is permission-filtered, you can hand the response straight to your agent: everything in it is callable with the same credential, and nothing in it can send mail.

Execute a tool

POST /ai/tools/{name}/call runs one tool. The request body is the tool's JSON argument object, exactly as your model produced it in its tool call; an empty body means no arguments.

curl -s -X POST "https://api.warmbly.com/v1/ai/tools/list_threads/call" \
  -H "Authorization: Bearer wmbly_..." \
  -H "Content-Type: application/json" \
  -d '{"folder": "inbox", "unseen_only": true, "limit": 10}'
{
  "data": {
    "name": "list_threads",
    "result": { "threads": [], "count": 0 }
  }
}

result is the tool's output, embedded as JSON. Feed it back to the model as the tool-call result and continue the loop.

Errors use the standard envelope with a stable code: an unknown tool is 404 not_found, a tool your credential lacks the scope for is 403 forbidden, a malformed argument body is 400 bad_request, and a tool-level failure (a validation message, a missing record) is 422 unprocessable with the tool's own message, which is meant for the model to read and react to.

This is a side-effectful POST, so Idempotency-Key is honored like everywhere else in the API; per-key rate limits and usage logging apply.

Wiring a Hermes agent

Hermes-format models (and anything served with an OpenAI-compatible tool-calling API) take the function schemas in the system prompt or the tools array, then emit tool calls you execute and answer. The loop against Warmbly:

  1. GET /ai/tools?format=hermes once at session start and give the data array to the model as its available tools.
  2. When the model emits a tool call { "name": ..., "arguments": ... }, POST /ai/tools/{name}/call with the arguments object as the body.
  3. Return data.result to the model as the tool response, and repeat.
import json, requests

BASE = "https://api.warmbly.com/v1"
HEADERS = {"Authorization": "Bearer wmbly_..."}

tools = requests.get(f"{BASE}/ai/tools?format=hermes", headers=HEADERS).json()["data"]
# system prompt: "You may call these tools:\n<tools>" + json.dumps(tools) + "</tools>"

def run_tool(call):
    r = requests.post(
        f"{BASE}/ai/tools/{call['name']}/call",
        headers=HEADERS,
        json=call.get("arguments") or {},
    )
    body = r.json()
    return body["data"]["result"] if r.ok else body  # errors are model-readable too

The same two calls back any framework's "custom tool" escape hatch: point the executor at /ai/tools/{name}/call and the discovery step at /ai/tools.

Choosing a surface

You haveUse
Claude Code, Claude Desktop, Cursor, any MCP clientThe MCP server at /v1/mcp
A Hermes / OpenAI-style function-calling agentThis REST surface
The dashboardThe built-in assistant, which runs the same registry as the signed-in member
A terminalwarmblyctl tool list and warmblyctl tool call

All four run the identical registry with identical gates, so a tool behaves the same no matter which door it came through.

See also

  • MCP server for the protocol-native version of this surface
  • Permissions for the scope each tool checks
  • Realtime API to stream events into a long-running agent instead of polling

On this page