---
title: "ToolPiper HTTP API on Mac: Call 300+ Tools from Any Script"
description: "ToolPiper exposes over 300 tools over a local JSON HTTP API at 127.0.0.1:9998. Shell, Python, Node, anywhere. Loopback-only by default, tp_lan tokens for LAN."
date: 2026-05-13
author: "Ben Racicot"
tags: ["HTTP API", "REST API", "Automation", "Privacy", "macOS", "Developer Tools"]
type: "article"
canonical: "https://modelpiper.com/blog/toolpiper-http-api/"
---

# ToolPiper HTTP API on Mac: Call 300+ Tools from Any Script

> ToolPiper exposes over 300 tools over a local JSON HTTP API at 127.0.0.1:9998. Shell, Python, Node, anywhere. Loopback-only by default, tp_lan tokens for LAN.

## TL;DR

ToolPiper's local HTTP API at http://127.0.0.1:9998 is a JSON REST API exposing all 300+ tools. curl, Python requests, fetch from Node, anything that speaks HTTP. Loopback requests are unauthenticated. LAN requests need a tp_lan bearer token. The OpenAPI spec is available at GET /v1/openapi.json.

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.

## FAQ

### Does the HTTP API stream responses?

Most endpoints return a single JSON response. Specific endpoints (chat with streaming enabled, audio transcription with live mode, pose streaming) use Server-Sent Events or websockets where the use case calls for it. The OpenAPI spec marks streaming endpoints explicitly.

### Are there rate limits?

Not on the API surface itself. ToolPiper's tools may rate-limit internally (the browser pool has a concurrency cap, downloaded models can only run so many concurrent inferences), but the HTTP layer accepts requests as fast as you send them.

### Can I use the HTTP API from a different language?

Yes. Anything that speaks HTTP and JSON works. Swift, Go, Rust, Ruby, Java, PHP. We've tested the most common ones. The OpenAPI spec is the canonical schema reference for code generation.

### How do I find the right endpoint for a specific tool?

The endpoint name maps to the tool name. `vision_screenshot` in MCP becomes `POST /vision/screenshot` on HTTP. `audio_transcribe` becomes `POST /audio/transcribe`. The OpenAPI spec at `/v1/openapi.json` is the definitive reference.

### Is the API stable across ToolPiper updates?

The endpoint shapes are stable. New endpoints are added in minor releases; existing endpoint behavior doesn't change without a major version bump. Breaking changes are surfaced in the changelog before the release that introduces them.
