OpenTools docs
Give your agent 203 tools through one connection: documents, media, browsers, AI models, inboxes, people, schedules and proof. Use MCP if your agent supports it, or the REST API from your own code. Both use the same API key and the same tools.
Quickstart
- Create a free account. You get a workspace with 1,000 runs a month.
- Create an API key under API keys. It starts with
ot_live_and is shown once. - Add OpenTools to your agent over MCP, or call the REST API.
curl https://opentools.opentools.workers.dev/v1/jobs \
-H "Authorization: Bearer $OPENTOOLS_KEY" \
-H "Content-Type: application/json" \
-d '{"tool": "pdf.merge", "input": {"files": [{"url": "https://example.com/a.pdf"}, {"url": "https://example.com/b.pdf"}]}}'Connect an agent (MCP)
The MCP endpoint is https://opentools.opentools.workers.dev/mcp (streamable HTTP). Authenticate with your API key as a bearer token. The catalog is too big to list every tool to your agent up front, so it finds them with search_tools, reads one with describe_tool and runs it with run_tool, alongside run_batch, get_batch and get_job. To list tools directly, add ?tools= to the URL with ids, prefixes, categories or a use-case bundle (?tools=pdf.*,images, ?tools=bundle:web-research), or ?tools=all. Direct tools are named with dots replaced by underscores (pdf.merge → pdf_merge).
claude mcp add --transport http opentools https://opentools.opentools.workers.dev/mcp \
--header "Authorization: Bearer $OPENTOOLS_KEY"{
"mcpServers": {
"opentools": {
"url": "https://opentools.opentools.workers.dev/mcp",
"headers": {
"Authorization": "Bearer ot_live_…"
}
}
}
}{
"mcpServers": {
"opentools": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://opentools.opentools.workers.dev/mcp",
"--header",
"Authorization: Bearer ot_live_…"
]
}
}
}REST API
Base URL https://opentools.opentools.workers.dev/v1. Send Authorization: Bearer <key> on every request. Bodies are JSON.
?include=schema for each tool’s JSON input schema. No key needed.{ "tool": "…", "input": { … }, "wait": true }. With wait: true (the default) the response is the finished job. With wait: false you get 202 and a queued job to poll.status, tool, limit (max 100), before (an ISO timestamp, for paging).{
"id": "5d0c…",
"object": "job",
"tool": "image.remove_bg",
"status": "succeeded",
"duration_ms": 1840,
"attempts": 1,
"credits": 2,
"output": {
"data": {
"width": 2400,
"height": 3200,
"transparent": true
},
"files": [
{
"id": "a81f…",
"name": "bottle-cutout.png",
"content_type": "image/png",
"size_bytes": 1203344,
"url": "https://…signed…",
"expires_at": "…"
}
]
},
"error": null
}Files
Anywhere a tool takes a file, pass one of:
{ "url": "https://…" }— a public link. Best for agents.{ "file_id": "…" }— a file you uploaded toPOST /v1/files.{ "base64": "…", "name": "scan.pdf" }— inline bytes, up to 10 MB.
curl https://opentools.opentools.workers.dev/v1/files \
-H "Authorization: Bearer $OPENTOOLS_KEY" \
-F file=@contract.pdfOutput files come back with signed download links that last one hour; call GET /v1/jobs/:id for fresh ones. Inputs and outputs are deleted after your workspace’s retention period (24 hours by default, up to 30 days).
Drive
Tool files expire. The Drive is where agents keep the ones worth keeping: reports, results and artefacts, in folders, with no expiry. People browse and open them in the app (images, video, PDFs, spreadsheets, Markdown and text all preview in the browser), and a Drive file’s id works as { "file_id": "…" } input to any tool.
Over MCP, use drive_save, drive_list, drive_read, drive_move and drive_delete. Paths look like reports/2026/q3.csv, and missing folders are created. When a name is taken, if_exists picks rename (the default), replace or fail.
curl https://opentools.opentools.workers.dev/v1/drive \
-H "Authorization: Bearer $OPENTOOLS_KEY" \
-H "Content-Type: application/json" \
-d '{"path": "reports/2026/q3.pdf", "file": {"file_id": "…"},
"description": "Q3 board report"}'curl "https://opentools.opentools.workers.dev/v1/drive?path=reports/2026/q3.csv" \
-H "Authorization: Bearer $OPENTOOLS_KEY" --data-binary @q3.csv
curl "https://opentools.opentools.workers.dev/v1/drive?path=reports&recursive=true" -H "Authorization: Bearer $OPENTOOLS_KEY"
curl "https://opentools.opentools.workers.dev/v1/drive/FILE_ID?text=true" -H "Authorization: Bearer $OPENTOOLS_KEY"Saved files come back with a one-hour download url and an app_url to hand to a person. POST /v1/drive with {"action": "mkdir" | "move", …} organises it, and DELETE /v1/drive?path=… removes a file or a whole folder.
Batches
Run one tool over many inputs: up to 1,000 on Free, 25,000 on Pro and 250,000 on Scale. Every input becomes its own job, queued durably and retried independently, so one bad file never fails the rest.
curl https://opentools.opentools.workers.dev/v1/batches \
-H "Authorization: Bearer $OPENTOOLS_KEY" \
-H "Content-Type: application/json" \
-d '{"tool": "image.remove_bg", "inputs": [
{"file": {"url": "https://example.com/1.jpg"}},
{"file": {"url": "https://example.com/2.jpg"}}
]}'Poll GET /v1/batches/:id for progress (succeeded, failed, pending), or pass webhook_url to be told when it’s done. Every input’s result is at GET /v1/batches/:id/results (JSON, or ?format=csv), in input order; page with ?after=. POST /v1/batches/:id/cancel stops one.
Bulk runs over storage
For thousands of files, don’t send them at all. Point a batch at a folder in a storage connection with from: OpenTools lists it page by page, starts each file as soon as it’s found and, with to, writes every output back. Shared options go in input.
curl https://opentools.opentools.workers.dev/v1/batches \
-H "Authorization: Bearer $OPENTOOLS_KEY" \
-H "Content-Type: application/json" \
-d '{
"tool": "doc.ocr",
"name": "Q3 scans",
"from": { "connection": "archive", "path": "scans/2026", "match": ["*.pdf", "*.tiff"] },
"input": { "language": "eng" },
"to": { "connection": "archive", "path": "searchable/{dir}/{stem}.pdf", "overwrite": "skip" },
"webhook_url": "https://example.com/hooks/opentools"
}'matchtakes patterns like*.pdf; one with a slash matches the whole path, and**crosses folders.recursive(default true) includes subfolders.- In
to.path,{dir}is the file’s folder underfrom.path, so the output mirrors the source tree.{name},{stem},{ext},{date}and{job}work too. overwriteisrename(default),skiporoverwrite. A results CSV is written next to the outputs unlessto.resultsis false.- While the folder is being read, the batch has
listing: trueandtotalkeeps growing. Jobs run in parallel: 20 at once on Free, 50 on Pro, 200 on Scale.
Agents do the same with run_batch over MCP: pass from and to instead of inputs.
Webhooks
When a batch finishes (succeeded, failed or canceled), OpenTools POSTs { "type": "batch.finished", "data": <batch> } to its webhook_url, retrying with backoff until it gets a 2xx. The batch’s response includes a webhook.secret once; each delivery is signed with it in the OpenTools-Signature header.
import { createHmac, timingSafeEqual } from "node:crypto";
// header: "t=1767225600,v1=5f2b…"
function verify(secret, rawBody, header) {
const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}Errors & retries
Errors are JSON: { "error": { "code": "invalid_input", "message": "…" } }. Bad input (invalid_input, not_found) fails straight away. Transient problems — a slow source URL, a busy service — are retried three times with backoff before a job is marked failed. You’re only charged for runs that succeed.
Limits
- 100 MB per file (10 MB inline base64)
- 1,000 inputs per batch on Free, 25,000 on Pro, 250,000 on Scale
- Free workspaces: 1,000 runs a month
- Results kept 24 hours by default