> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cyclemate.club/llms.txt
> Use this file to discover all available pages before exploring further.

# Place Autocomplete

> Proxied Google Places (New) autocomplete — dual unrestricted + service-area-bounded predictions in one call

## Overview

Backs the planner place search (`packages/shared/components/planner/lib/googlePlaces.ts`). The server fans out **two** Google Places API (New) `places:autocomplete` calls in parallel — one unrestricted (biased to a 50 km circle around `origin` when provided) and, when `bbox` is present, one hard-bounded to that rectangle — and returns both prediction lists. The client compares the two to flag results in/out of the operating city's service area.

The Google API key lives only on the server (`GOOGLE_PLACES_API_KEY` function secret, API-scoped to Places API (New), no application restriction). Clients carry no Google key.

Predictions carry **no geometry** — resolve a picked prediction with [Place Details](/api-reference/places/details), which also closes the billing session.

## Authentication

Any authenticated caller (user session, publishable key, or service key):

```
Authorization: Bearer YOUR_API_KEY
```

Rate limited per user (signed-in) or IP (anonymous): **120 / 60 requests per 60s** shared with `/place-search/details`. Breaches return `429` with a `Retry-After` header.

## Body Parameters

<ParamField body="input" type="string" required>
  The typed query. Non-empty, at most 256 characters.
</ParamField>

<ParamField body="sessionToken" type="string" required>
  UUID minted by the client on the first keystroke of a typing interaction and reused for every subsequent autocomplete call, then passed to the details call. Google bills the whole interaction as **one** Autocomplete session. At most 128 characters.
</ParamField>

<ParamField body="origin" type="object">
  `{ latitude, longitude }` — the map center. Enables `distanceMeters` on predictions (straight-line meters from this point) and the 50 km `locationBias` circle on the unrestricted call.
</ParamField>

<ParamField body="bbox" type="number[]">
  `[minLng, minLat, maxLng, maxLat]` — the active service-area rectangle. When present, the second (bounded) Google call runs with this as a `locationRestriction`. Values must be valid lng/lat with min \< max.
</ParamField>

## Response

<ResponseField name="unrestricted" type="Prediction[]">
  Predictions from the unbounded call, in Google's ranking order. Each prediction is **slimmed to exactly the fields the client reads** — nothing else survives the proxy:

  `placeId` (string) · `text.text` (string, the full prediction text, e.g. `"Waterloo Station, London, UK"`) · `structuredFormat.mainText.text` / `structuredFormat.secondaryText.text` (strings) · `types` (string\[], Google's underscored types) · `distanceMeters` (number, only when `origin` was sent — may be absent on e.g. `route` rows).

  Absent fields are omitted, never `null`. Suggestions without a `placePrediction` (query predictions) are dropped.
</ResponseField>

<ResponseField name="restricted" type="Prediction[] | null">
  Predictions from the `bbox`-bounded call, same slimming. `null` when no `bbox` was sent **or when the bounded call failed** — the bounded leg only powers the in/outside-service-area partition, so its failure degrades to a flat list rather than failing the search. Clients must treat `null` as "no partition available".
</ResponseField>

## Example

```bash theme={null}
curl -s -X POST 'https://YOUR_PROJECT.supabase.co/functions/v1/place-search/autocomplete' \
  -H 'Authorization: Bearer YOUR_PUBLISHABLE_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "input": "waterloo",
    "sessionToken": "33333333-3333-4333-8333-333333333333",
    "origin": { "latitude": 51.5074, "longitude": -0.1278 },
    "bbox": [-0.51, 51.28, 0.33, 51.69]
  }'
```

```json theme={null}
{
  "unrestricted": [
    {
      "placeId": "ChIJq5ZP7bkEdkgRehp2VdTG7Vs",
      "text": { "text": "Waterloo Station, London, UK" },
      "structuredFormat": {
        "mainText": { "text": "Waterloo Station" },
        "secondaryText": { "text": "London, UK" }
      },
      "types": ["transit_station", "establishment", "point_of_interest"],
      "distanceMeters": 1099
    }
  ],
  "restricted": [ { "placeId": "ChIJq5ZP7bkEdkgRehp2VdTG7Vs", "...": "..." } ]
}
```

## Errors

| Status | Meaning                                                                                                                                                     |
| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid JSON or a failed parameter check (message in `error`).                                                                                              |
| `401`  | Missing/invalid credentials.                                                                                                                                |
| `404`  | Unknown `place-search` subroute.                                                                                                                            |
| `429`  | Rate limited — `retry_after_seconds` in the body, `Retry-After` header set.                                                                                 |
| `502`  | The **unrestricted** Google call failed — body carries `upstream_status`. The planner client treats this as outage-shaped and falls back to Mapbox for 60s. |
| `503`  | `GOOGLE_PLACES_API_KEY` is not configured on the server.                                                                                                    |
| `504`  | The Google call exceeded the 4s upstream timeout.                                                                                                           |
