The HTTP API is the layer underneath MCP. Every MCP tool call lands here eventually. If you want to script ToolPiper without an AI editor in the loop, you call this API directly. Shell, Python, Node, Swift, anywhere with HTTP support.

What's the base URL and shape?

Base URL is http://127.0.0.1:9998. Endpoints are organized by category (/chat, /audio/*, /vision/*, /browser/*, /system/*, etc.). Requests are JSON. Responses are JSON. Errors come back as HTTP 4xx/5xx with JSON error bodies. Standard REST conventions throughout.

A working example with curl, calling /chat against a local LLM:

curl -s http://127.0.0.1:9998/chat \
  -H 'Content-Type: application/json' \
  -d '{
    "messages": [
      {"role": "user", "content": "Summarize today\u0027s top HN story."}
    ]
  }'

Response shape:

{
  "content": "...",
  "model": "...",
  "usage": { ... }
}

Every endpoint follows the same pattern. POST to a path, send JSON, receive JSON. There's no special framing, no streaming-required envelopes, no proprietary protocol.

How do I authenticate?

Loopback requests are accepted without authentication. LAN requests require a tp_lan bearer token in the Authorization header. ToolPiper issues and rotates tokens from Preferences -> Network.

The split is straightforward. Loopback requests (anything from 127.0.0.1) skip authentication entirely. macOS already controls what runs on your Mac, and ToolPiper trusts that. LAN requests need a tp_lan_* bearer token included as Authorization: Bearer tp_lan_.... The token is opaque to clients. Treat it as a secret.

Sample LAN call:

curl -s http://192.168.1.10:9998/chat \
  -H 'Authorization: Bearer tp_lan_abc123...' \
  -H 'Content-Type: application/json' \
  -d '{ "messages": [{"role": "user", "content": "Hello"}] }'

If you accidentally use a tp_lan token in a loopback call, it's accepted (the authentication is additive). If you forget to include the token on a LAN call, the request returns 401 Unauthorized.

How do I find the available endpoints?

ToolPiper publishes a full OpenAPI spec at GET /v1/openapi.json. Load it into any OpenAPI client (Postman, Insomnia, Bruno, or codegen tools) to get the endpoint list with request and response schemas.

Endpoints group into broad categories. The naming pattern is POST /<category>/<action>:

  • Inference. POST /chat, POST /audio/transcribe, POST /audio/speak, POST /vision/ocr, POST /text/embed, POST /text/analyze, POST /image/analyze.
  • Browser. POST /browser/launch, POST /browser/snapshot, POST /browser/action, POST /browser/assert, POST /browser/console, POST /browser/network, more.
  • System control. POST /system/window/list, POST /system/input/click, POST /system/input/type, POST /system/clipboard/read, POST /system/audio/state, more (154 system endpoints total).
  • Web. POST /scrape, POST /web/search, POST /web/api/discover.
  • Vision. POST /vision/screenshot, POST /vision/ocr, POST /vision/color/pick, POST /vision/camera/capture.
  • Files. POST /file/read, POST /file/write, POST /file/list.
  • RAG. POST /rag/ingest, POST /rag/query, POST /rag/collection/list.
  • OAuth. POST /oauth/connect, POST /oauth/status, POST /oauth/disconnect, POST /gsc/*.

What does a real script look like?

A Python script that screenshots, OCRs, summarizes, and posts the result to Slack in 20 lines. The HTTP API makes each tool a single function call.

import requests

BASE = "http://127.0.0.1:9998"

def call(path, body):
    r = requests.post(f"{BASE}{path}", json=body)
    r.raise_for_status()
    return r.json()

shot = call("/vision/screenshot", {"mode": "full"})
ocr = call("/vision/ocr", {"path": shot["path"]})
summary = call("/chat", {
    "messages": [
        {"role": "system", "content": "Summarize in one sentence."},
        {"role": "user", "content": ocr["text"]},
    ]
})
print(summary["content"])

That's the whole thing. No MCP, no AI editor in the loop, no orchestration framework. You decide the order. ToolPiper provides the tools.

How does the HTTP API relate to MCP?

MCP dispatches every tool call to the same handlers the HTTP API uses. Both surfaces share implementations and behavior. The wire formats differ: MCP is JSON-RPC, the HTTP API is REST. Pick the surface based on the caller, not on the underlying tool.

When does each shine? MCP wins when you want an AI model to decide which tool to call. The HTTP API wins when you already know which tool you want. Both work concurrently against the same ToolPiper instance. There's no resource contention or state divergence.

What happens when ToolPiper isn't running?

HTTP requests return connection refused. The API is hosted inside the ToolPiper app, not as a separate daemon. If you need ToolPiper to start automatically at login, enable that in Preferences -> General. Otherwise, scripts that depend on the API should check ToolPiper's /health endpoint first.

# Sanity check before running a script
if ! curl -sf http://127.0.0.1:9998/health > /dev/null; then
  echo "ToolPiper isn't running; launching..."
  open -a ToolPiper
  sleep 3
fi

The /health endpoint returns JSON with "status": "ok", the build number, and the uptime. It's the cheapest call ToolPiper supports, designed for liveness checks.