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 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_resultsintegerThe 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 60Seconds 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:
| Status | Meaning |
|---|---|
200 | The run finished. The body has its final status, its charge and the results in items. |
202 | The run is still going. The body has its id and a poll path. Poll it. |
4xx, 5xx | The run did not start, and nothing was charged. See errors. |
Read the result#
A run that finishes in time returns 200:
{
"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.itemsholds up tomax_resultsresults.failed,timed_outorstopped: the run ended early.errorexplains why, anditemsholds 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:
{
"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 "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 "https://lovelyrouter.com/v1/tools/runs/run_8c1f0e2a9b7d4f3e6a5c2b1d?offset=100&limit=100" \
-H "Authorization: Bearer $LOVELY_API_KEY"offsetis the index of the first result to return. The default is0.limitis the page size, from 1 up to the run'smax_results, which is also the default.- The response echoes
offsetand setsnext_offsetto the offset of the next page, ornullon 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 -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:
| Field | Meaning |
|---|---|
id | Run id, run_ followed by 24 characters. Use it to poll or stop the run. |
status | running, succeeded, failed, timed_out or stopped |
results | Results returned and billed. 0 until the run finishes. |
max_results | The result cap that applies to this run |
charge_usd | What the run cost. null until it is settled. |
reserved_usd | The hold on your balance while the run is unsettled. 0 after. |
error | Why 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.
| Outcome | Charge |
|---|---|
succeeded | Per-call fee + per-result fee × results |
failed, timed_out | Per-result fee × results delivered before it ended |
stopped | Per-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_resultsif it is higher. - You leave it out. The tool's
default_resultsapplies. 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_resultsalready 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:
{
"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.
| Status | type | Cause | What to do |
|---|---|---|---|
400 | invalid_request_error | The body isn't JSON, input isn't an object, a required input field is missing, or the tool rejected the input | Check input against the tool's input_schema. The message names missing fields. |
401 | authentication_error | Missing, invalid, revoked or expired API key | Send a valid key in Authorization: Bearer. |
402 | insufficient_quota | The balance or the key's spend limit can't cover the hold | Lower max_results or top up. |
404 | not_found | Unknown tool id, or a run id that doesn't exist or belongs to another account | Check the id. Find tools with POST /v1/tools/discover. |
413 | invalid_request_error | input is over 50,000 characters | Send less input per run. |
429 | rate_limit_exceeded | 10 runs are already in progress on your account | Wait for one to finish, then retry. |
502 | tool_unavailable | The tool could not start | Retry 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 "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.