# Run

```text
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

```json
{
  "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](/docs/api/discover) |
| `provider` + `endpoint` | strings | one of | a [raw endpoint](/docs/api/endpoints) instead of a capability; `path` fills `{placeholders}`, `query` adds query parameters |
| `input` | object | required | must match the capability's `input_schema` from [inspect](/docs/api/inspect); for a raw endpoint, the provider's own request body |
| `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 |
| `response.include_raw` | bool | `false` | include the provider's raw payload alongside the normalized result |

## Response

```json
{
  "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

```text
created → reserved → running → succeeded | no_match | failed | timed_out
```

A run still in `created`, `reserved` or `queued` can be [cancelled](/docs/api/runs). `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.

## 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](/docs/api/wallet) |
| `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](/docs/api/errors).
