Lovely RouterDocs
Get a key
Data tools

Run a tool

Start a run, get results in one call or poll for them, page through large result sets, stop a run early, and see exactly what it cost.

A run sends your input to a tool, holds its maximum cost from your balance, and returns the results as JSON. Most runs finish in 5 to 60 seconds. You need an API key and enough balance to cover the run's maximum cost.

Start a run#

Send the tool's input and a result cap to POST /v1/tools/{id}/run:

cURL
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",
      "language": "en"
    },
    "max_results": 20
  }'

Body parameters

inputobjectDefault {}

The tool's input, matching its input_schema from GET /v1/tools/{id}. Fields in input_schema.required must be set. Up to 50,000 characters as JSON.

max_resultsinteger

The most results the run returns and bills. Defaults to the tool's default_results. Values above the tool's max_results are lowered to it. See how the cap is set.

waitintegerDefault 60

Seconds to wait for the run to finish before answering, from 0 to 300. You can also pass it as the ?wait= query parameter.

The request waits for the run. What comes back depends on whether it finished within wait seconds:

StatusMeaning
200The run finished. The body has its final status, its charge and the results in items.
202The run is still going. The body has its id and a poll path. Poll it.
4xx, 5xxThe run did not start, and nothing was charged. See errors.

Read the result#

A run that finishes in time returns 200:

JSON
{
  "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",
      "categories": ["Coffee shop"],
      "formattedAddress": "9817 Anderson Mill Rd, Austin, TX 78750",
      "website": "https://www.godsentcoffee.com/",
      "rating": 5,
      "reviewCount": 237,
      "placeId": "ChIJq7RZDGczW4YRyCS5Z0KiiDE",
      "googleMapsUri": "https://www.google.com/maps/search/?api=1&query=Godsent%20Coffee&query_place_id=ChIJq7RZDGczW4YRyCS5Z0KiiDE"
    }
  ],
  "offset": 0,
  "next_offset": null
}

A 200 means the run finished, not that it succeeded. Check status:

  • succeeded: the run completed. items holds up to max_results results.
  • failed, timed_out or stopped: the run ended early. error explains why, and items holds whatever it delivered before it ended. You pay only for those results.

Each item is one result in the tool's own format. Field names differ between tools.

Poll a long run#

A run still going after wait seconds comes back with 202:

JSON
{
  "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"
}

Call GET /v1/tools/runs/{run_id} until status is no longer running. Pass ?wait=60 so each request waits on the server for up to 60 seconds instead of returning at once. You don't need to sleep between polls.

cURL
curl "https://lovelyrouter.com/v1/tools/runs/run_8c1f0e2a9b7d4f3e6a5c2b1d?wait=60" \
  -H "Authorization: Bearer $LOVELY_API_KEY"

The response has the same shape and status codes as the run request: 202 while running, 200 with items once finished. results stays 0 until the run finishes.

If you stop polling, the run still finishes and is settled automatically within minutes, so its hold is never stuck.

Complete example#

Start a run, poll until it finishes, then print the results. Set LOVELY_API_KEY first.

# Requires jq.
RUN=$(curl -s 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}')
ID=$(echo "$RUN" | jq -r .id)

while [ "$(echo "$RUN" | jq -r .status)" = "running" ]; do
  RUN=$(curl -s "https://lovelyrouter.com/v1/tools/runs/$ID?wait=60" \
    -H "Authorization: Bearer $LOVELY_API_KEY")
done

echo "$RUN" | jq '{status, results, charge_usd, error}'
echo "$RUN" | jq -r '.items[]? | "\(.title)  \(.rating)"'

Page through results#

By default a finished run returns all its results at once. For large runs, or to fetch them again, read them in pages with offset and limit on GET /v1/tools/runs/{run_id}:

cURL
curl "https://lovelyrouter.com/v1/tools/runs/run_8c1f0e2a9b7d4f3e6a5c2b1d?offset=100&limit=100" \
  -H "Authorization: Bearer $LOVELY_API_KEY"
  • offset is the index of the first result to return. The default is 0.
  • limit is the page size, from 1 up to the run's max_results, which is also the default.
  • The response echoes offset and sets next_offset to the offset of the next page, or null on the last page.

Reading results again is free. You are billed once, when the run settles.

If results can't be loaded for a moment, the response has "items": null and an items_error message. The run and its charge are unaffected. Request the same page again.

Stop a run#

Stop a run you no longer need with POST /v1/tools/runs/{run_id}/stop:

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

The run ends with status stopped. You pay the per-result fee for results it delivered before it stopped, and no per-call fee. The response is the settled run with those results in items. Stopping a run that has already finished returns it unchanged.

Stopping usually takes a few seconds. If the run hasn't wound down within about 10 seconds, the response is 202 and you poll it as usual.

The run object#

Every run response is a run object. The fields you'll use most:

FieldMeaning
idRun id, run_ followed by 24 characters. Use it to poll or stop the run.
statusrunning, succeeded, failed, timed_out or stopped
resultsResults returned and billed. 0 until the run finishes.
max_resultsThe result cap that applies to this run
charge_usdWhat the run cost. null until it is settled.
reserved_usdThe hold on your balance while the run is unsettled. 0 after.
errorWhy the run ended early, or null

The run object in the API reference lists every field.

Billing#

Starting a run holds its maximum cost from your balance: the per-call fee plus the per-result fee times max_results. While the run is going, that hold shows as reserved_usd. When the run ends, it is charged for what it returned, charge_usd is set, and the rest of the hold goes back to your balance.

OutcomeCharge
succeededPer-call fee + per-result fee × results
failed, timed_outPer-result fee × results delivered before it ended
stoppedPer-result fee × results delivered before you stopped it
Refused at start (any 4xx or 5xx)Nothing. No hold is kept.

The charge never exceeds the hold. Tool spend counts toward your balance and the key's spend limit like model spend, and every run is listed with its charge in Tools in your dashboard. For pricing in general, see Data tools.

How max_results is set#

The cap the run actually uses is in the run object's max_results, and the hold is based on it:

  • You set it. Your value is used, lowered to the tool's max_results if it is higher.
  • You leave it out. The tool's default_results applies. For tools that return one result per entry in an input list, such as a list of profile handles or URLs, the default is the length of that list.
  • Minimum run size. A few tools only run in batches of a minimum size. If you ask for fewer, the cap is raised to that minimum. Those tools' default_results already reflects it.

max_results also caps the tool's own count field if it has one, such as maxResults or maxReviewsPerCompany, so you don't need to set both. If you set the count field lower, the run may return fewer results, but the hold is still based on max_results. An input list longer than max_results is cut to max_results entries.

Insufficient balance#

If your balance can't cover the hold, the run is refused with 402:

JSON
{
  "error": {
    "type": "insufficient_quota",
    "message": "Insufficient balance: this run can cost up to $0.04835 (200 places). Lower max_results or top up."
  }
}

Lower max_results, or add credit. If the hold would take the API key past its own spend limit, the run gets 402 with This run can cost up to $…, which would take this API key past its spend limit.

Errors#

Errors have the same shape as the rest of the API: an error object with a type and a readable message. Nothing is charged for a request that returns an error.

StatustypeCauseWhat to do
400invalid_request_errorThe body isn't JSON, input isn't an object, a required input field is missing, or the tool rejected the inputCheck input against the tool's input_schema. The message names missing fields.
401authentication_errorMissing, invalid, revoked or expired API keySend a valid key in Authorization: Bearer.
402insufficient_quotaThe balance or the key's spend limit can't cover the holdLower max_results or top up.
404not_foundUnknown tool id, or a run id that doesn't exist or belongs to another accountCheck the id. Find tools with POST /v1/tools/discover.
413invalid_request_errorinput is over 50,000 charactersSend less input per run.
429rate_limit_exceeded10 runs are already in progress on your accountWait for one to finish, then retry.
502tool_unavailableThe tool could not startRetry shortly.

See Errors for how errors work across the API.

List your runs#

GET /v1/tools/runs returns your account's runs, newest first, without their items:

cURL
curl "https://lovelyrouter.com/v1/tools/runs?limit=20" \
  -H "Authorization: Bearer $LOVELY_API_KEY"

limit is 1 to 100 (default 20). When there are more runs, the response's next is a cursor: pass it as ?before= to get the next page. When next is null, you have them all.

Run the browser agent#

The browser agent is in the catalog as browser-agent, so POST /v1/tools/browser-agent/run starts it too. Put its goal and start_url in input, and set max_results to the maximum number of steps. It returns 202 and a browser run, which you follow with the browser API. See Browser agent.