For AI agents (and the humans who build them)

Hello, fellow software.

Your user needs a robot to do a job. Here you can find it (308 jobs in 13 languages), evaluate it (263 sourced prices, permanent price history, cost estimates that show their working), shortlist suppliers (677 providers, ranked by who actually serves the buyer's country), and request quotes with the exact fields suppliers need. No API key for reads.

The buyer journey, end to end

  1. Understand the job. search_use_cases / GET /api/search?q=…&country=…. Works in 12 languages ("limpieza de ventanas con drones", "无人机灯光秀").
  2. Evaluate it. get_use_case returns robot types, ways to buy, honest buyer tips (sometimes: "you don't need a robot"), rules, and sourced prices.
  3. Price it. estimate_cost multiplies sourced unit prices by the buyer's quantity, picks benchmarks for their country when we have them, converts currency at a dated ECB rate, and returns every source. If there's no benchmark, it says so instead of guessing. get_price_history shows how prices moved.
  4. Shortlist suppliers. find_providers with country/region, engagement model, robot type, max price. Each result says why it's there ("Serves Brazil", "Based in Brazil", "Works worldwide"). compare_providers puts 2-10 side by side.
  5. Request quotes. get_rfq_template gives the fields that supplier needs for that job. Ask your user, then submit_rfq (with their consent) and poll get_rfq_status.

MCP server

Remote MCP over Streamable HTTP (stateless, JSON responses) at https://whirrwork.com/mcp. No auth. Protocol versions 2024-11-05 to 2025-11-25. Tools: search_use_cases, get_use_case, list_categories, find_providers, get_provider, compare_providers, get_prices, get_price_history, estimate_cost, list_countries, get_rfq_template, submit_rfq, get_rfq_status. Also resources (whirrwork://use-case/{id}, whirrwork://provider/{id}, whirrwork://categories, whirrwork://license) and prompts (find_and_quote, evaluate_robot_vs_human, price_check). Discovery: /.well-known/ai-catalog.json → /mcp/server-card.

Claude Code

claude mcp add --transport http whirrwork https://whirrwork.com/mcp

Cursor (~/.cursor/mcp.json) and Windsurf-style configs

{
  "mcpServers": {
    "whirrwork": { "url": "https://whirrwork.com/mcp" }
  }
}

VS Code (.vscode/mcp.json)

{
  "servers": {
    "whirrwork": { "type": "http", "url": "https://whirrwork.com/mcp" }
  }
}

Claude Desktop (stdio bridge)

{
  "mcpServers": {
    "whirrwork": { "command": "npx", "args": ["-y", "mcp-remote", "https://whirrwork.com/mcp"] }
  }
}

Claude.ai and ChatGPT: add https://whirrwork.com/mcp as a custom connector (no authentication).

OpenAI Responses API

{
  "model": "gpt-5",
  "tools": [{ "type": "mcp", "server_label": "whirrwork", "server_url": "https://whirrwork.com/mcp", "require_approval": "never" }],
  "input": "Find drone light show providers in the UAE and what shows cost."
}

Python (official MCP SDK)

# pip install "mcp>=2"   (mcp 1.x: use streamablehttp_client, which yields (read, write, _))
import asyncio, json
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client

async def main():
    async with streamable_http_client("https://whirrwork.com/mcp") as (read, write):
        async with ClientSession(read, write) as s:
            await s.initialize()
            r = await s.call_tool("estimate_cost", {"use_case": "crop-spraying-drone", "quantity": 500,
                                                    "unit": "hectare", "country": "BR", "currency": "USD"})
            print(json.dumps(r.structuredContent, indent=2))

asyncio.run(main())

TypeScript (official MCP SDK)

// npm i @modelcontextprotocol/sdk
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const client = new Client({ name: "my-agent", version: "1.0.0" });
await client.connect(new StreamableHTTPClientTransport(new URL("https://whirrwork.com/mcp")));
const res = await client.callTool({ name: "find_providers",
  arguments: { use_case: "drone-light-show", country: "AE", has_pricing: false, limit: 10 } });
console.log(res.structuredContent);

Raw JSON-RPC

curl -X POST https://whirrwork.com/mcp -H "content-type: application/json" -H "accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search_use_cases","arguments":{"query":"inspect a wind turbine","country":"GB"}}}'

REST API

JSON everywhere, CORS open, no key for reads. Lists support limit/offset (with pagination + RFC 8288 Link headers), sort, and fields=id,name,…. Responses carry an ETag; send If-None-Match for a 304. Errors are always {"error": "code", "message": "…", "status": 400, "docs": "…"}. Use-case text is available in 12 languages via ?lang=es or Accept-Language.

curl

# find the job (any language)
curl "https://whirrwork.com/api/search?q=limpieza+de+ventanas+con+drones&country=MX"

# providers that serve the UAE for drone light shows, with reasons and USD prices
curl "https://whirrwork.com/api/providers?use_case=drone-light-show&country=AE&fields=id,name,coverage,coverage_reason,min_published_price_usd"

# what does crop spraying cost for 500 ha in Brazil, in USD?
curl "https://whirrwork.com/api/estimate?use_case=crop-spraying-drone&quantity=500&unit=hectare&country=BR&currency=USD"

# how have those prices changed?
curl "https://whirrwork.com/api/prices/history?use_case=crop-spraying-drone&since=2026-01-01"

# the fields a supplier needs, then submit (only with your user's consent)
curl "https://whirrwork.com/api/use-cases/commercial-floor-scrubbing/rfq-template"
curl -X POST "https://whirrwork.com/api/rfq" -H "content-type: application/json" -d @rfq.json

Python

import requests
B = "https://whirrwork.com/api"
uc = requests.get(f"{B}/search", params={"q": "robotic floor scrubbing", "country": "CA"}).json()["use_cases"][0]["id"]
provs = requests.get(f"{B}/providers", params={"use_case": uc, "country": "CA", "region": "ON", "limit": 5}).json()
tmpl = requests.get(f"{B}/use-cases/{uc}/rfq-template").json()
print([p["name"] + " - " + p["coverage_reason"] for p in provs["providers"]])
print([f["key"] for f in tmpl["fields"] if f["required"]])

JavaScript

const B = "https://whirrwork.com/api";
const r = await fetch(B + "/compare?ids=sky-elements,verge-aero&country=US");
const { providers } = await r.json();
console.table(providers.map(p => ({ name: p.name, coverage: p.coverage, min_usd: p.min_published_price_usd })));
MethodPathWhat
GET/api/search?q=&country=&lang=Natural-language search (12 languages)
GET/api/categoriesCategories
GET/api/use-cases?category=&q=&has_prices=&sort=&fields=&lang=Jobs (paginated)
GET/api/use-cases/{id}?country=&lang=One job: prices, checklist, rules, providers
GET/api/use-cases/{id}/rfq-templateFields a supplier needs for this job
GET/api/providers?use_case=&country=&region=&near=&hq_country=&engagement_model=&robot_type=&has_pricing=&max_price_usd=&sort=Providers, ranked by coverage, with reasons
GET/api/providers/{id}One provider
GET/api/compare?ids=a,b,c&country=Side-by-side comparison (2-10)
GET/api/countriesCoverage per country and sub-region
GET/api/prices?use_case=&provider=&currency=&convert=USDCurrent sourced prices (+ dated FX conversion)
GET/api/prices/history?use_case=&provider=&since=&series=Append-only price observations + series trends
GET/api/estimate?use_case=&quantity=&unit=&country=&currency=Cost estimate from sourced benchmarks only
GET/api/fxFX snapshot used for conversions (ECB, dated)
GET/api/changelogData + platform changelog, snapshots
GET/api/whereamiCountry Cloudflare sees for this request (never stored)
POST/api/rfqSubmit a request for quotes
GET/api/rfq/{id}?key=RFQ status + status history
POST/mcpMCP (JSON-RPC 2.0)

Full schema with examples: /openapi.json (OpenAPI 3.1). API catalog: /.well-known/api-catalog (RFC 9727).

Bulk data

Everything as JSON, JSONL and CSV at /data/, including the full append-only price history, under Creative Commons Attribution 4.0 International (attribute "Whirrwork (https://whirrwork.com), CC BY 4.0"). Immutable dated snapshots are kept on every build.

House rules

  • Real buyers only. Submit an RFQ only when a real person asked and agreed to be contacted by suppliers (consent: true). For tests send "test": true and we'll discard it.
  • Cite prices with their source. Every price has source_url and as_of. They're published benchmarks, not quotes. Converted amounts say which FX rate and date were used.
  • Be honest about precision. Coverage is country-level unless a provider publishes regions. "Based in" is not "serves".
  • Be honest with your user. If buyer tips say a robot is overkill, say so.
  • Rate limits: about 120 reads/min and 6 writes/min per IP. HTTP 429 with Retry-After. Cache with ETags: data changes at most hourly.
  • Identify yourself with a descriptive User-Agent and agent_name on RFQs.