Getting started

MCP server

Connect Claude, Cursor or any MCP client: the Streamable HTTP endpoint, the stdio bridge, and the nine tools.

Arettic is an MCP server. Claude Desktop, Claude Code, Cursor or your own agent can ask which tool works for a task, buy the result, and read the receipt through nine tools. This page covers the two ways to connect, the client configs, every tool with its arguments, a typical session, test mode, errors, and the server card.

Before launch, agent keys go to design partners; everyone else can join the waitlist. The @arettic/mcp npm package is published at launch. The hosted endpoint and the tools on this page are the ones the package will forward to.

What you need

An agent key. Test keys start with sk_test_ and are free: they buy from mock tools and use no credits. Live keys start with sk_live_ and buy real results with your org's credits. Org keys (ok_…) are for the org API and don't work here, because the MCP tools act as an agent.

To get a key, sign in to the dashboard, create an org, and add an agent in test mode. The key is shown once. The quickstart walks through it, and Authentication and keys explains the three kinds of credential.

Two ways to connect

The hosted server speaks Streamable HTTP at https://api.arettic.com/mcp. A client that supports Streamable HTTP with a custom header connects to it directly. A client that only runs local stdio servers uses the bridge, @arettic/mcp, a small Node program that forwards every tools/list and tools/call to the hosted server.

Both paths give the same tools with the same names, descriptions and schemas, because the bridge defines none of its own. Pick the hosted endpoint when you can send a header; pick the bridge when your client wants a command to run.

The hosted endpoint

Send JSON-RPC messages as POST https://api.arettic.com/mcp. Three headers matter:

The server is stateless: it builds a fresh session for every POST. There is no Mcp-Session-Id header, no initialize handshake is needed before tools/call, and every request carries the key. Responses are plain JSON, never a stream. GET /mcp and DELETE /mcp answer HTTP 405 with Allow: POST and the error code method_not_allowed, because there are no server-initiated streams and no sessions to end.

Each key can make 600 requests a minute to /mcp. Every response carries RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset and RateLimit-Policy; over the limit you get HTTP 429 with Retry-After. See Rate limits and quotas.

curl
curl https://api.arettic.com/mcp \
  -H "Authorization: Bearer $ARETTIC_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"recommend","arguments":{"task_type":"find_email"}}}'
Response (example)
{
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\n  \"task_type\": \"find_email\",\n  \"region\": \"GLOBAL\",\n  \"sort\": \"score\",\n  \"test_mode\": true,\n  …\n}"
      }
    ]
  },
  "jsonrpc": "2.0",
  "id": 1
}

Every tool answers with one text content item, and that text is JSON: the same body the matching REST endpoint returns. Parse it. When the API refused the call, the result also carries isError: true and the text is the API's error body (see Errors and retries). tools/list returns the nine tools with their JSON Schemas.

The stdio bridge

@arettic/mcp runs as a child process of your MCP client. It reads your key from the ARETTIC_API_KEY environment variable, connects to the hosted server, and forwards every tools/list and tools/call. It needs Node 18 or newer. The package is published to npm at launch; until then the command below has nothing to download.

The command a client runs
ARETTIC_API_KEY=sk_test_… npx -y @arettic/mcp
Environment variables the bridge reads
VariableRequiredDefaultWhat it does
ARETTIC_API_KEYyesAn agent key: sk_test_… or sk_live_….
ARETTIC_API_URLnohttps://api.arettic.comThe API host. The bridge talks to /mcp on it.

If the key is missing, is an org key, or the API refuses it, the bridge writes one line to stderr saying what to do and exits with code 1. It never prints the key. If the API is merely unreachable at startup, the bridge still starts and retries on the first call. All logging goes to stderr; stdout carries only MCP messages.

Use the bridge from code

The package exports createStdioProxy, so you can embed the bridge in your own process or serve it on another MCP transport. Options: apiKey (required), baseUrl (default https://api.arettic.com), timeoutMs (default 5 minutes per request), log (a function; default stderr) and fetch.

TypeScript
import { createStdioProxy } from "@arettic/mcp";

const proxy = createStdioProxy({ apiKey: process.env.ARETTIC_API_KEY! });
await proxy.start(); // serves on stdin/stdout; pass any MCP Transport to serve elsewhere

Client configs

Each block below carries a placeholder key; paste your own. The stdio blocks need Node on the machine that runs the client. A test key is the safe default: swap in a live key when you're ready to spend credits.

Claude Desktop

Open Settings, then Developer, then Edit Config, and add this to claude_desktop_config.json. Claude Desktop starts the bridge for you.

claude_desktop_config.json
{
  "mcpServers": {
    "arettic": {
      "command": "npx",
      "args": [
        "-y",
        "@arettic/mcp"
      ],
      "env": {
        "ARETTIC_API_KEY": "sk_test_…"
      }
    }
  }
}

Claude Code

Terminal
# The stdio bridge
claude mcp add arettic --env ARETTIC_API_KEY=sk_test_… -- npx -y @arettic/mcp

# Or the hosted endpoint directly
claude mcp add --transport http arettic https://api.arettic.com/mcp --header "Authorization: Bearer sk_test_…"

Or commit a .mcp.json to your project with the same mcpServers block as the Claude Desktop config, so the whole team gets the server.

Cursor

Add the server to ~/.cursor/mcp.json (all projects) or .cursor/mcp.json (one project). Either the bridge:

mcp.json (bridge)
{
  "mcpServers": {
    "arettic": {
      "command": "npx",
      "args": [
        "-y",
        "@arettic/mcp"
      ],
      "env": {
        "ARETTIC_API_KEY": "sk_test_…"
      }
    }
  }
}

or the hosted endpoint:

mcp.json (hosted endpoint)
{
  "mcpServers": {
    "arettic": {
      "url": "https://api.arettic.com/mcp",
      "headers": {
        "Authorization": "Bearer sk_test_…"
      }
    }
  }
}

Other clients

Any client that supports Streamable HTTP with a custom header can use https://api.arettic.com/mcp with Authorization: Bearer <agent key>; most accept a url plus headers block like the Cursor one. A client that can only launch local servers uses the command block: the bridge works with any host that starts stdio servers.

Call it from code

Your own agent can talk to the hosted endpoint with any MCP client library, or with plain HTTP. Both samples below run recommend, then execute, then fetch the receipt when there is one. If you'd rather skip MCP, the SDKs call the REST API directly.

TypeScript (@modelcontextprotocol/sdk)
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const transport = new StreamableHTTPClientTransport(new URL("https://api.arettic.com/mcp"), {
  requestInit: { headers: { Authorization: `Bearer ${process.env.ARETTIC_API_KEY}` } },
});
const mcp = new Client({ name: "my-agent", version: "1.0.0" });
await mcp.connect(transport);

// Every tool answers with one text item holding JSON.
const body = (r: { content?: unknown }) => JSON.parse((r.content as { text: string }[])[0].text);

const rec = await mcp.callTool({ name: "recommend", arguments: { task_type: "find_email" } });
const top = body(rec).options.find((o: { purchasable: boolean }) => o.purchasable);

const bought = await mcp.callTool({
  name: "execute",
  arguments: {
    tool_id: top.tool_id,
    input: { first_name: "Emily", last_name: "Carter", domain: "example.com" },
  },
});
const outcome = body(bought);
if (bought.isError) throw new Error(`${outcome.error.code}: ${outcome.error.message}`);
console.log(outcome.status, outcome.result); // passed { email: "[email protected]", … }

if (outcome.receipt_id) {
  // Live keys only: test purchases write no receipt.
  const receipt = await mcp.callTool({
    name: "get_receipt",
    arguments: { receipt_id: outcome.receipt_id },
  });
  console.log(body(receipt));
}
await mcp.close();
Python (standard library)
import json
import os
import urllib.request

API = "https://api.arettic.com"
KEY = os.environ["ARETTIC_API_KEY"]


def call_tool(name, arguments, rpc_id=1):
    body = json.dumps({
        "jsonrpc": "2.0",
        "id": rpc_id,
        "method": "tools/call",
        "params": {"name": name, "arguments": arguments},
    }).encode()
    req = urllib.request.Request(
        f"{API}/mcp",
        data=body,
        headers={
            "Authorization": f"Bearer {KEY}",
            "Content-Type": "application/json",
            "Accept": "application/json, text/event-stream",
        },
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=300) as res:
        result = json.load(res)["result"]
    data = json.loads(result["content"][0]["text"])
    if result.get("isError"):
        raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
    return data


rec = call_tool("recommend", {"task_type": "find_email"})
top = next(o for o in rec["options"] if o["purchasable"])
bought = call_tool("execute", {
    "tool_id": top["tool_id"],
    "input": {"first_name": "Emily", "last_name": "Carter", "domain": "example.com"},
}, rpc_id=2)
print(bought["status"], bought.get("result"))
if bought.get("receipt_id"):  # live keys only
    print(call_tool("get_receipt", {"receipt_id": bought["receipt_id"]}, rpc_id=3))

In the Python sample an HTTP-level refusal (401, 405, 406, 429) raises urllib.error.HTTPError; a tool error comes back inside the result and raises the RuntimeError. The MCP SDK client behaves the same way: transport failures throw, tool errors come back as results with isError.

The nine tools

Tool names, argument names and descriptions come from the hosted server, and tools/list returns them with JSON Schemas. Each tool runs the same code as its REST endpoint, so its answer has the same fields; the pages linked below describe those fields one by one. Money is in credits: 1 credit is $0.001, and every amount comes back as { "credits": "38", "usd": "0.038" }.

The nine tools and their REST equivalents
ToolWhat it doesSame as
list_task_typesThe task types, each with its pass rule, input and output.GET /v1/task-types
recommendRanked tools for a task: score, every score input, price per success, pass rule.POST /v1/recommend
get_toolOne tool in detail: scores by region, history, latest benchmark, schemas.GET /v1/tools/{id}
executeRun a tool (one input, or up to 1,000 as inputs; over 25 runs as a job); charged only if the result passes its check. Test keys use mock providers.POST /v1/execute
get_jobStatus and per-item results of a batch of more than 25 items.GET /v1/jobs/{id}
get_receiptThe receipt for a purchase: cost, check, outcome and refund.GET /v1/receipts/{id}
open_disputeDispute a charged item within 7 days (by receipt_id and item_index). Decided within 48 hours against the stored result; upheld means a refund.POST /v1/disputes
get_approvalStatus of a purchase waiting for approval.GET /v1/approvals/{id}
get_balanceThe agent's budget and the org's credit balance.GET /v1/balance

list_task_types

No arguments. Returns task_types, one entry per task type with task_type, pass_rule (for example find_email@v1), passes_if, input and output, plus the open-data license. The same list is on Pass rules.

recommend

Ranked tools for one task. Send task_type or a plain-language task. Ranking is by score, then price when scores are within 2 points; whether a tool can be bought never changes its rank, it's the purchasable field. Up to 10 options come back. Every field is described on Recommend.

Arguments of recommend
ArgumentTypeRequiredNotes
task_typestringone of task_type or taskOne of find_email, verify_email, enrich_company, enrich_person, web_search, extract_url.
taskstring, up to 500 charactersone of task_type or taskPlain language, mapped to a task type by a keyword classifier. The answer says what it chose in mapped_from_task. If nothing matches, the error is unknown_task_type. Each free-text call uses one of your plan's daily free-text lookups.
regionstring, up to 10 charactersnoA region code such as US; default GLOBAL. Tools that cover GLOBAL always match.
max_pricenumbernoMost credits per success you'll accept. Costlier tools are left out.
min_scorenumber, 0 to 100noTools scoring below this are left out.
sortstringnoscore (default), price, value or latency. Within 2 points of score, price breaks the tie.
MCP tool call
{ "name": "recommend", "arguments": { "task_type": "find_email" } }
Response (example, test key)
{
  "task_type": "find_email",
  "region": "GLOBAL",
  "sort": "score",
  "test_mode": true,
  "formula_version": "v1",
  "ranking": "score, then price when scores are within 2 points; purchasability never changes rank",
  "options": [
    {
      "tool_id": "mock-find-email",
      "name": "Mock Email Finder",
      "provider": "Arettic Mock Provider",
      "score": 56.53,
      "score_week": "2026-09-28",
      "score_inputs": { "A": 0.4902, "S": 0.5958, "P": 0.4902, "R": 0.7225, "L": 1, "D": 0 },
      "sample_size": 10,
      "price_per_success": { "credits": "38", "usd": "0.038" },
      "success_rate": 0.5958,
      "p50_latency_ms": 5,
      "pass_rule": "find_email@v1",
      "purchasable": true,
      "regions": ["GLOBAL", "US"]
    }
  ]
}

With a task instead of a task_type, the answer adds mapped_from_task with from, confidence and alternatives. A live key sees real tools here; a test key sees the mock ones.

get_tool

One tool in detail. The only argument is tool_id (a string of up to 80 characters: the id from recommend, such as mock-find-email). Returns tool (name, provider, task type, regions, purchasable, price_per_success, prices_by_plan, the pass_rule with its text, the input and output schemas, test_mode, live_calls_available), scores by region with every score input, score_history, and latest_benchmark. Unknown ids answer unknown_tool. See Public data and Scores.

execute

Buys a result. The tool is run, the result is checked against the task type's published pass rule, and you're charged only if it passes. The full flow, every status and every field are on Execute.

Arguments of execute
ArgumentTypeRequiredNotes
tool_idstring, up to 80 charactersyesFrom recommend.
inputobjectone of input or inputsOne input in the task type's input shape (see list_task_types).
inputsarray of objects, up to 1,000one of input or inputsUp to 25 run now and answer with per-item results. More than 25 answer status: "queued" with a job_id for get_job. With a test key every size runs now.
max_pricenumbernoThe most you'll pay in total, in credits. If the price (price per success times inputs) is above it, nothing runs: price_above_max.
fallbackbooleannoIf an item fails, try the next-ranked tool once. Both attempts go on the receipt.
approval_idstring, up to 40 charactersnoFrom a status: "approval_required" answer, once an owner has approved it.
idempotency_keystring, up to 200 charactersnoThe same key with the same request returns the same answer and never charges twice. The bridge adds one (mcp-…) when you don't send one.
MCP tool call
{
  "name": "execute",
  "arguments": {
    "tool_id": "mock-find-email",
    "input": { "first_name": "Emily", "last_name": "Carter", "domain": "example.com" }
  }
}
Response (example, test key, passed)
{
  "test_mode": true,
  "tool_id": "mock-find-email",
  "task_type": "find_email",
  "check_version": "v1",
  "charged": { "credits": "0", "usd": "0.000" },
  "status": "passed",
  "result": { "email": "[email protected]", "verification_status": "valid" },
  "would_have_charged": { "credits": "38", "usd": "0.038" }
}
Response (example, test key, failed)
{
  "test_mode": true,
  "tool_id": "mock-verify-email",
  "task_type": "verify_email",
  "check_version": "v1",
  "charged": { "credits": "0", "usd": "0.000" },
  "status": "failed",
  "reason": "status:unknown",
  "would_have_charged": { "credits": "0", "usd": "0.000" }
}

The status of one input is passed, partial (charged pro rata, with a reason) or failed (a reason, no result, nothing charged). A live key also gets execution_id, receipt_id, refunded and the real charged amount. A batch of up to 25 answers status: "completed" with a summary and per-item items. Two answers ask you to come back later: status: "queued" (with job_id, items, max_charge, poll and a message) when the batch is over 25 items, and status: "approval_required" (with approval_id, reason, amount, expires_at and a message) when the purchase is over the agent's approval threshold or monthly budget.

get_job

The only argument is job_id. Returns job_id, status (queued, running, done, failed or expired), items, progress, created_at, started_at and finished_at. Once the job has its receipt the answer also carries receipt_id, tool_id, task_type, check_version, charged, refunded, summary and the per-item items with results. A job you didn't start answers not_found. See Batches and jobs.

get_receipt

The only argument is receipt_id. Returns the receipt: what was bought, from which provider, what it cost, the result's hash, the check and its version, the outcome and the refund, per item. Any agent of the org can read the org's receipts. Receipts never change; refunds from upheld disputes are added on top. Every field is on Receipts.

open_dispute

Disputes a charged item. Only charged items can be disputed, within 7 days of the purchase, one dispute per item. Arettic decides within 48 hours against the stored copy of the result; a dispute not decided in time is upheld, and an upheld dispute refunds the item's charge. Test keys can't dispute: there was no charge. See Disputes.

Arguments of open_dispute
ArgumentTypeRequiredNotes
receipt_idstring, up to 40 charactersyesThe receipt the item is on.
item_indexinteger, 0 or morenoWhich item on the receipt. Default 0.
reasonstringyeswrong_result, invalid_result, stale_result, duplicate_charge or other.
evidencestring, up to 4,000 characterswhen reason is otherWhat's wrong with the result.

Returns dispute_id, status (open at first), receipt_id, item_index, execution_id, tool_id, reason, evidence, charged, refunded, opened_at, decide_by and decided_at. An item that wasn't charged answers not_disputable; one older than 7 days answers dispute_window_closed.

get_approval

The only argument is approval_id, from an approval_required answer. Returns approval_id, status (pending, approved, rejected, expired or used), reason (over_threshold or over_budget), agent, tool, items, amount, expires_at, decided_at and decision_note. Approvals expire 24 hours after they're requested. Poll it until the status changes, then call execute again with the same tool_id and input plus the approval_id. See Budgets and approvals.

get_balance

No arguments. Returns the org's balance and the agent's monthly budget.

Response (example)
{
  "org_balance": {
    "paid": { "credits": "0", "usd": "0.000" },
    "trial": { "credits": "0", "usd": "0.000" },
    "total": { "credits": "0", "usd": "0.000" }
  },
  "agent_spent_month": { "credits": "0", "usd": "0.000" },
  "agent_budget": { "credits": "50000", "usd": "50.000" },
  "agent_budget_left": { "credits": "50000", "usd": "50.000" }
}

A typical session

Three calls: recommend to pick a tool, execute to buy the result, get_receipt to see what happened. A test key covers the first two exactly as shown; the receipt needs a live key.

  1. Call recommend with the task type. Take the first option whose purchasable is true and keep its tool_id and price_per_success.
  2. Call execute with that tool_id and one input. Read status: passed gives result and charged; failed gives reason and charges nothing. If the answer is approval_required, poll get_approval until it's approved, then call execute again with the approval_id. If it's queued, poll get_job with the job_id.
  3. With a live key the answer includes receipt_id. Call get_receipt with it. The receipt and get_balance agree: the balance drops by exactly charged.
Step 1: MCP tool call
{ "name": "recommend", "arguments": { "task_type": "verify_email" } }
Step 2: MCP tool call
{
  "name": "execute",
  "arguments": { "tool_id": "mock-verify-email", "input": { "email": "[email protected]" } }
}
Response (example, test key)
{
  "test_mode": true,
  "tool_id": "mock-verify-email",
  "task_type": "verify_email",
  "check_version": "v1",
  "charged": { "credits": "0", "usd": "0.000" },
  "status": "passed",
  "result": { "email": "[email protected]", "status": "valid" },
  "would_have_charged": { "credits": "6", "usd": "0.006" }
}

With a live key, recommend ranks the real verify_email tools and execute charges the price of the one you chose. The answer then carries the ids you need for the receipt:

Response (example, live key)
{
  "status": "passed",
  "execution_id": "0f6a9d2c-5b1e-4c7a-9e3d-2a8b7c6d5e4f",
  "receipt_id": "7c2e4b9a-1d3f-4a5b-8c6d-9e0f1a2b3c4d",
  "tool_id": "hunter-verify-email",
  "task_type": "verify_email",
  "check_version": "v1",
  "charged": { "credits": "7", "usd": "0.007" },
  "refunded": { "credits": "0", "usd": "0.000" },
  "result": { "email": "[email protected]", "status": "valid" }
}
Step 3: MCP tool call
{ "name": "get_receipt", "arguments": { "receipt_id": "7c2e4b9a-1d3f-4a5b-8c6d-9e0f1a2b3c4d" } }
Response (example, live key)
{
  "receipt_id": "7c2e4b9a-1d3f-4a5b-8c6d-9e0f1a2b3c4d",
  "created_at": "2026-09-29T10:15:07.412Z",
  "tool_id": "hunter-verify-email",
  "job_id": null,
  "idempotency_key": "mcp-2b6d0f2e-0c4b-4a8e-9d1f-3c5e7a9b1d2f",
  "request_hash": "…",
  "check_version": "v1",
  "charged": { "credits": "7", "usd": "0.007" },
  "refunded": { "credits": "0", "usd": "0.000" },
  "summary": { "items": 1, "passed": 1, "partial": 0, "failed": 0 },
  "items": [
    {
      "index": 0,
      "execution_id": "0f6a9d2c-5b1e-4c7a-9e3d-2a8b7c6d5e4f",
      "tool_id": "hunter-verify-email",
      "provider": "hunter",
      "outcome": "captured",
      "charged": { "credits": "7", "usd": "0.007" },
      "refunded": { "credits": "0", "usd": "0.000" },
      "pricing_mode": "per_success",
      "input_hash": "…",
      "result_hash": "…",
      "check_version": "v1",
      "settled_at": "2026-09-29T10:15:07.398Z"
    }
  ]
}

The three hashes are SHA-256 hex digests of the request, the item's input and the result, shortened here. The idempotency_key is the one the bridge generated; a call over the hosted endpoint without a key shows null there. Ids, times and prices are examples; live prices are on Pricing.

Test mode

With a test key, recommend and execute see the mock tools: one per task type, ids starting with mock-. No provider is called and no credits move. The answer carries test_mode: true, charged of zero, and would_have_charged: what a live purchase of the same result would have cost. The input decides the outcome, so you can rehearse both paths:

Inputs that make a mock tool fail on purpose
Task typeInputOutcome
anyany field contains provider-errorProvider error: status: "failed", reason: "provider_error", nothing charged.
anyany field contains nomatchNo result: fails.
find_emaildomain ends in .catchall.testCatch-all address: fails with reason: "catch_all".
verify_emailemail starts with unknownUnknown status: fails with reason: "status:unknown".
enrich_companydomain is missing-fields.testIndustry missing: fails.
enrich_personcompany_domain contains nocontactNo contact field: fails.
web_searchquery contains few results2 of 5 results: partial, charged pro rata.
extract_urlurl contains blockedCaptcha page: fails.

Everything else passes. For example, mock-verify-email passes [email protected] and fails [email protected]; mock-find-email returns [email protected] for Emily Carter at example.com. Batches work in test mode too and always run inline, up to 1,000 inputs, so a test key never gets a job_id. Test purchases write no receipt, so there is no receipt_id to look up, and open_dispute answers invalid_input ("Test-mode purchases are free; there's nothing to dispute"). Approvals only happen for live keys, so get_approval has nothing to show either.

To go live, create a live agent in the dashboard and swap the key. Nothing else changes. The full test-mode reference, including how mock tools are scored, is on Test mode.

Errors and retries

Tool errors

When the API refuses a call, the tool result has isError: true and its text is the API's error body: code, message, doc_url and retryable. doc_url points at the entry for that code on Error codes; retryable says whether trying again can help. A rate limit adds retry_after_seconds.

Response (example, isError: true)
{
  "error": {
    "code": "unknown_task_type",
    "message": "Couldn't map that task to a task type. Send task_type as one of: find_email, verify_email, enrich_company, enrich_person, web_search, extract_url",
    "doc_url": "https://arettic.com/docs/errors#unknown_task_type",
    "retryable": false
  }
}

The codes you'll meet most through MCP: invalid_input (a field is missing or malformed; the message names it), unknown_task_type, unknown_tool, not_found (a receipt, job or approval that isn't yours), insufficient_credits, over_budget, price_above_max, tool_paused, provider_error (nothing charged; retry or use fallback: true), request_in_progress (retry in a few seconds with the same idempotency_key) and trial_limit.

Protocol errors

A call to a tool that doesn't exist, or arguments that don't match the schema, is answered by the MCP layer rather than the API. The result still has isError: true, but its text is a plain sentence such as MCP error -32602: Tool nope not found or MCP error -32602: Input validation error: Invalid arguments for tool recommend: …. Nothing ran and nothing was charged; fix the call. The bridge passes these through unchanged.

HTTP errors

HTTP-level answers from the hosted endpoint
StatusWhenBody
401No key, an org key, or a revoked key.unauthenticated
403The agent has an IP allowlist and this address isn't on it.ip_not_allowed
405GET or DELETE on /mcp.method_not_allowed, with Allow: POST
406The Accept header lacks application/json or text/event-stream.A JSON-RPC error from the transport
415The Content-Type header isn't application/json.A JSON-RPC error from the transport
429Over 600 requests a minute on this key.rate_limited, with Retry-After

What the bridge does for you

Response (example, bridge, isError: true)
{
  "error": {
    "code": "connection_failed",
    "message": "Couldn't reach the Arettic API at https://api.arettic.com (fetch failed). To retry without paying twice, call execute again with idempotency_key \"mcp-2b6d0f2e-0c4b-4a8e-9d1f-3c5e7a9b1d2f\".",
    "retryable": true,
    "idempotency_key": "mcp-2b6d0f2e-0c4b-4a8e-9d1f-3c5e7a9b1d2f"
  }
}

The server card

GET https://api.arettic.com/.well-known/mcp.json describes the server for clients and directories: the endpoints, how to authenticate, where to get a key, the tools, and where the docs and the OpenAPI document are. No key is needed, and it allows cross-origin reads.

curl
curl https://api.arettic.com/.well-known/mcp.json
Response (example)
{
  "name": "arettic",
  "title": "Arettic",
  "version": "0.3.0",
  "description": "Ask which data tool works for a task, then buy the result through one key and pay only if it passes a published check.",
  "endpoints": {
    "streamable_http": "https://api.arettic.com/mcp",
    "stdio": {
      "command": "npx",
      "args": [
        "-y",
        "@arettic/mcp"
      ],
      "env": [
        "ARETTIC_API_KEY"
      ],
      "status": "published at launch"
    }
  },
  "auth": {
    "type": "bearer",
    "header": "Authorization",
    "format": "Bearer sk_test_… or sk_live_…",
    "get_a_key": "https://arettic.com/docs"
  },
  "tools": [
    {
      "name": "list_task_types",
      "status": "available",
      "description": "The task types, each with its pass rule, input and output."
    },
    {
      "name": "recommend",
      "status": "available",
      "description": "Ranked tools for a task: score, every score input, price per success, pass rule."
    },
    {
      "name": "get_tool",
      "status": "available",
      "description": "One tool in detail: scores by region, history, latest benchmark, schemas."
    },
    {
      "name": "execute",
      "status": "available",
      "description": "Run a tool (one input, or up to 1,000 as inputs; over 25 runs as a job); charged only if the result passes its check. Test keys use mock providers."
    },
    {
      "name": "get_job",
      "status": "available",
      "description": "Status and per-item results of a batch of more than 25 items."
    },
    {
      "name": "get_receipt",
      "status": "available",
      "description": "The receipt for a purchase: cost, check, outcome and refund."
    },
    {
      "name": "open_dispute",
      "status": "available",
      "description": "Dispute a charged item within 7 days (by receipt_id and item_index). Decided within 48 hours against the stored result; upheld means a refund."
    },
    {
      "name": "get_approval",
      "status": "available",
      "description": "Status of a purchase waiting for approval."
    },
    {
      "name": "get_balance",
      "status": "available",
      "description": "The agent's budget and the org's credit balance."
    }
  ],
  "docs": "https://arettic.com/docs",
  "openapi": "https://api.arettic.com/openapi.json"
}

endpoints.stdio.status says published at launch until the npm package is out. version is the hosted server's version, the same one initialize reports in serverInfo.

Where next

Updated 2026-09-29 · This page as Markdown · JSON