Base path: `https://api.1toolring.com/api/v1/tools`

Auth: `Authorization: Bearer <key>` (or `x-api-key: <key>`). Discover, inspect and the runs list also accept a dashboard session JWT, so the console can browse the catalog. Price: per call, in credits (1 credit = Rp1), shown on every tool before you run it. Discover, inspect and run status are free.

The tool catalog holds thousands of third-party tools across SERP, web scraping, people and company enrichment, social, ecommerce, maps and SEO. It is searched live, so this page documents the routes and the pricing model rather than listing every tool.

## The flow: discover, inspect, run

1. **Discover.** Describe the job in plain words. You get tool ids, a short description and the price.
2. **Inspect.** Read one tool's `input_schema` and its exact price model before you spend anything.
3. **Run.** Send the input. Credits are held up front, the call runs, and the hold settles to the real cost. A slow tool answers `202` with a `run_id` you poll.

The same flow is available to agents as MCP tools (`discover_tools`, `inspect_tool`, `run_tool`, `get_tool_run`). See [MCP](/sdks/mcp).

## Endpoints

| Method | Path | Price | Description |
| --- | --- | --- | --- |
| POST | `/discover` | free | Search the catalog by job. Body `{query, limit?}` (limit 1 to 20, default 10). |
| POST | `/inspect` | free | One tool's detail: `input_schema`, price, notes, health. Body `{tool}` or `{provider, endpoint}`. |
| POST | `/run` | per the tool's price | Run a tool. Body `{tool, input}`. `200` when it finishes in time, `202` while it runs. |
| GET | `/runs/{run_id}` | free | Status and result of one of your runs. Refreshes a running run before answering. |
| GET | `/runs` | free | Your runs, newest first. `limit` and `cursor` query parameters. |

## Discover

```bash
curl -X POST https://api.1toolring.com/api/v1/tools/discover \
  -H "Authorization: Bearer $TOOLRING_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"google search results for a keyword","limit":3}'
```

```json
{
  "data": {
    "query": "google search results for a keyword",
    "count": 3,
    "results": [
      {
        "id": "dataforseo/serp/google-organic",
        "provider": "dataforseo",
        "provider_name": "DataForSEO",
        "endpoint": "/serp/google-organic",
        "description": "Live Google organic results for a keyword, location and language.",
        "price": { "model": "per_call", "credits": 60, "usd": 0.0033 }
      }
    ]
  }
}
```

Values in the examples on this page are illustrative. Read the real price from `discover` or `inspect`.

## Inspect

```bash
curl -X POST https://api.1toolring.com/api/v1/tools/inspect \
  -H "Authorization: Bearer $TOOLRING_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tool":"dataforseo/serp/google-organic"}'
```

```json
{
  "data": {
    "id": "dataforseo/serp/google-organic",
    "provider": "dataforseo",
    "provider_name": "DataForSEO",
    "endpoint": "/serp/google-organic",
    "description": "Live Google organic results for a keyword, location and language.",
    "summary": "Top organic results with title, URL, snippet and rank.",
    "method": "POST",
    "price": { "model": "per_call", "credits": 60, "usd": 0.0033 },
    "input_schema": {
      "body": {
        "type": "object",
        "required": ["keyword"],
        "properties": {
          "keyword": { "type": "string" },
          "location_code": { "type": "integer" },
          "language_code": { "type": "string" }
        }
      }
    },
    "notes": [],
    "categories": ["serp", "seo"],
    "tags": ["google", "search"],
    "doc_url": "https://docs.dataforseo.com/",
    "health": { "status": "ok", "p50_ms": 2100, "p95_ms": 5400 }
  }
}
```

`input_schema` has up to three parts, each a JSON Schema: `body` (the JSON body), `queryParams` and `pathParams`. Send only the parts the schema declares.

## Run

```bash
curl -X POST https://api.1toolring.com/api/v1/tools/run \
  -H "Authorization: Bearer $TOOLRING_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tool":"dataforseo/serp/google-organic","input":{"body":{"keyword":"kopi pontianak","language_code":"id"}}}'
```

A run that finishes in time answers `200`:

```json
{
  "data": {
    "run_id": "run_01JC8Q4Z6N4M2V9W3X7Y5T1R0P",
    "tool": "dataforseo/serp/google-organic",
    "status": "completed",
    "output": { "items": [ { "rank": 1, "title": "...", "url": "..." } ] },
    "provider_status": 200,
    "credits_charged": 60,
    "credits_held": 0,
    "created_at": "2026-11-02T09:00:00Z",
    "completed_at": "2026-11-02T09:00:03Z"
  }
}
```

`output` is the tool's answer, passed through as is. The `X-ToolRing-Credits` header on a finished run is the number of credits actually charged.

### RunResult fields

| Field | Type | Description |
| --- | --- | --- |
| `run_id` | string | Your run id, e.g. `run_<ulid>`. |
| `tool` | string | The tool id that ran. |
| `status` | string | `running`, `completed` or `failed`. |
| `output` | any | The tool's answer, verbatim. |
| `provider_status` | integer | The HTTP status the tool's provider returned. |
| `provider_error` | string | Short reason when the provider failed. |
| `credits_charged` | integer | Credits actually charged (0 while running and on failure). |
| `credits_held` | integer | Credits held while the run is in flight. |
| `created_at`, `completed_at` | string | ISO 8601 timestamps. |

## Async runs and polling

Scrapers and bulk enrichment can take longer than one request. Then `/run` answers `202` with `status: "running"` and the hold still in place:

```json
{
  "data": {
    "run_id": "run_01JC8Q7B2K9D4F6H8J0L2N4P6R",
    "tool": "apify/google-maps-scraper",
    "status": "running",
    "credits_held": 9000,
    "created_at": "2026-11-02T09:05:00Z"
  }
}
```

Poll until `status` is `completed` or `failed`:

```bash
curl https://api.1toolring.com/api/v1/tools/runs/run_01JC8Q7B2K9D4F6H8J0L2N4P6R \
  -H "Authorization: Bearer $TOOLRING_KEY"
```

Every 3 to 5 seconds is a sensible interval. Polling is free. The hold settles when the run finishes, whether you poll or not: a background job also checks running runs. Only runs made by your own account are visible.

## Pricing model

Every tool carries a `price` object:

| Field | Description |
| --- | --- |
| `model` | `per_call`, `per_result`, `per_unit`, `tiered` or `free`. |
| `credits` | Price per call, result or unit, in credits (1 credit = Rp1). Any non-free unit costs at least 1 credit. |
| `usd` | The same price in US dollars, for reference. |
| `unit_label` | What one unit is, for `per_unit` tools (for example 1,000 characters). |
| `flat_credits` | A fixed fee on top of the unit price, when the tool has one. |
| `notes` | Extra pricing detail, such as tier boundaries. |

How a run is billed:

- **Hold, then settle.** Before the call, 1ToolRing holds the most the run can cost. `per_call` holds the price. `per_result`, `per_unit` and `tiered` hold an estimate from your input (fields such as `limit`, `maxItems`, `max_results`, `depth` or `num`). When the run finishes, the hold settles to the real cost and the rest is released.
- **Failed calls cost 0.** A provider error or a `failed` run releases the whole hold. You are charged nothing.
- **Run cap.** A single run has a spending cap. If the estimate is above it, the run is refused with `400 RUN_TOO_LARGE` before anything is held. Lower the result count or split the job.
- **Balance check.** If your balance is below the hold, the run is refused with `402 INSUFFICIENT_CREDITS` and nothing runs.

Credits come from the same prepaid wallet as every other 1ToolRing call. See [Rate limits and credits](/concepts/rate-limits) and [pricing](https://1toolring.com/pricing/).

## Errors

Envelope: `{"error":{"code","message"}}`.

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `VALIDATION_ERROR` | The body is malformed or `input` does not match the tool. |
| 400 | `RUN_TOO_LARGE` | The estimated cost is above the per-run cap. Nothing was held. |
| 401 | `UNAUTHORIZED` | Missing or invalid key. |
| 402 | `INSUFFICIENT_CREDITS` | The wallet cannot cover the hold. Carries `balance`, `needed` and `topup_url`. |
| 403 | `TOOL_BLOCKED` | This tool is not offered through 1ToolRing. Pick another from discover. |
| 404 | `TOOL_NOT_FOUND` | Unknown tool id. |
| 404 | `NOT_FOUND` | Unknown run, or the tool catalog is not enabled on this host. |
| 429 | `RATE_LIMITED` | Too many requests. Retry after a short wait. |
| 502 | `UPSTREAM_ERROR` | The tool network could not be reached. Nothing was charged. |
| 503 | `TOOLS_UNAVAILABLE` | Third-party tools are temporarily unavailable. Nothing was charged. |

```json
{
  "error": {
    "code": "INSUFFICIENT_CREDITS",
    "message": "Credit balance is too low for this run.",
    "balance": 40,
    "needed": 60,
    "topup_url": "https://app.1toolring.com/billing"
  }
}
```

A provider failure inside a run is not an HTTP error: the run comes back with `status: "failed"`, `provider_status`, a short `provider_error` and `credits_charged: 0`.