`POST /execute` runs any available capability. Each capability publishes its input schema, timeout, example, and credit cost.

## Request

```http
POST https://data.upscrape.com/execute
Authorization: Bearer UPSCRAPE_API_KEY
Content-Type: application/json
Prefer: wait=30
Idempotency-Key: UNIQUE_LOGICAL_OPERATION
```

```json
{
  "capability": "platform.capability.action",
  "input": {}
}
```

The same API is also served under a `/v1` prefix: `POST https://data.upscrape.com/v1/execute` behaves identically, as do `/v1/jobs/{id}` and the other REST paths. The unversioned paths remain supported; use whichever you prefer.

`capability` must be an available capability ID. `input` must satisfy that capability's JSON Schema. Find both on its [public platform page](/scrapers).

## Credit ceiling

To cap what one request may cost, send `max_credits` in the body or an `X-Max-Credits` header. Both take a positive integer, and the body wins when both are present.

```json
{
  "capability": "platform.capability.action",
  "input": {},
  "max_credits": 5
}
```

If the published price for the submitted input is higher than the ceiling, the request is refused with HTTP `422` and `credit_limit_exceeded` before any job is created. A part-credit price such as `0.25` counts as the next whole credit for this check. The ceiling never changes the price, and a value that is not a positive integer is a validation error. This is the same check MCP applies to its `max_credits` argument.

## Waiting for completion

Without `Prefer: wait`, execution is asynchronous and normally returns HTTP `202`. Send `Prefer: wait=N` to wait for up to `N` seconds, with a server-side maximum of 30 seconds.

A wait is a response preference, not a different job type. If the job is still running when the window ends, Upscrape returns the same job as pending.

## Completed response

Completed work returns HTTP `200` with `state: "completed"` and `success: true`. Capability output is intentionally open-ended because each upstream source has a different data shape.

```json
{
  "job_id": "job_id",
  "state": "completed",
  "success": true,
  "results": [
    {"data": {"...": "capability-specific JSON"}}
  ],
  "billing": {"credits_charged": 1},
  "stats": {}
}
```

Use the capability's sample response as a realistic preview, but treat its input schema, not the sample output, as the validation contract.

## Pending response

Pending work returns HTTP `202` with a job ID, a non-terminal `state`, `poll_url` and `poll_after_ms`. Poll `poll_url` instead of resubmitting the execution.

## Validation

Invalid capability IDs, malformed JSON, and schema-invalid inputs fail before execution. Fix the request rather than retrying it unchanged. The response uses the standard `error` envelope with `code: "invalid_input"` and an `error.fields` map; see [errors](/docs/api/errors).

HTTP `503` with `temporarily_unavailable` means the capability cannot be served right now and no job was created. Do not retry in a tight loop; try again later.

When a waited job fails, the response keeps `state: "failed"` and uses a non-2xx status. Known validation, authorization, upstream, and availability failures map to their documented HTTP class; an unrecognized terminal failure is HTTP `500`, never a successful `200`.

## Charging

The capability's published price for the submitted input is charged once when the logical job completes successfully. Most capabilities publish a single `credits_per_request`; one whose price depends on its input publishes a range, shown as `N-M credits / request`. Polls, internal retries, failed executions, and idempotent replays do not add another capability charge.
