# Search places

Canonical page: https://agentsky.dev/tools/googlemap/places
JSON record: https://gateway.agentsky.dev/v1/endpoints/googlemap/places

Search places is a tool from Google Maps, callable through the AgentSky API as googlemap/places and billed per call ($0.0384).

Searches for places from an ordinary text query such as a cuisine, business name, or category with a location, and returns matching places with their identifiers, names, and addresses. Results can be biased toward a circle or rectangle, which prefers nearby places without excluding others, or narrowed by place type, minimum rating, price level, and whether the business is open now. Ranking is selectable between relevance and distance, and results page up to sixty in total.

A list of places with their id, display name, and formatted address, plus a page token when more remain.

## Facts

- **Id:** `googlemap/places`
- **Provider:** Google Maps
- **Kind:** tool
- **Billed per:** call
- **Timeout:** 5 min
- **Max request:** 1 MB

## Call it

`POST https://gateway.agentsky.dev/v1/run` with `{"provider": "googlemap", "endpoint": "places", "input": {…}}` and `Authorization: Bearer $AGENTSKY_API_KEY`.

### Input fields

| Field | Type | Required | Description |
|---|---|---|---|
| `textQuery` | string | yes | What to search for, including the place or area. |
| `pageSize` | integer | no | How many places to return per page. Default: 20. Min: 1. Max: 20. |
| `includedType` | string | no | Restrict results to a single place type. |
| `openNow` | boolean | no | Only return places open at the time of the request. |
| `minRating` | number | no | Lowest average rating to accept. Min: 0. Max: 5. |
| `locationBias` | object | no | A circle or rectangle to prefer results near. |
| `languageCode` | string | no | Language of the returned place names. |

## Example

### curl

```bash
# Set once per job; reuse this key and the identical body after a timeout.
: "${AGENTSKY_IDEMPOTENCY_KEY:?Set a unique, saved key for this job}"
curl --fail-with-body https://gateway.agentsky.dev/v1/run \
  -H "authorization: Bearer $AGENTSKY_API_KEY" \
  -H "content-type: application/json" \
  -H "Idempotency-Key: $AGENTSKY_IDEMPOTENCY_KEY" \
  -d '{"provider":"googlemap","endpoint":"places","input":{"textQuery":"coffee in Sydney, Australia","pageSize":10,"languageCode":"en"}}'
# If status is READY/RUNNING, poll GET /v1/runs/{runId} with the same API key.
```

### JavaScript

```js
// Node.js 20+. Save one unique key per job; reuse it with the identical body.
const key = process.env.AGENTSKY_IDEMPOTENCY_KEY;
if (!key || !process.env.AGENTSKY_API_KEY) {
  throw new Error("Set AGENTSKY_API_KEY and a saved AGENTSKY_IDEMPOTENCY_KEY first");
}
const base = "https://gateway.agentsky.dev";
const headers = {
  authorization: `Bearer ${process.env.AGENTSKY_API_KEY}`,
  "content-type": "application/json",
  "Idempotency-Key": key,
};
const body = JSON.stringify({
  "provider": "googlemap",
  "endpoint": "places",
  "input": {
    "textQuery": "coffee in Sydney, Australia",
    "pageSize": 10,
    "languageCode": "en"
  }
});
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function request(path, options = {}) {
  for (let attempt = 0; attempt < 3; attempt++) {
    let response;
    let data;
    try {
      response = await fetch(base + path, {
        ...options, headers, signal: AbortSignal.timeout(180_000),
      });
      const text = await response.text();
      try { data = JSON.parse(text); } catch { /* Proxy timeouts may return HTML. */ }
    } catch (error) {
      if (attempt === 2) throw error; // Resume with the same key and body.
      await sleep(2000);
      continue;
    }
    // Failed runs can have non-2xx HTTP status. Never regenerate them automatically.
    if (data?.runId && data?.status) return data;
    if (response.ok && data) throw new Error("Unexpected response: missing runId/status");
    if (attempt < 2 && (response.status >= 500 || response.status === 408 || response.status === 429)) {
      await sleep(2000);
      continue; // POST retries keep the original key and body.
    }
    throw new Error(`HTTP ${response.status}: ${JSON.stringify(data ?? "Non-JSON response; retry with the same key and body")}`);
  }
}

let run = await request("/v1/run", { method: "POST", body });
console.log("Save runId:", run.runId);
const deadline = Date.now() + 30 * 60_000;
while (["READY", "RUNNING"].includes(run.status)) {
  if (Date.now() >= deadline) {
    throw new Error(`Still pending: poll /v1/runs/${run.runId} later; do not create a new job`);
  }
  await sleep(5000);
  run = await request(`/v1/runs/${encodeURIComponent(run.runId)}`);
}
console.log("Status:", run.status, "Charge:", run.price?.amount);
if (run.status !== "COMPLETED") {
  throw new Error(`Run ${run.runId}: ${JSON.stringify(run.error ?? run.status)}`);
}
console.log(run.output); // Output shape depends on the endpoint.
// READY/RUNNING price is provisional. Use the terminal receipt for reconciliation.
```

## Pricing

$0.0384 per call

Read the final customer charge from price.amount.value after the run finishes. A RUNNING response is not the final bill.

## Questions

### What does Search places do?

Searches for places from an ordinary text query such as a cuisine, business name, or category with a location, and returns matching places with their identifiers, names, and addresses. Results can be biased toward a circle or rectangle, which prefers nearby places without excluding others, or narrowed by place type, minimum rating, price level, and whether the business is open now. Ranking is selectable between relevance and distance, and results page up to sixty in total.

### How much does Search places cost?

$0.0384 per call, using your plan when signed in. The customer charge is returned as price.amount.value with its currency; READY/RUNNING values are provisional.

### What does Search places take?

textQuery is required; the rest of the fields are optional. Send them in the input object of a POST to /v1/run.

### What are the limits on Search places?

The catalog lists an execution timeout of 5 min. A request can return READY or RUNNING before completion; poll GET /v1/runs/{runId} with the same API key until it reaches a terminal status. An HTTP timeout does not prove the generation stopped.

### How do I retry safely and check the final charge?

Save a unique Idempotency-Key for each job. After a network error or HTML timeout, resend the identical POST body with the same key and API key to recover the existing run, then poll its runId. A new key can start another billable generation. An HTTP error or timeout does not establish the run outcome or final charge. Pending runs can still settle; check the terminal price.amount and billing state, and retain the runId for reconciliation. Do not automatically create a new job after a terminal failure.
