Getting started

Quickstart

From a key to a receipt: recommend a tool, execute it, read the result, and see what you were charged.

This page takes you from nothing to a receipt. You get a key, ask which tool works for a task, buy one result, read the answer, and then do the same with a live key. Every step shows curl, the TypeScript SDK and the Python SDK, and the MCP tool where there is one. Start with a test key: it buys from mock tools, so nothing is charged while you build.

Arettic is pre-launch. Keys go to design partners first. Everyone else joins the waitlist at /waitlist and gets an invite when self-serve sign-up opens: a key with $1 of credits, test mode with mock providers, and the MCP server. The @arettic/sdk and @arettic/mcp packages (npm) and the arettic package (PyPI) are published at launch. The curl examples work as written for anyone who has a key.

1. Get a key

  1. Sign in at /app with your work email. You get a one-time link by email. It works once and expires in 15 minutes. The same link creates your account if you're new.
  2. Create an org. Give it a name of 2 to 100 characters. Creating the org gives it $1 of trial credits (1,000 credits) that last 30 days.
  3. Open Agents and add an agent. Pick a name, the mode Test (mock tools, no credits move), a monthly budget (default $50) and when to ask you before a purchase (default: any single purchase over $20). You can't change the mode later, so make a second agent for live when you get there.
  4. Copy the key. It starts with sk_test_ and is shown once, on the same page, in a box that disappears after 2 minutes. Arettic keeps only a SHA-256 hash of it, so it can't show it again. Lost it? Rotate the key on the same page; the old one stops at once.

Put the key in the ARETTIC_API_KEY environment variable. Both SDKs and the MCP bridge read it. Every request to the API sends it as Authorization: Bearer sk_test_….

Shell
export ARETTIC_API_KEY=sk_test_…
# ARETTIC_API_URL is optional. The default is https://api.arettic.com.
Install the SDKs (published at launch)
npm install @arettic/sdk   # TypeScript. No dependencies; needs Node 18+ or any runtime with fetch.
pip install arettic        # Python 3.9+. Standard library only.

Doing this from code instead of the dashboard? A signed-in session or an org key can create agents with POST /v1/orgs/{orgId}/agents; the answer carries the key once. See Authentication and keys and the org API.

2. Ask which tool works

Send a task type. Arettic ranks every tool that can do it by score, then by price when scores are within 2 points. Whether a tool can be bought through Arettic never changes its rank, so pick the first option with purchasable: true. The call is free; it counts against your plan's daily score lookups (1,000 a day on pay as you go).

With a test key the options are mock tools, and their ids start with mock-. With a live key they're real tools.

curl
curl https://api.arettic.com/v1/recommend \
  -H "Authorization: Bearer $ARETTIC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "task_type": "verify_email", "region": "US" }'
TypeScript
import { Arettic } from "@arettic/sdk";

// Reads ARETTIC_API_KEY (and ARETTIC_API_URL) from the environment when you leave them out.
const arettic = new Arettic({ apiKey: process.env.ARETTIC_API_KEY });

const { options } = await arettic.recommend({ task_type: "verify_email", region: "US" });
const tool = options.find((o) => o.purchasable);
if (!tool) throw new Error("No purchasable tool for verify_email");
console.log(tool.tool_id, tool.price_per_success); // "mock-verify-email" with a test key
Python
from arettic import Arettic

client = Arettic()  # reads ARETTIC_API_KEY (and ARETTIC_API_URL, if set)

rec = client.recommend(task_type="verify_email", region="US")
tool_id = next(o["tool_id"] for o in rec["options"] if o["purchasable"])
print(tool_id)  # "mock-verify-email" with a test key

From an MCP client, connect once and the model calls the recommend tool itself. With Claude Code, add the stdio bridge (published at launch). Any client that speaks Streamable HTTP can instead connect to https://api.arettic.com/mcp with the same Authorization: Bearer header. Claude Desktop and Cursor configs are on MCP server.

Claude Code
claude mcp add arettic --env ARETTIC_API_KEY=sk_test_… -- npx -y @arettic/mcp
MCP tool call
{ "name": "recommend", "arguments": { "task_type": "verify_email", "region": "US" } }

The MCP recommend tool takes task_type or a plain-language task, plus region, max_price, min_score and sort, all as top-level arguments. Over REST, max_price and min_score go inside constraints.

Response (example)
{
  "task_type": "verify_email",
  "region": "US",
  "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-verify-email",
      "name": "Mock Email Verifier",
      "provider": "Arettic Mock Provider",
      "score": 62.87,
      "score_week": "2026-09-28",
      "score_inputs": { "A": 0.5958, "S": 0.5958, "P": 0.5958, "R": 0.7225, "L": 1, "D": 0 },
      "sample_size": 10,
      "price_per_success": { "credits": "6", "usd": "0.006" },
      "success_rate": 0.5958,
      "p50_latency_ms": 5,
      "pass_rule": "verify_email@v1",
      "purchasable": true,
      "regions": ["GLOBAL", "US"]
    }
  ]
}

Each option carries its score (0 to 100), the six score inputs, price_per_success in credits and USD (1 credit = $0.001), the pass_rule its results are checked against, and purchasable. Send a plain-language task instead of task_type and the answer says which type it chose in mapped_from_task. Every field, sort and constraint is on Recommend.

3. Buy one result

Send the tool_id and one input. The input fields come from the task type: for verify_email it's { "email": "…" }. With a test key the mock provider answers and the real pass rule runs on its answer. No credits move.

curl
curl https://api.arettic.com/v1/execute \
  -H "Authorization: Bearer $ARETTIC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "tool_id": "mock-verify-email", "input": { "email": "[email protected]" } }'
TypeScript
const bought = await arettic.execute({
  tool_id: tool.tool_id,
  input: { email: "[email protected]" },
});

if (bought.status === "passed" || bought.status === "partial") {
  console.log(bought.result, bought.charged); // charged is 0 in test mode
} else if (bought.status === "failed") {
  console.log("Not charged:", bought.reason);
}
Python
res = client.execute({"tool_id": tool_id, "input": {"email": "[email protected]"}})
print(res["status"], res.get("result"), res.get("reason"))
print(res["would_have_charged"])  # test mode: what a live key would have paid
MCP tool call
{ "name": "execute", "arguments": { "tool_id": "mock-verify-email", "input": { "email": "[email protected]" } } }
Response (example)
{
  "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" }
}

The SDKs add an idempotency_key to every execute call, so a retry after a dropped connection can never buy twice. From curl, send your own (up to 200 characters) once you go live. execute also takes inputs (a list of up to 1,000), max_price (the most you'll pay for the whole request, in credits), approval_id and fallback. All of it is on Execute.

4. Read the response

Look at status first.

Execute statuses
StatusWhat happenedWhat you get
passedThe result passed the published check for its task type.result, and charged (the price). In test mode charged is 0 and would_have_charged shows the price.
failedThe result didn't pass the check, or the provider errored.reason only. The data is withheld and nothing is charged. In test mode would_have_charged is 0.
partialWeb search only: fewer results than asked for.result and reason (for example results:2/5). Charged pro rata.
completedA batch of 2 to 25 inputs finished.summary (items, passed, partial, failed) and items[], one entry per input with its own status.
queuedOver 25 inputs with a live key: the request runs as a job (HTTP 202). Test keys run every batch inline.job_id. Poll GET /v1/jobs/{job_id}, or use waitForJob / wait_for_job in the SDKs. See Batches and jobs.
approval_requiredA live purchase over the agent's approval threshold or its monthly budget (HTTP 202). An owner has been emailed.approval_id and expires_at (24 hours). Poll GET /v1/approvals/{approval_id}, then send the same request again with approval_id. See Budgets and approvals.

The fields of a single-input answer:

Execute response fields
FieldMeaning
test_modetrue on answers to a test key. Absent on live answers.
tool_id, task_typeThe tool that ran and its task type.
check_versionThe version of the pass rule that judged the result (v1). It's on the receipt too.
chargedWhat was taken, as credits and USD: { "credits": "6", "usd": "0.006" }. Always 0 in test mode.
would_have_chargedTest mode only: what a live key would have paid for this answer.
resultThe provider's answer. Present on passed and partial only.
reasonWhy it failed or was partial: for example status:unknown, no_result, provider_error, timeout, blocked_page.
execution_id, receipt_id, refundedLive only: this item's id, the receipt for the whole request, and the credits that were held but not taken.

Try the fail path now. The mock verifier fails any address that starts with unknown. An address that starts with bad comes back invalid, which is a definitive answer, so it passes and would be charged. Any input that contains provider-error gives provider_error, and nomatch gives no_result. The inputs for every mock tool are on Test mode.

curl
curl https://api.arettic.com/v1/execute \
  -H "Authorization: Bearer $ARETTIC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "tool_id": "mock-verify-email", "input": { "email": "[email protected]" } }'
Response (example)
{
  "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" }
}

Anything the API refuses comes back as an error with one shape, and the SDKs raise it as AretticApiError. invalid_input (HTTP 400) is free: the input is checked before any provider is called, and message names the field. unknown_tool (404) means the id isn't in the catalog your key can see; a live key sees no mock- tools. unauthenticated (401) means the key is missing, wrong or revoked. Every code is on Error codes. doc_url links to the code's entry and retryable says whether sending the same request again can work.

Response (example)
{
  "error": {
    "code": "invalid_input",
    "message": "input.email: must be an email address. Nothing was charged.",
    "doc_url": "https://arettic.com/docs/errors#invalid_input",
    "retryable": false
  }
}

Any task, the same call

Email is only the first example. Every task type works the same way: pick the tool from recommend, send its input, and pay only if the result passes. With a test key, these mock tools answer each task type:

Every task type, its test tool and an example input
Task typeUsed forTest toolExample input
find_emailOutbound prospecting, recruiting outreach, partner sourcingmock-find-email{"first_name":"Emily","last_name":"Carter","domain":"acme.com"}
verify_emailCleaning a list before a campaign, sign-up and CRM hygienemock-verify-email{"email":"[email protected]"}
enrich_companyAccount research, lead scoring and routing, CRM enrichmentmock-enrich-company{"domain":"acme.com"}
enrich_personLead qualification, contact research, candidate and investor researchmock-enrich-person{"first_name":"Jason","last_name":"Miller","company_domain":"acme.com"}
web_searchMarket and competitor research, news monitoring, building target listsmock-web-search{"query":"Series A fintech startups in New York","n":5}
extract_urlReading pricing pages, docs, job posts and filings into an agentmock-extract-url{"url":"https://acme.com/pricing"}

5. Switch to a live key

Add a second agent on the Agents page and pick the mode Live. Its key starts with sk_live_. Change nothing else: the same code and the same endpoints. Three things are different.

Send an idempotency_key with every live purchase. If the connection drops and you send the same request again with the same key, you get the first answer back with replayed: true, never a second charge. The SDKs make a key for each call; pass your own to keep retries safe across restarts of your program.

curl
curl https://api.arettic.com/v1/execute \
  -H "Authorization: Bearer $ARETTIC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tool_id": "hunter-verify-email",
    "input": { "email": "[email protected]" },
    "idempotency_key": "quickstart-1"
  }'
TypeScript
const live = new Arettic({ apiKey: process.env.ARETTIC_API_KEY }); // an sk_live_… key

const bought = await live.execute(
  { tool_id: "hunter-verify-email", input: { email: "[email protected]" } },
  { idempotencyKey: "quickstart-1" },
);

// Live answers carry a receipt_id: fetch the receipt with the same key.
if ("receipt_id" in bought) {
  const receipt = await live.receipt(bought.receipt_id);
  console.log(receipt.charged, receipt.items[0]?.outcome); // { credits: "7", usd: "0.007" } "captured"
}
Python
live = Arettic(api_key="sk_live_…")

res = live.execute(
    {"tool_id": "hunter-verify-email", "input": {"email": "[email protected]"}},
    idempotency_key="quickstart-1",
)
print(res["status"], res["charged"]["credits"])

# Live answers carry a receipt_id: fetch the receipt with the same key.
receipt = live.receipt(res["receipt_id"])
for item in receipt["items"]:
    print(item["provider"], item["outcome"], item["charged"]["credits"])
Response (example)
{
  "status": "passed",
  "execution_id": "c02576cf-b3ce-4b0f-a30c-6e8aad4328ce",
  "receipt_id": "67a1fb46-c554-4cf6-b4e2-df1dbdf0e936",
  "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" }
}

Use the tool_id your live recommend returned; hunter-verify-email and its 7-credit price are an example. On a fail the answer has reason, charged is 0 and refunded is the full price: the hold went back to your balance.

Fetch the receipt

Every live purchase writes one immutable receipt: what was asked (as a hash), which provider answered, the check and its version, and, per item, what was charged and refunded. Fetch it by id with the same key. Any agent of the org can read the org's receipts, and they're on the dashboard under Receipts.

curl
curl https://api.arettic.com/v1/receipts/67a1fb46-c554-4cf6-b4e2-df1dbdf0e936 \
  -H "Authorization: Bearer $ARETTIC_API_KEY"
MCP tool call
{ "name": "get_receipt", "arguments": { "receipt_id": "67a1fb46-c554-4cf6-b4e2-df1dbdf0e936" } }
Response (example)
{
  "receipt_id": "67a1fb46-c554-4cf6-b4e2-df1dbdf0e936",
  "created_at": "2026-09-29T18:04:11.512Z",
  "tool_id": "hunter-verify-email",
  "job_id": null,
  "idempotency_key": "quickstart-1",
  "request_hash": "23350b3dcc6226289d9d6f4bdb8ccf7d7258c28317131242e6e566de6c8db75d",
  "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": "c02576cf-b3ce-4b0f-a30c-6e8aad4328ce",
      "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": "ab5c971d93369517129c982ea1dc700e48f8c9f851aa78deef918c40d9af3828",
      "result_hash": "4e2efddb74f95b6ac46553aab5d2a016e3f3877ceeffa14f4eb2bae0be6ab637",
      "check_version": "v1",
      "settled_at": "2026-09-29T18:04:11.498Z"
    }
  ]
}

outcome is the ledger state of the item: captured means charged, released means not charged. input_hash and result_hash are SHA-256 of the canonical JSON (keys sorted, no spaces), so you can check a result you stored against its receipt. Test keys write no receipts. The receipt field by field, the list endpoint and the CSV export are on Receipts. Think a charged result is wrong? Dispute it within 7 days: Disputes.

To see what's left, call GET /v1/balance (balance() in both SDKs, get_balance over MCP). It returns the org's credits, paid and trial, plus this agent's month-to-date spend, its budget and what remains of it.

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

Where next

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