# Willhaben Locations API Documentation

<p>Scrappa's `GET /api/willhaben/locations` endpoint is the Willhaben Locations API documentation page for developers who need Austrian provinces and districts as structured JSON.

    ## What this endpoint returns

    Each response returns one level of the Austrian location tree with the live listing count for every value, so you can build a location picker or scope a search to a region. Omit `area_id` for the nine provinces; pass the id of a province as `area_id` to descend to its districts. A response names the level it answered at in `navigatorId`, because Willhaben serves the walk from its own facet navigator rather than a fixed tree, and that id is what tells you which vocabulary you are looking at. Expect `province` at the top and `district` one level below. `navigatorId` is null when the response carries no values at all — an `area_id` with nothing under it — so treat null as empty rather than as a level. Levels deeper than a district exist upstream and are passed through verbatim, but a descent past a district has not been verified against a captured payload, so treat anything below `district` as undocumented until you have seen it yourself.

    ## How to use it

    Resolve a location first, then pass the id to [Willhaben Search](/docs/willhaben-api/willhaben_search) as `area_id`. Use [Autocomplete](/docs/willhaben-api/willhaben_autocomplete) when your users type a place name instead of browsing a list.</p>

- **Documentation:** [https://scrappa.co/docs/willhaben-api/willhaben_locations](https://scrappa.co/docs/willhaben-api/willhaben_locations)
- **API group:** Willhaben API
- **Endpoint:** `GET https://scrappa.co/api/willhaben/locations`

## Authentication

Send your Scrappa API key in the `X-API-KEY` request header. Paid endpoints also support accountless x402 payments when called without an API key.

## Parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `area_id` | integer | No | Parent location id to descend one level. Omit for provinces. |
| `vertical` | integer | No | Vertical id the counts are measured against. One of: 12 Ferienimmobilien kaufen, 14 Grundstuecke, 15 Gewerbe kaufen, 16 Gewerbe mieten, 32 Ferienimmobilien mieten, 35 Sonstige, 42 Neubauprojekte, 90 Immobilien, 101 Eigentumswohnung, 102 Haus kaufen, 131 Mietwohnungen, 132 Haus mieten. Defaults to 131 (Mietwohnungen). |

## Example response

```json
{
    "data": {
        "locations": [
            {
                "id": 3,
                "hits": 4133,
                "label": "Nieder\u00f6sterreich"
            },
            {
                "id": 900,
                "hits": 4819,
                "label": "Wien"
            }
        ],
        "rowsFound": 19604,
        "navigatorId": "province",
        "parentAreaId": null
    },
    "success": true
}
```

## Errors

| Status | Error | Description |
| --- | --- | --- |
| 404 | Not Found | The requested listing, agency or seller does not exist upstream. Non-billable, and not retryable: a different exit returns the same 404. |
| 422 | Validation Error | A query parameter failed validation — a missing required id, an unknown vertical, or a value outside the documented range. Messages are per field in Laravel's standard wording, so the accepted vertical ids are published on the `vertical` parameter of each page rather than repeated in the message. Non-billable. |
| 503 | Upstream Rejected The Request | Willhaben answered with a JSON application error — an invalid filter, or a sort id that the vertical does not offer. Non-billable, and not retryable: the same request fails the same way on every exit. Published as 503 because SanitizeApiErrorResponse rewrites every upstream 502 before the response leaves the app; branch on `meta.retryable` rather than on the status code. |
| 503 | Temporarily Unavailable | Willhaben could not be reached at all: no exit was available, the transport failed, the edge refused every exit, or every exit was rate limited. All of these rotate and retry, so this row is always retryable. The `failure_type` says which one happened — `upstream_unreachable`, `transport_error`, `edge_blocked`, `rate_limited` or `attempts_exhausted` — so a caller can tell a dead provider from a throttled one. Non-billable; a later request with a fresh pool fetch is worth making. |

## More Scrappa resources

- [API documentation](https://scrappa.co/docs)
- [Full LLM-readable API reference](https://scrappa.co/llms-full.txt)
- [OpenAPI specification](https://scrappa.co/docs/api.json)
- [Pricing](https://scrappa.co/pricing)
