# Places API > Address autocomplete and structured lookup API for the Netherlands, Belgium and Luxembourg. Base URL: `https://places-api.com/api/v1` Full OpenAPI spec: [/openapi.yaml](https://places-api.com/openapi.yaml) Interactive docs: [/docs.html](https://places-api.com/docs.html) ## Getting started 1. Create an account at [places-api.com/users/register](https://places-api.com/users/register) 2. Generate an API key at [places-api.com/users/api-keys](https://places-api.com/users/api-keys) 3. Pass the key in the `X-API-Key` header with every request ## Authentication All requests require an API key in the `X-API-Key` header. Two key types: - **Secret keys** (`sk_` prefix) — server-side use, no domain validation - **Publishable keys** (`pk_` prefix) — client-side/browser use, validates `Origin`/`Referer` against the key's allowed domains (supports wildcards like `*.example.com` and `localhost`) ## GET /autocomplete Search for addresses by query string. ### Parameters | Name | Type | Required | Default | Description | |------|------|----------|---------|-------------| | `q` | string | yes | — | Search query, minimum 2 characters | | `country` | string | no | all | Comma-separated ISO codes: `NL`, `BE`, `LU` | | `limit` | integer | no | 10 | Max results, 1–50 | ### Response ```json { "results": [ { "display": "Kerkstraat 1, 1234AB Amsterdam", "street": "Kerkstraat", "housenumber": "1", "housenumber_addition": null, "postcode": "1234AB", "city": "Amsterdam", "country": "NL" } ], "count": 1 } ``` **Fields:** - `display` (string) — formatted full address - `street` (string) — street name (language-matched for Belgian addresses) - `housenumber` (string) — house number - `housenumber_addition` (string|null) — suffix like A, B, bis - `postcode` (string) — postal code - `city` (string) — city name (language-matched for Belgian addresses) - `country` (string) — `NL`, `BE`, or `LU` ### Errors | Status | Error | When | |--------|-------|------| | 400 | `Missing required parameter: q` | `q` param absent | | 400 | `Query must be at least 2 characters` | `q` too short | | 401 | `Missing or invalid API key` | bad or missing `X-API-Key` | | 403 | `Domain not allowed for this key` | publishable key + wrong origin | | 422 | `Invalid country code(s): XX. Valid codes: NL, BE, LU` | invalid `country` value | | 429 | `Rate limit exceeded` | burst or monthly limit hit | On 429, response headers include: `ratelimit-limit`, `ratelimit-remaining`, `ratelimit-reset` (seconds). ## GET /lookup Structured address lookup by postcode and house number. Returns exact matches, not fuzzy search results. ### Parameters | Name | Type | Required | Default | Description | |------|------|----------|---------|-------------| | `postcode` | string | yes | — | Postal code (e.g., `1234AB`, `1000`). NL codes are auto-normalized. | | `housenumber` | string | no | — | House number (e.g., `42`). Omit for postcode-level info. | | `country` | string | yes | — | ISO code: `NL`, `BE`, or `LU` | ### Response **Single match** (postcode + housenumber → one address): ```json { "status": "found", "address": { "street": "Kerkstraat", "housenumber": "1", "housenumber_addition": null, "postcode": "1234AB", "city": "Amsterdam", "country": "NL" } } ``` **Multiple additions** (e.g., 42A, 42B exist): ```json { "status": "found", "address": { "street": "Keizersgracht", "housenumber": "42", "housenumber_addition": null, "postcode": "1015CS", "city": "Amsterdam", "country": "NL" }, "additions": ["A", "B", "I", "II", "hs"] } ``` **Postcode only (NL)** — returns street + house number range: ```json { "status": "found", "street": "Meidoornlaan", "city": "Roosendaal", "country": "NL", "housenumber_range": { "min": 1, "max": 89 } } ``` **Postcode only (BE/LU)** — returns city only (postcodes cover entire municipalities): ```json { "status": "found", "city": "Brussel", "country": "BE" } ``` **Not found:** ```json { "status": "not_found" } ``` ### Errors | Status | Error | When | |--------|-------|------| | 400 | `Missing required parameter: postcode` | `postcode` absent | | 400 | `Missing required parameter: country` | `country` absent | | 401 | `Missing or invalid API key` | bad or missing `X-API-Key` | | 403 | `Domain not allowed for this key` | publishable key + wrong origin | | 422 | `Invalid country code: XX. Valid codes: NL, BE, LU` | invalid `country` value | | 429 | `Rate limit exceeded` | burst or monthly limit hit | ## Examples ```bash # Basic search curl -H "X-API-Key: YOUR_API_KEY" \ "https://places-api.com/api/v1/autocomplete?q=kerkstraat%201" # Filter by country curl -H "X-API-Key: YOUR_API_KEY" \ "https://places-api.com/api/v1/autocomplete?q=rue%20de%20la%20loi&country=BE" # Multiple countries, limited results curl -H "X-API-Key: YOUR_API_KEY" \ "https://places-api.com/api/v1/autocomplete?q=avenue&country=BE,LU&limit=5" ``` ## Autocomplete Widget A drop-in JavaScript widget is available at `/places-autocomplete.js`. No build step required. ### Method 1: Data Attributes (zero JS) ```html ``` **Data attributes:** | Attribute | Required | Default | Description | |-----------|----------|---------|-------------| | `data-places-autocomplete` | yes | — | Enables auto-init on the input | | `data-places-api-key` | yes | — | Your API key | | `data-places-country` | no | all | Comma-separated: `NL`, `BE`, `LU` | | `data-places-limit` | no | 10 | Max results | | `data-places-debounce` | no | 200 | Debounce delay in ms | | `data-places-min-length` | no | 2 | Min chars before searching | | `data-places-url` | no | `https://places-api.com/api/v1` | API base URL override | ### Method 2: JavaScript API ```javascript const input = document.getElementById("address"); PlacesAutocomplete.init(input, { apiKey: "pk_live_xxx", country: "NL,BE,LU", limit: 5 }); input.addEventListener("places:select", (e) => { console.log(e.detail); // e.detail contains: { display, street, housenumber, // housenumber_addition, postcode, city, country } }); ``` **Options:** `apiKey`, `country`, `limit`, `debounce` (ms, default 200), `minLength` (default 2), `url`. **Methods:** - `PlacesAutocomplete.init(element, options)` — initialize on an input, returns instance - `PlacesAutocomplete.destroy(element)` — remove widget and clean up **Events:** - `places:select` — fired on the input when an address is selected. `event.detail` contains the address object (same shape as the API response). ### Keyboard support Arrow Up/Down to navigate, Enter to select, Escape to close. Full ARIA combobox pattern. ## Lookup Widget Mode The widget also supports a `lookup` mode for the standard Dutch postcode + house number pattern. ### Data Attributes ```html ``` ### JavaScript API ```javascript PlacesAutocomplete.init(postcodeInput, { apiKey: "pk_live_xxx", country: "NL", mode: "lookup", fields: { housenumber: "#housenumber", addition: "#addition-select", street: "#street", city: "#city" } }); postcodeInput.addEventListener("places:lookup", (e) => { console.log(e.detail); // Full lookup API response }); ``` **Lookup mode fields:** `housenumber` (watched), `addition` (populated as `