Run
POST /v1/run: execute a capability with routing and failover, or a raw endpoint under max_cost.
POST /v1/run
Execute a capability. The maximum billable amount is reserved before the provider call, then settled to the actual charge on success or released on failure. The request waits up to wait_seconds (default 30, max 120) for the result and answers 200 with the finished run; a run still executing answers 202 with the same payload (done: false) to poll at GET /v1/runs/{run_id}?wait=60. Send "async": true to get the 202 immediately. A run that would breach the organization's daily spend guard ends as blocked with error budget_exceeded before any money moves.
Headers
| Header | Required | Notes |
|---|---|---|
Authorization |
yes | Bearer rg_test_... or Bearer rg_live_... |
Idempotency-Key |
recommended | any unique string; makes the call safe to retry |
Request
{
"capability": "contact.find",
"input": {"first_name": "Alex", "last_name": "Rivera", "company_domain": "example.com"},
"routing": {"provider": "auto", "strategy": "best_value", "max_cost": "0.10", "allow_fallback": true},
"response": {"include_raw": false}
}
| Field | Type | Default | Notes |
|---|---|---|---|
capability |
string | one of | from discover |
provider + endpoint |
strings | one of | a raw endpoint instead of a capability; path fills {placeholders}, query adds query parameters |
input |
object | required | must match the capability's input_schema from inspect; for a raw endpoint, the provider's own request body. email.send takes attachments. An HTML entity in a text value (AT&T, as agents sometimes write it) is decoded before a capability runs, and the run stores the decoded input; html and content fields and raw endpoint bodies are sent as given |
routing.provider |
string | "auto" |
auto, or a provider slug to pin; pinning disables cross-provider fallback even with allow_fallback: true |
routing.strategy |
string | "best_value" |
cheapest, fastest, highest_success_rate, best_value |
routing.max_cost |
string | none | USD cap; offers above it are excluded before the call, fallbacks included, so a cap at the cheapest provider's price also disables failover (a failed run says which fallbacks it excluded). On a raw endpoint it also bounds the hold and the charge: set it on every raw call |
routing.allow_fallback |
bool | true |
only applies with provider: "auto"; set false to disable waterfall failover |
routing.fresh |
bool | false |
run even when this exact input missed in the last 12 hours (see repeated misses) |
response.include_raw |
bool | false |
include the provider's raw payload alongside the normalized result |
Attachments on email.send
An email sent from one of your inboxes can carry files: a deck for an investor, a PDF quote, a one-pager. Pass them in input.attachments, on a new message or a reply (in_reply_to):
{
"capability": "email.send",
"input": {
"inbox_id": "alex@getacme.com",
"to": ["investor@fund.example"],
"subject": "Acme deck, as promised",
"text": "Hi Sam,\n\nThe deck is attached.\n\nAlex",
"attachments": [
{"filename": "acme-deck.pdf", "content_type": "application/pdf", "url": "https://files.getacme.com/acme-deck.pdf"},
{"filename": "one-pager.pdf", "data_base64": "JVBERi0xLjQK..."}
]
},
"routing": {"max_cost": "0.01"}
}
| Field | Notes |
|---|---|
filename |
required; the plain file name the recipient sees, no path |
content_type |
media type such as application/pdf; guessed from the filename when omitted |
url |
a public https URL the sending provider downloads itself; pre-signed and redirecting URLs work. Exactly one of url or data_base64 |
data_base64 |
the file's bytes as standard base64, inline in the request |
size_bytes |
optional with url: the file's size, counted toward the total |
Limits per message: 10 files, 10 MB in total, and 4 MB of inline data_base64 (the request body that carries it is capped at 8 MB). Anything larger goes by url. The run's result lists the attachments the provider accepted; the run's stored input keeps each inline file's name, type and size but not its bytes. A send is priced per email whatever it carries. gmail.send does not take attachments yet.
Response
{
"run_id": "run_...",
"status": "succeeded",
"kind": "capability",
"capability": "contact.find",
"endpoint": null,
"provider": "...",
"routing_reason": "auto: best_value picked ...",
"billing": {"currency": "USD", "quoted": "0.05", "charged": "0.05", "platform_funded": true},
"timing": {"duration_ms": 842},
"attempts": [
{"provider": "...", "outcome": "provider_timeout", "duration_ms": 8000, "detail": "..."},
{"provider": "...", "outcome": "succeeded", "duration_ms": 842, "detail": null}
],
"result": {"...": "..."},
"request_id": "req_...",
"created_at": "2026-09-02T09:30:00+00:00"
}
hints, when present, provides free guidance about provider pinning, disabled fallback and how to refine a missed people search. It never changes your routing or spending limits. On unbilled no-matches and provider failures, the error also explains budget-excluded alternatives using quotes for the requested input when available.
result is present on success; error ({code, message}) is present on failure. billing.charged is what actually settled: null or lower than quoted when the reservation was released or partly released. A raw endpoint run has kind: "endpoint", capability: null, endpoint set, billing.quoted as the rate-card estimate (or 0), and result.data holding the provider's payload untouched.
A run stopped by a workspace control ends as blocked before any money moves, with controls: [{"control": "daily_spend_guard" | "per_run_limit", "manage": "/v1/spend-guard"}]. It is terminal: tell the user, do not retry until the control changes.
Run lifecycle
created → reserved → running → succeeded | no_match | failed | timed_out
A run still in created, reserved or queued can be cancelled. no_match means at least one provider answered authoritatively that there is no result; whether that bills depends on the capability's stated billing conditions.
A run the router was executing when it restarted stays running for up to about 15 minutes, then ends failed with error interrupted and its hold released: retry it with a new Idempotency-Key.
Repeated misses
On people.search, company.search, company.enrich, company.funding, person.enrich, contact.find and contact.phone, an input that every provider answered with no_match in the last 12 hours is not sent to them again: the run ends no_match at once, charges nothing, and carries repeat_of with the id of the run that missed. A repeated hit always runs, and so does a run with different routing, a provider added since, or routing.fresh: true.
Waterfall failover
With provider: "auto", a provider error, timeout or unbilled no-match falls through to the next-best provider automatically, up to 3 attempts. Only the provider that delivers is charged; every attempt appears in attempts with its outcome. max_cost caps every attempt, and a pinned provider never falls back.
Idempotency
Replaying the same Idempotency-Key returns the original run, marked Idempotent-Replay: true, without executing or billing again. Use it on every retry loop.
Errors you should handle
| Code | Meaning |
|---|---|
insufficient_balance (402) |
reservation exceeds available balance: top up |
cost_limit_exceeded (402) |
the run's hold exceeds the workspace's per-run ceiling (blocked) |
budget_exceeded (402) |
the daily spend guard would be breached (blocked) |
endpoint_not_found (404) |
unknown raw endpoint for that provider; list them at /v1/catalog/providers/{provider}/endpoints |
provider_not_available (409) |
no live provider for this capability yet (production) |
no_match (404) |
the providers found nothing; billed only if the capability bills no-matches |
provider_timeout (504), provider_error (502) |
all attempts failed; nothing charged |
Full list: Errors.
Reading this as an agent? This page as markdown: /docs/api/run.md · every page: /docs/llms.txt