---
title: Tools endpoints
description: Reference for every /v1/tools endpoint, the tool object and the run object.
layout: reference
---

The tools API lists, searches and runs [data tools](/docs/tools). Requests and responses are JSON. The base URL is `https://lovelyrouter.com/v1`.

**Authentication.** The catalog endpoints (list, retrieve, search and plan) are public. Running tools and reading runs need an API key in the `Authorization: Bearer` header. Tool runs are billed from the same prepaid balance as models.

**Errors.** Failed requests return an `error` object with a `type` and a `message`. See [Errors](#errors).

**Languages.** Catalog endpoints take `lang` (`zh` or `ja`) to localize tool names and descriptions, as a query parameter on `GET` or a body field on `POST`.

```bash title="cURL" aside
curl https://lovelyrouter.com/v1/tools/runs \
  -H "Authorization: Bearer $LOVELY_API_KEY"
```

## The tool object

A tool in the catalog. List and search endpoints return the summary fields. [Retrieve a tool](#retrieve-a-tool) adds the input schema, an example input, the time limit and a ready-made run request.

:::params Attributes
- `id` string
  Tool id, used in run paths. For example `google-maps-places`.
- `name` string
  Display name, localized with `lang`.
- `platform` string
  The site or source the tool reads, such as `Google Maps` or `TikTok`.
- `category` string
  One of `social-media`, `search`, `ecommerce`, `lead-generation`, `reviews`, `jobs`, `travel`, `web`.
- `description` string
  What the tool returns, localized with `lang`.
- `price` object
  The live price.
  - `per_call_usd` number
    Fee charged once per successful run. `0` if the tool has none.
  - `per_result_usd` number
    Fee per result returned. `0` if the tool has none.
  - `unit` string
    What one result is: `place`, `review`, `page`, `profile`, `step`…
  - `label` string
    Human-readable price, such as `$0.0015 / review + $0.0075 / call`.
- `default_results` integer
  The result cap a run uses when you don't set `max_results`.
- `max_results` integer
  The highest result cap a run of this tool accepts.
- `featured` boolean
  Whether the tool is highlighted as popular on the [tools page](/tools).
- `input_schema` object
  JSON Schema for a run's `input`. Retrieve only.
- `example_input` object
  An input that works as is. Retrieve only.
- `timeout_s` integer
  The run's time limit in seconds. A run that exceeds it ends as `timed_out`. Retrieve only.
- `run` object
  A ready-made run request: `method`, `path` and a `body` with the example input and default cap. Retrieve only.
:::

:::aside
```json title="The tool object"
{
  "id": "google-maps-places",
  "name": "Search Google Maps places",
  "platform": "Google Maps",
  "category": "search",
  "description": "Find local businesses on Google Maps by keyword and location: name, address, phone, website, rating, review count, opening hours, coordinates.",
  "price": {
    "per_call_usd": 0.00075,
    "per_result_usd": 0.000238,
    "unit": "place",
    "label": "$0.000238 / place + $0.00075 / call"
  },
  "default_results": 10,
  "max_results": 200,
  "featured": true
}
```
:::

## List tools

::endpoint GET /v1/tools

Returns every available tool. No key needed.

:::params Query parameters
- `category` string
  Only tools in this category, such as `reviews`.
- `q` string
  Keywords. Returns matching tools ranked by relevance, up to 100.
- `lang` string, default `en`
  `zh` or `ja` to localize names and descriptions.
:::

Returns a list object with the tools in `data`.

```bash title="cURL" aside
curl "https://lovelyrouter.com/v1/tools?category=reviews"
```

```python title="Python" aside
import requests

tools = requests.get(
    "https://lovelyrouter.com/v1/tools",
    params={"category": "reviews"},
).json()["data"]
```

```js title="Node.js" aside
const res = await fetch('https://lovelyrouter.com/v1/tools?category=reviews');
const { data: tools } = await res.json();
```

:::aside
```json title="Response"
{
  "object": "list",
  "data": [
    {
      "id": "google-maps-reviews",
      "name": "Google Maps reviews",
      "platform": "Google Maps",
      "category": "reviews",
      "description": "Reviews of a Google Maps place (by place ID or URL): text, stars, date, owner replies.",
      "price": {
        "per_call_usd": 0,
        "per_result_usd": 0.000675,
        "unit": "review",
        "label": "$0.000675 / review"
      },
      "default_results": 20,
      "max_results": 500,
      "featured": false
    }
  ]
}
```
:::

## Retrieve a tool

::endpoint GET /v1/tools/{id}

Returns one tool with its `input_schema`, `example_input`, `timeout_s` and a ready-made `run` request. No key needed. Returns `404` for an unknown id.

:::params Path parameters
- `id` string, required
  The tool id.
:::

:::params Query parameters
- `lang` string, default `en`
  `zh` or `ja` to localize the name and description. Input schemas stay in English.
:::

```bash title="cURL" aside
curl https://lovelyrouter.com/v1/tools/trustpilot-reviews
```

:::aside
```json title="Response"
{
  "id": "trustpilot-reviews",
  "name": "Trustpilot reviews",
  "platform": "Trustpilot",
  "category": "reviews",
  "description": "Trustpilot reviews of a company by domain: stars, text, date, reply.",
  "price": {
    "per_call_usd": 0.0075,
    "per_result_usd": 0.0015,
    "unit": "review",
    "label": "$0.0015 / review + $0.0075 / call"
  },
  "default_results": 20,
  "max_results": 500,
  "featured": false,
  "input_schema": {
    "type": "object",
    "properties": {
      "companyUrls": {
        "type": "array",
        "title": "Company URLs or Domains",
        "description": "Enter Trustpilot company URLs (e.g., https://www.trustpilot.com/review/example.com) or just the domain (e.g., example.com). One per line."
      },
      "maxReviewsPerCompany": {
        "type": "integer",
        "title": "Max reviews per company (reviews)",
        "default": 0,
        "minimum": 0
      }
    },
    "required": ["companyUrls"]
  },
  "example_input": {
    "companyUrls": ["pipedrive.com"],
    "maxReviewsPerCompany": 20
  },
  "timeout_s": 300,
  "run": {
    "method": "POST",
    "path": "/v1/tools/trustpilot-reviews/run",
    "body": {
      "input": { "companyUrls": ["pipedrive.com"], "maxReviewsPerCompany": 20 },
      "max_results": 20
    }
  }
}
```
:::

## Search tools

::endpoint POST /v1/tools/discover

Keyword search over the catalog. Returns the best matches, each with a relevance `score`. No key needed.

:::params Body parameters
- `query` string, required
  What you need, in a few words. For example `tiktok comments`. The first 1,000 characters are used.
- `limit` integer, default `5`
  Number of results, from 1 to 40.
- `lang` string, default `en`
  `zh` or `ja` to localize names and descriptions.
:::

Returns `query`, `count` and `results`: tool objects with a `score`. Scores only compare tools within one query. A body without `query` returns `400`.

```bash title="cURL" aside
curl https://lovelyrouter.com/v1/tools/discover \
  -H "content-type: application/json" \
  -d '{"query": "google maps reviews", "limit": 2}'
```

:::aside
```json title="Response"
{
  "query": "google maps reviews",
  "count": 2,
  "results": [
    {
      "id": "google-maps-reviews",
      "name": "Google Maps reviews",
      "platform": "Google Maps",
      "category": "reviews",
      "description": "Reviews of a Google Maps place (by place ID or URL): text, stars, date, owner replies.",
      "price": { "per_call_usd": 0, "per_result_usd": 0.000675, "unit": "review", "label": "$0.000675 / review" },
      "default_results": 20,
      "max_results": 500,
      "featured": false,
      "score": 0.889
    },
    {
      "id": "google-maps-places",
      "name": "Search Google Maps places",
      "platform": "Google Maps",
      "category": "search",
      "description": "Find local businesses on Google Maps by keyword and location: name, address, phone, website, rating, review count, opening hours, coordinates.",
      "price": { "per_call_usd": 0.00075, "per_result_usd": 0.000238, "unit": "place", "label": "$0.000238 / place + $0.00075 / call" },
      "default_results": 10,
      "max_results": 200,
      "featured": true,
      "score": 0.717
    }
  ]
}
```
:::

## Plan a task

::endpoint POST /v1/tools/suggest

Describe a task in plain words and get an ordered plan: the tool to run first, an optional second tool and close alternatives, each with an example input and an estimated cost. Free, with a rate limit of 100 requests per minute per API key. Callers without a key share one pool of 100 requests per minute. Over the limit, the endpoint returns `429` with a `retry-after` header.

:::params Body parameters
- `task` string, required
  The task, up to 2,000 characters. Longer tasks return `413`.
- `limit` integer, default `5`
  Most suggestions to return, from 1 to 8.
- `lang` string, default `en`
  `zh` or `ja` to localize names and descriptions.
:::

:::params Returns
- `task` string
  The task you sent.
- `source` string
  `ai` for a plan from the planner, `keyword` for keyword matches when the planner is unavailable.
- `cached` boolean
  Whether the plan came from a cache of identical tasks (kept for a day).
- `count` integer
  Number of suggestions. `0` when no tool fits the task.
- `suggestions` array
  The plan, in order. Each entry is a tool object plus:
  - `step` integer
    Position in the plan, from 1.
  - `role` string
    `primary` (run first), `next` (a second kind of data the task needs) or `alternative` (instead of the primary).
  - `match` number
    Confidence in this step, from 0 to 1.
  - `input` object
    The tool's example input. Replace its values with your task's.
  - `input_required` array
    Input fields you must set.
  - `max_results` integer
    The result cap the estimate assumes: the tool's default.
  - `est_cost_usd` number
    The most this step costs at that cap.
  - `run` object
    A ready-made run request with `method`, `path` and `body`.
- `total_est_cost_usd` number
  Sum of the `primary` and `next` steps. Alternatives are not included.
:::

```bash title="cURL" aside
curl https://lovelyrouter.com/v1/tools/suggest \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -H "content-type: application/json" \
  -d '{"task": "Find vegan restaurants in Austin and summarize what their reviews complain about"}'
```

:::aside
```json title="Response"
{
  "task": "Find vegan restaurants in Austin and summarize what their reviews complain about",
  "source": "ai",
  "cached": false,
  "count": 2,
  "suggestions": [
    {
      "step": 1,
      "role": "primary",
      "match": 0.961,
      "id": "google-maps-places",
      "name": "Search Google Maps places",
      "platform": "Google Maps",
      "category": "search",
      "price": { "per_call_usd": 0.00075, "per_result_usd": 0.000238, "unit": "place", "label": "$0.000238 / place + $0.00075 / call" },
      "input": {
        "searchTerms": ["coffee shop"],
        "location": "Austin, Texas, USA",
        "maxResults": 3,
        "language": "en"
      },
      "input_required": [],
      "max_results": 10,
      "est_cost_usd": 0.00313,
      "run": {
        "method": "POST",
        "path": "/v1/tools/google-maps-places/run",
        "body": { "input": { "searchTerms": ["coffee shop"], "location": "Austin, Texas, USA", "maxResults": 3, "language": "en" }, "max_results": 10 }
      }
    },
    {
      "step": 2,
      "role": "next",
      "match": 0.875,
      "id": "google-maps-reviews",
      "name": "Google Maps reviews",
      "platform": "Google Maps",
      "category": "reviews",
      "price": { "per_call_usd": 0, "per_result_usd": 0.000675, "unit": "review", "label": "$0.000675 / review" },
      "input": { "placeIds": ["ChIJN1t_tDeuEmsRUsoyG83frY4"], "maxReviews": 3, "reviewsSort": "newest" },
      "input_required": [],
      "max_results": 20,
      "est_cost_usd": 0.0135,
      "run": {
        "method": "POST",
        "path": "/v1/tools/google-maps-reviews/run",
        "body": { "input": { "placeIds": ["ChIJN1t_tDeuEmsRUsoyG83frY4"], "maxReviews": 3, "reviewsSort": "newest" }, "max_results": 20 }
      }
    }
  ],
  "total_est_cost_usd": 0.01663
}
```
:::

## The run object

A run of a tool. Returned by [run](#run-a-tool), [retrieve](#retrieve-a-run), [stop](#stop-a-run) and [list](#list-runs).

:::params Attributes
- `id` string
  Run id: `run_` followed by 24 characters.
- `object` string
  Always `tool_run`.
- `tool` string
  The tool id.
- `tool_name` string
  The tool's display name.
- `status` string
  `running`, `succeeded`, `failed`, `timed_out` (hit the tool's time limit) or `stopped` (stopped by you).
- `input` object or null
  The input the run used: field names from a tool's earlier versions mapped to the current ones, and the tool's limit field filled in from the result cap (split across the searches for tools whose limit counts per search). Use it to match runs to your queries, for example after a dropped connection. `null` for some runs made before October 20, 2026.
- `results` integer
  Results returned and billed. `0` until the run finishes.
- `max_results` integer
  The result cap that applies to this run. It can differ from what you asked for: it is lowered to the tool's maximum, or raised to a tool's minimum run size.
- `charge_usd` number or null
  What the run cost. `null` until the run is settled.
- `reserved_usd` number
  The amount held from your balance while the run is unsettled. `0` once it is settled.
- `error` string or null
  Why the run ended early. `null` for `running` and `succeeded` runs.
- `created_at` string
  When the run started, ISO 8601.
- `finished_at` string or null
  When the run ended, ISO 8601. `null` while it is running.
- `poll` string
  Path to poll for the result. Only in `202` responses.
- `items` array or null
  The results, in the tool's own format. Only when the run has finished, and not in [list](#list-runs). `null` if results are briefly unavailable; see `items_error`.
- `offset` integer
  Index of the first result in `items`. Only when the run has results.
- `next_offset` integer or null
  Offset of the next page of results, or `null` on the last page.
- `items_error` string
  Present only when `items` is `null`. Request the run again to load the results.
- `report` object
  Facts about the whole run that some tools return beside `items`. Website referral tools (`site-referrals`, `site-outgoing`): the queried `site`, the `period`, the number of `sites` in the list, `unattributed` (referral visits not credited to any site, or `null`) and, for `site-referrals`, `monthly`: the site's total referral visits per month, like `[{"month": "2026-04", "visits": 55133}]` (`null` for `site-outgoing`). Each of their items also has `monthly`, that site's visits per month, or `null` when there are no monthly numbers for it.
:::

:::aside
```json title="The run object"
{
  "id": "run_8c1f0e2a9b7d4f3e6a5c2b1d",
  "object": "tool_run",
  "tool": "google-maps-places",
  "tool_name": "Search Google Maps places",
  "status": "succeeded",
  "input": {
    "searchTerms": ["coffee shop"],
    "location": "Austin, Texas, USA",
    "maxResults": 20,
    "language": "en",
    "radius": 20
  },
  "results": 20,
  "max_results": 20,
  "charge_usd": 0.00551,
  "reserved_usd": 0,
  "error": null,
  "created_at": "2026-10-11T15:02:11.204Z",
  "finished_at": "2026-10-11T15:02:38.917Z",
  "items": [
    {
      "title": "Godsent Coffee",
      "categoryName": "Coffee shop",
      "address": "9817 Anderson Mill Rd, Austin, TX 78750",
      "totalScore": 5,
      "reviewsCount": 237,
      "placeId": "ChIJq7RZDGczW4YRyCS5Z0KiiDE"
    }
  ],
  "offset": 0,
  "next_offset": null
}
```
:::

## Run a tool

::endpoint POST /v1/tools/{id}/run

Starts a run and waits up to `wait` seconds for it. The run's maximum cost (per-call fee plus per-result fee times the cap) is held from your balance and settled when it ends. Needs an API key.

Returns `200` with the finished [run object](#the-run-object) and its `items`, or `202` with the running run and a `poll` path. A `200` can carry a run that `failed` or `timed_out`: check `status`. Any error status means the run didn't start and nothing was charged.

You can also send the tool id in the body: `POST /v1/tools/run` with `"tool": "google-maps-places"`.

Running `browser-agent` returns `202` and a browser run instead, with `max_results` as the maximum number of steps. See [Browser agent](/docs/browser-agent).

:::params Path parameters
- `id` string, required
  The tool id.
:::

:::params Body parameters
- `input` object, default `{}`
  The tool's input, matching its `input_schema`. Fields in `input_schema.required` must be set. Up to 50,000 characters as JSON.
- `max_results` integer
  Most results to return and bill, up to the tool's `max_results`. Defaults to the tool's `default_results`, or for tools that take a list of entries with one result each, to the list's length.
- `wait` integer, default `60`
  Seconds to wait for the run to finish, from 0 to 300. Also accepted as a `?wait=` query parameter.
:::

```bash title="cURL" aside
curl https://lovelyrouter.com/v1/tools/google-maps-places/run \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "input": {"searchTerms": ["coffee shop"], "location": "Austin, Texas, USA"},
    "max_results": 20
  }'
```

```python title="Python" aside
import os
import requests

res = requests.post(
    "https://lovelyrouter.com/v1/tools/google-maps-places/run",
    headers={"Authorization": f"Bearer {os.environ['LOVELY_API_KEY']}"},
    json={
        "input": {"searchTerms": ["coffee shop"], "location": "Austin, Texas, USA"},
        "max_results": 20,
    },
    timeout=90,
)
run = res.json()  # 200: finished, 202: poll run["poll"]
```

```js title="Node.js" aside
const res = await fetch('https://lovelyrouter.com/v1/tools/google-maps-places/run', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.LOVELY_API_KEY}`,
    'content-type': 'application/json',
  },
  body: JSON.stringify({
    input: { searchTerms: ['coffee shop'], location: 'Austin, Texas, USA' },
    max_results: 20,
  }),
});
const run = await res.json(); // 200: finished, 202: poll run.poll
```

:::aside
```json title="Response (202)"
{
  "id": "run_8c1f0e2a9b7d4f3e6a5c2b1d",
  "object": "tool_run",
  "tool": "google-maps-places",
  "tool_name": "Search Google Maps places",
  "status": "running",
  "results": 0,
  "max_results": 20,
  "charge_usd": null,
  "reserved_usd": 0.00551,
  "error": null,
  "created_at": "2026-10-11T15:02:11.204Z",
  "finished_at": null,
  "poll": "/v1/tools/runs/run_8c1f0e2a9b7d4f3e6a5c2b1d?wait=60"
}
```
:::

## Retrieve a run

::endpoint GET /v1/tools/runs/{run_id}

Returns a run, optionally waiting for it to finish. Polling a finished run settles it if it hasn't been settled yet. Returns `202` while the run is going and `200` once it has finished, with its results in `items`. Runs belonging to another account return `404`.

For a `browser-agent` run, this returns the browser run. See [Browser agent](/docs/browser-agent).

:::params Path parameters
- `run_id` string, required
  The run id.
:::

:::params Query parameters
- `wait` integer, default `0`
  Seconds to wait for a running run to finish, from 0 to 300.
- `offset` integer, default `0`
  Index of the first result to return.
- `limit` integer
  Number of results to return, from 1 to the run's `max_results`, which is also the default.
:::

```bash title="cURL" aside
curl "https://lovelyrouter.com/v1/tools/runs/run_8c1f0e2a9b7d4f3e6a5c2b1d?wait=60&offset=0&limit=50" \
  -H "Authorization: Bearer $LOVELY_API_KEY"
```

:::aside
```json title="Response"
{
  "id": "run_8c1f0e2a9b7d4f3e6a5c2b1d",
  "object": "tool_run",
  "tool": "google-maps-places",
  "tool_name": "Search Google Maps places",
  "status": "succeeded",
  "results": 20,
  "max_results": 20,
  "charge_usd": 0.00551,
  "reserved_usd": 0,
  "error": null,
  "created_at": "2026-10-11T15:02:11.204Z",
  "finished_at": "2026-10-11T15:02:38.917Z",
  "items": [
    { "title": "Godsent Coffee", "totalScore": 5, "reviewsCount": 237, "placeId": "ChIJq7RZDGczW4YRyCS5Z0KiiDE" }
  ],
  "offset": 0,
  "next_offset": null
}
```
:::

## Stop a run

::endpoint POST /v1/tools/runs/{run_id}/stop

Stops a running run. It ends as `stopped`, and you pay the per-result fee for results delivered before it stopped, with no per-call fee. Returns the settled run with those results, or `202` if it is still winding down after about 10 seconds. Stopping a finished run returns it unchanged.

:::params Path parameters
- `run_id` string, required
  The run id.
:::

```bash title="cURL" aside
curl -X POST https://lovelyrouter.com/v1/tools/runs/run_8c1f0e2a9b7d4f3e6a5c2b1d/stop \
  -H "Authorization: Bearer $LOVELY_API_KEY"
```

:::aside
```json title="Response"
{
  "id": "run_8c1f0e2a9b7d4f3e6a5c2b1d",
  "object": "tool_run",
  "tool": "google-maps-places",
  "tool_name": "Search Google Maps places",
  "status": "stopped",
  "results": 4,
  "max_results": 20,
  "charge_usd": 0.018,
  "reserved_usd": 0,
  "error": "The run was stopped. You are billed only for results delivered before it stopped.",
  "created_at": "2026-10-11T15:02:11.204Z",
  "finished_at": "2026-10-11T15:02:19.630Z",
  "items": [
    { "title": "Godsent Coffee", "totalScore": 5, "reviewsCount": 237, "placeId": "ChIJq7RZDGczW4YRyCS5Z0KiiDE" }
  ],
  "offset": 0,
  "next_offset": null
}
```
:::

## List runs

::endpoint GET /v1/tools/runs

Returns your account's runs, newest first, without their items. Includes runs started with any of your keys.

:::params Query parameters
- `limit` integer, default `20`
  Number of runs, from 1 to 100.
- `before` integer
  Cursor: the `next` value from the previous page.
:::

Returns a list object with run objects in `data`, and `next`: the cursor for the next page, or `null` when there are no more runs.

```bash title="cURL" aside
curl "https://lovelyrouter.com/v1/tools/runs?limit=2" \
  -H "Authorization: Bearer $LOVELY_API_KEY"
```

:::aside
```json title="Response"
{
  "object": "list",
  "data": [
    {
      "id": "run_8c1f0e2a9b7d4f3e6a5c2b1d",
      "object": "tool_run",
      "tool": "google-maps-places",
      "tool_name": "Search Google Maps places",
      "status": "succeeded",
      "results": 20,
      "max_results": 20,
      "charge_usd": 0.00551,
      "reserved_usd": 0,
      "error": null,
      "created_at": "2026-10-11T15:02:11.204Z",
      "finished_at": "2026-10-11T15:02:38.917Z"
    },
    {
      "id": "run_51d0b9e4c3a2f1e0d9c8b7a6",
      "object": "tool_run",
      "tool": "trustpilot-reviews",
      "tool_name": "Trustpilot reviews",
      "status": "succeeded",
      "results": 20,
      "max_results": 20,
      "charge_usd": 0.0375,
      "reserved_usd": 0,
      "error": null,
      "created_at": "2026-10-11T14:48:03.512Z",
      "finished_at": "2026-10-11T14:48:21.077Z"
    }
  ],
  "next": 1791730083512
}
```
:::

## Errors

Every error has the same shape. `type` is stable and safe to branch on. `message` is for people and can change.

| Status | `type` | Cause |
| --- | --- | --- |
| `400` | `invalid_request_error` | Body isn't JSON, `input` isn't an object, a required input field is missing, the tool rejected the input, or `query` / `task` is missing |
| `401` | `authentication_error` | Missing, invalid, revoked or expired API key |
| `402` | `insufficient_quota` | Balance or the key's spend limit can't cover the run's hold |
| `404` | `not_found` | Unknown tool id, or no run with that id on your account |
| `413` | `invalid_request_error` | `input` over 50,000 characters, or `task` over 2,000 |
| `429` | `rate_limit_exceeded` | 10 runs already in progress on your account, or the planner rate limit |
| `500` | `api_error` | Unexpected error. Retry. |
| `502` | `tool_unavailable` | The tool could not start. You were not charged. Retry shortly. |

A run request that returns an error is not charged. See [Errors](/docs/api/errors) for the API as a whole.

:::aside
```json title="Error"
{
  "error": {
    "type": "invalid_request_error",
    "message": "Missing required input: companyUrls. See GET /v1/tools/trustpilot-reviews."
  }
}
```
:::
