Buying results

Recommend

Ask which tool works for a task and get them ranked by score, then price.

POST /v1/recommend tells your agent which tool to use for a task. You send one of the task types, or the task in plain language. You get back the tools for that task, ranked by score, each with the price of one passing result and the check that result must pass.

The call is free: it uses no credits. It does count against your plan's daily lookup quota. Nothing is run or bought here. To buy a result, send the tool_id you pick to execute.

It needs an agent key, sk_test_… or sk_live_…, in the Authorization header (see Authentication). Test keys see mock tools. Live keys see the real catalog. Without a key you can still read the catalog and scores, unranked, through Public data.

Arettic is pre-launch. Keys go to design partners; everyone else can join the waitlist. The @arettic/sdk (npm), arettic (PyPI) and @arettic/mcp packages used on this page are published at launch.

Call it

curl
curl -s https://api.arettic.com/v1/recommend \
  -H "Authorization: Bearer sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{
    "task_type": "find_email",
    "region": "US",
    "constraints": { "max_price": 50, "min_score": 40 },
    "sort": "score",
    "limit": 5
  }'

This asks for find_email tools that serve the US, cost at most 50 credits per passing result and score at least 40, five at most. With a test key the only match is the mock finder:

Response (example)
{
  "task_type": "find_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-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"]
    }
  ]
}

options[0].tool_id is what you send to execute. Check purchasable first: ranking ignores it, so the top option can be a tool that is listed for information only (see Info-only tools).

Request

POST https://api.arettic.com/v1/recommend with a JSON body. Headers: Authorization: Bearer <agent key> and Content-Type: application/json. Every field is optional, except that you must send task_type or task.

Request fields
FieldTypeDefaultWhat it does
task_typestringnoneOne of the task types, listed below. Required unless you send task. When both are sent, task_type is used and task is ignored.
taskstringnoneThe task in plain language, up to 500 characters (longer text is cut). Arettic maps it to a task type and says which one it chose. Counts as a free-text lookup as well as a score lookup.
regionstringGLOBALWhere the results are needed, for example US. Upper-cased. An empty string means GLOBAL.
constraints.max_pricenumbernonePrice cap in credits (1 credit = $0.001). Tools whose price per success is above it are left out. Tools with no price stay in.
constraints.min_scorenumbernoneLowest score to include, on the 0 to 100 scale. Tools with no score yet are left out whenever you set it, even at 0.
sortstringscorescore, price, value or latency (see How tools are ranked). Anything else falls back to score.
limitnumber10How many options to return, 1 to 50. Decimals are rounded down, 0 becomes 1, and more than 50 becomes 50.

Numbers are checked strictly. constraints.max_price, constraints.min_score and limit must be non-negative numbers, or the call answers invalid_input with a message that names the field, for example "limit must be a non-negative number". A body that isn't a JSON object answers invalid_input too. An unknown sort is not an error: it falls back to score.

The task types: find_email, verify_email, enrich_company, enrich_person, web_search, extract_url. Send task_type when you know it. It skips the mapping step and the free-text quota.

Response

Response fields
FieldMeaning
task_typeThe task type the options are for.
mapped_from_taskOnly when you sent task: how the text was mapped, as from, confidence and alternatives. See Free-text tasks.
regionThe region used, upper-cased. GLOBAL when you sent none.
sortThe sort that was applied.
test_modetrue for a test key. Test keys see mock tools only.
formula_versionThe version of the public score formula behind every score. Today v1.
rankingThe ranking rule as a sentence, so an agent reading the JSON knows how the list was ordered.
optionsThe ranked tools, at most limit of them. Empty when no tool matches.

Each option

Option fields
FieldTypeMeaning
tool_idstringThe id you send to execute as tool_id.
namestringThe tool's name.
providerstringThe provider's name.
scorenumber or nullThe tool's score, 0 to 100, from the public formula. null when the tool has no score yet.
score_weekstring or nullThe Monday (UTC) of the week the score was computed for, as YYYY-MM-DD.
score_inputsobject or nullEvery input to the score: A, S, P, R, L and D, each between 0 and 1. The table below says what they are.
sample_sizenumberData points behind the score: benchmark cases plus live calls in the last 28 days. 0 without a score.
price_per_successobject or nullThe pay-as-you-go price of one passing result, as { credits, usd }, both strings (1 credit = $0.001). max_price is compared with this number. Pro and Max orgs pay less: GET /v1/tools/{id} shows prices_by_plan, and execute charges your plan's price. null when the tool has no price.
success_ratenumber or nullThe same as score_inputs.S: the share of calls that passed the check.
p50_latency_msnumber or nullMedian latency in the tool's latest benchmark run, in milliseconds. null when it has none.
pass_rulestringThe check a result must pass before you are charged, as task_type@version, for example find_email@v1. See Pass rules.
purchasablebooleanWhether execute can buy it through Arettic. Never changes the rank.
regionsstring[]The regions the tool serves, for example ["GLOBAL", "US"].
Score inputs
InputMeaning
AAccuracy: the share of benchmark cases where a correct result was delivered.
SPass rate: benchmark and live calls in the last 28 days, weighted by volume.
PAudit precision: the share of audited passes confirmed correct. Uses A until 50 audits exist.
RReliability: 1 minus the tool's error and timeout rate across its benchmark cases and live calls in the last 28 days.
LSpeed: the task's median latency divided by this tool's latency, capped at 1.
DUpheld disputes divided by passed results. This one is a penalty.

The formula, its weights and its rules are on How scores work and at GET https://api.arettic.com/v1/formula.

How tools are ranked

The default order is by score, highest first. Two scores within 2 points of each other count as a tie: the cheaper tool comes first, and at the same price the higher score. Tools with no score yet come after every scored tool, cheapest first.

Whether a tool can be bought through Arettic never changes its rank. It is a field, purchasable, not a factor. In the API's own tests, a tool one point below the top scorer but cheaper ranks first, and a tool 30 points below ranks last even though it is sold here.

Constraints are applied before sorting and limit after it. So sort: "price" with limit: 1 gives you the cheapest tool that meets your constraints.

Sorts
SortOrder
scoreThe default. Highest score first, with the tie rule above. Tools with no score come last, cheapest first.
priceCheapest price per success first. Tools with no price come last. Equal prices keep the score order.
valueHighest score divided by price in credits first. Tools with no score or no price come last. Equal values keep the score order.
latencyLowest p50_latency_ms first. Tools with no latency come last. Equal latencies keep the score order.

Info-only tools

A tool is purchasable when it is curated for sale here and has a price. Every other listed tool is there for information only: you see its score and where it ranks, but execute refuses it with not_purchasable (HTTP 409). Its price_per_success may be null.

So take the first option with purchasable: true, not options[0]. An info-only tool is still worth reading: it shows how the tool you buy compares with the rest of the market.

Regions

region says where the results are needed. It is upper-cased, so us and US are the same. Leave it out for GLOBAL.

A tool is listed when its regions include your region or GLOBAL. Its score is the region's own score when one exists, otherwise its GLOBAL score; score_week and score_inputs come from whichever was used. The response repeats the region in region.

A region with no tools of its own, or a value that isn't a region at all, gives you the tools that serve GLOBAL with their GLOBAL scores.

Free-text tasks

When you send task instead of task_type, Arettic maps the text to a task type with a keyword classifier. It is deterministic and calls no model: the same text always gives the same type. It costs one free-text lookup from your daily quota, nothing else.

How it works: the text is lower-cased, extra spaces are collapsed, and anything after 500 characters is dropped. Each task type has a few keyword patterns with weights, for example "find … email" for find_email, "bounce" for verify_email, "search the web" for web_search. The type with the highest total wins, as long as that total is at least 2. Otherwise the call answers unknown_task_type (HTTP 400): "Couldn't map that task to a task type. Send task_type as one of: …".

The response always says what it chose, in mapped_from_task:

mapped_from_task
FieldMeaning
fromYour text, trimmed, up to 500 characters.
confidenceThe winner's total divided by the winner's plus the runner-up's, rounded to 2 decimals. 1 when only one type matched.
alternativesUp to two other task types that also matched, best first. Empty when nothing else matched.

If the type is wrong, or confidence is low, send task_type explicitly. For example, this text matches two types and the runner-up may be the one you meant:

curl
curl -s https://api.arettic.com/v1/recommend \
  -H "Authorization: Bearer sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{ "task": "find the email and job title of the CTO at acme.com" }'
Response (example)
{
  "task_type": "enrich_person",
  "mapped_from_task": {
    "from": "find the email and job title of the CTO at acme.com",
    "confidence": 0.57,
    "alternatives": ["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-enrich-person",
      "name": "Mock Person Enrichment",
      "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": "45", "usd": "0.045" },
      "success_rate": 0.5958,
      "p50_latency_ms": 5,
      "pass_rule": "enrich_person@v1",
      "purchasable": true,
      "regions": ["GLOBAL", "US"]
    }
  ]
}

Phrases from the API's own tests and where they map:

Free-text examples
TaskMaps to
find the email of the marketing head at acmefind_email
check if these emails will bounceverify_email
company size and industry for 100 US SaaS firmsenrich_company
get the job title and linkedin of the CMOenrich_person
search the web for articles about sales tax compliance toolsweb_search
scrape the text from https://acme.com/aboutextract_url
make me a sandwichNothing: unknown_task_type

Daily quotas

Recommend is free, but each org has a daily number of lookups that depends on its plan. All the org's agents share it, and it resets at 00:00 UTC. Every call that reaches the catalog counts one score lookup, whether or not any tool matches. A call with task also counts one free-text lookup. That one is taken before the text is mapped, so a task that can't be mapped still counts, though it takes no score lookup.

Daily lookup quotas by plan
PlanNameScore lookups a dayFree-text lookups a day
paygPay as you go1,000100
teamPro10,000500
enterpriseMaxNo fixed limitNo fixed limit

When a quota is used up, the call answers HTTP 429 with the code plan_limit. The message says which limit it was. retryable is false, and the SDKs never retry recommend, so wait for the reset, or send task_type instead of task if it was the free-text quota that ran out.

Response (example)
{
  "error": {
    "code": "plan_limit",
    "message": "Daily limit of 1000 score lookups reached for your plan. It resets at 00:00 UTC.",
    "doc_url": "https://arettic.com/docs/errors#plan_limit",
    "retryable": false
  }
}

The per-agent rate limit applies on top of the quota: 600 requests a minute for each agent key, reported in the RateLimit-* headers. See Rate limits and quotas.

Test keys and mock tools

With a test key (sk_test_…) the catalog is the mock provider: one tool per task type, ids starting with mock-, serving GLOBAL and US. They are benchmarked and scored like real tools, so constraints and sorts behave the same way. The response has test_mode: true. A live key never sees a mock tool, and a test key never sees a real one.

Mock tools
Task typeTool idNamePrice per success (credits)
find_emailmock-find-emailMock Email Finder38
verify_emailmock-verify-emailMock Email Verifier6
enrich_companymock-enrich-companyMock Company Enrichment30
enrich_personmock-enrich-personMock Person Enrichment45
web_searchmock-web-searchMock Web Search8
extract_urlmock-extract-urlMock Page Extractor3

So {"task_type": "find_email", "constraints": {"max_price": 10}} returns no options with a test key: the mock finder costs 38 credits. With a live key you see only the tools that are switched on in the real catalog, so a task type can have no options until its providers are live. What the mock tools return from execute is on Test mode.

SDK and MCP examples

Both SDKs read ARETTIC_API_KEY (and ARETTIC_API_URL) from the environment. Neither retries recommend, because it is a POST without an idempotency key. A refused call throws (TypeScript) or raises (Python) AretticApiError with the API's code. See SDKs.

TypeScript

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 rec = await arettic.recommend({
  task_type: "find_email",
  region: "US",
  constraints: { max_price: 50, min_score: 40 },
  sort: "score",
  limit: 5,
});

// Ranking ignores whether a tool can be bought here, so take the first purchasable option.
const tool = rec.options.find((o) => o.purchasable);
if (!tool) throw new Error("No purchasable tool for " + rec.task_type + " in " + rec.region);

console.log(tool.tool_id, tool.score, tool.price_per_success?.credits, tool.pass_rule);
// mock-find-email 56.53 38 find_email@v1

// Or map plain language. The answer says which task type it chose.
const mapped = await arettic.recommend({ task: "find the work email of Emily Carter at example.com" });
console.log(mapped.task_type, mapped.mapped_from_task?.confidence); // find_email 1

Python

Python
from arettic import Arettic

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

rec = client.recommend(
    task_type="find_email",
    region="US",
    constraints={"max_price": 50, "min_score": 40},
    sort="score",
    limit=5,
)

# Ranking ignores whether a tool can be bought here, so take the first purchasable option.
tool = next((o for o in rec["options"] if o["purchasable"]), None)
if tool is None:
    raise SystemExit(f"No purchasable tool for {rec['task_type']} in {rec['region']}")

print(tool["tool_id"], tool["score"], tool["price_per_success"]["credits"], tool["pass_rule"])
# mock-find-email 56.53 38 find_email@v1

# Or map plain language. The answer says which task type it chose.
mapped = client.recommend(task="find the work email of Emily Carter at example.com")
print(mapped["task_type"], mapped["mapped_from_task"]["confidence"])  # find_email 1

MCP

The MCP server's recommend tool takes the same fields with two differences: max_price and min_score are top-level arguments (there is no constraints object), and there is no limit, so you get up to 10 options. task is limited to 500 characters, region to 10, and min_score to 100. The result is the same JSON as above, as one text content block. A refused call comes back as a result with isError: true and the API's error body. Setup is on MCP server.

MCP tool call
{
  "name": "recommend",
  "arguments": {
    "task_type": "find_email",
    "region": "US",
    "max_price": 50,
    "min_score": 40,
    "sort": "score"
  }
}

Errors

Errors from recommend
CodeHTTPWhen
invalid_inputHTTP 400The body isn't a JSON object, neither task_type nor task was sent, or constraints.max_price, constraints.min_score or limit isn't a non-negative number. The message names the field.
unknown_task_typeHTTP 400task_type isn't one of the task types, or task couldn't be mapped to one.
unauthenticatedHTTP 401No agent key, or a revoked one. Only sk_test_… and sk_live_… keys work here; org keys (ok_…) don't.
plan_limitHTTP 429The org's daily score-lookup or free-text quota is used up. It resets at 00:00 UTC.
rate_limitedHTTP 429Too many requests from this agent in a short time. Wait for the number of seconds in Retry-After.

Every error is { "error": { "code", "message", "doc_url", "retryable" } }. doc_url points at the code's entry on Error codes.

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