# OhneMakler Search API Documentation

Scrappa's `GET /api/ohne-makler/search` endpoint returns provisionsfrei (no-agent) German property listings from ohne-makler.net as structured JSON instead of HTML that you would otherwise have to scrape.

## Endpoint reference for ohne-makler.net listing search

One request performs one upstream category-page read and returns up to 24 listing cards plus the page's full embedded state. Structured searches cover 14 property types (`wohnung`, `haus`, `grundstueck`, `zimmer`, `buero`, `einzelhandel`, `gastronomie`, `lagerhalle`, `landwirtschaftliches-objekt`, `wohnen-auf-zeit`, `gewerbliche-freizeitimmobilie`, `zinshaus-rendite`, `sonstiges`, `immobilie`) crossed with `transaction=kaufen|mieten`, addressed through `state` and optional `city` URL segments (German Bundesland and city/Kreis URL slugs exactly as published — examples: `baden-wurttemberg`, `buero`, `grundstueck`, `munchen`, `kreis-augsburg-stadt`). Both segments are optional: state-only searches return the whole state, no segments return a Germany-wide class, and an unknown or rotated slug returns a non-billable 404. `price_min`/`price_max`, `area_min`/`area_max`, `sort`, and `page` (24 results per page) filter and paginate the same single read.

## Free-text search

`q` takes a postal code (`80331`), city (`München`), Kreis (`Landkreis München`), or Bundesland (`Bayern`). It is resolved exactly like the site's own autocomplete (an exact name match wins, otherwise the first suggestion) and the search then runs through the target's search channel, so results are limited to that place: `q=80331&type=wohnung&transaction=kaufen` returns only listings in 80331. The resolved place is returned as `location` (`model` is `ZIPCODE`, `CITY`, `REGION`, or `STATE`). `type` and `transaction` are optional with `q`; without them every property class is searched. Price/area bounds, `sort`, and `page` apply as usual. Location segments (`state`/`city`) do not apply to free-text queries and are rejected with 422.

`radius` (10/25/50/100 km Umkreis) works when `q` resolves to a city, and on `state` + `city` category searches. The site offers no Umkreis for postal codes, Kreise, or states, so `radius` with such a `q` returns a non-billable 422 `radius_requires_city`; search the city name instead. A `q` that matches nothing returns a non-billable 404 `location_not_found`, and a listing object number (`OM-…`) returns a non-billable 422 `object_number_query` pointing to the listing endpoint.

## What the search response returns

Each result carries `id`, `object_number` (`OM-…`), `title`, absolute `url`, `price` plus the `price_on_request` flag (`Kaufpreis: auf Anfrage` becomes `price: null, price_on_request: true` — a genuine absence, not an error), `postal_code`, `city`, `district`, `street` (published only), `rooms`, `living_area_m2`, `plot_area_m2`, `preview_image`, the `seller_type` badge (`"Privatangebot"` when present — `null` never means "not private", the badge simply is not rendered on every listing), and `latitude`/`longitude`. Pagination fields are `page`, `page_size` (24), `total_results`, `last_page`, and `next_page`; requesting a page beyond the last page is a non-billable 404. `sort` accepts `price_asc`, `price_desc`, `date_asc`, `date_desc`. `price_min`/`price_max` and `area_min`/`area_max` are echoed back in `query`, and both `price_histogram` and `area_histogram` cover the whole result set, not just the page.

The response also ships the page's embedded state: `price_histogram` and `area_histogram` as structured buckets (`x`, `count`, `label` — `count: null` means the source published no listing for that bucket), the complete-result `geojson` FeatureCollection with one feature per listing across the whole result set (capped by the source at roughly 1 210 features), `breadcrumbs`, the resolved `search_form` state, and the page's JSON-LD `aggregate_rating` (there is no public review list on this platform — the aggregate rating is the only ratings surface).

An empty result set (0 cards, 0 geojson features) is a valid 200 success, not an error.

## What this API does not cover

This endpoint is read-only listing data. There is no account or watchlist access, no messaging or contact-form submission, no listing creation, and no payments. The platform publishes no price history and no source maps, exposes no GraphQL API, and offers no public review list - the JSON-LD aggregate rating is the only ratings surface. The site financing widget is not exposed through this API. The English mirror, new-build project pages, map tiles, and content/guide pages are deferred and not part of this version.

- **Documentation:** [https://scrappa.co/docs/ohne-makler-api/ohne_makler_search](https://scrappa.co/docs/ohne-makler-api/ohne_makler_search)
- **API group:** OhneMakler API
- **Endpoint:** `GET https://scrappa.co/api/ohne-makler/search`

## 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 |
| --- | --- | --- | --- |
| `q` | string | No | Free-text location: postal code (80331), city (München), Kreis, or Bundesland. Results are limited to the resolved place, returned as location. state/city segments are not accepted alongside q. |
| `type` | string | No | Property type: wohnung, haus, grundstueck, zimmer, buero, einzelhandel, gastronomie, lagerhalle, landwirtschaftliches-objekt, wohnen-auf-zeit, gewerbliche-freizeitimmobilie, zinshaus-rendite, sonstiges, immobilie. Required unless q is used. |
| `transaction` | string | No | kaufen (buy) or mieten (rent). Required unless q is used. |
| `state` | string | No | Bundesland URL slug, transliterated (e.g. hamburg, bayern, baden-wurttemberg). Omit for a Germany-wide search. |
| `city` | string | No | City or Kreis URL slug; requires state (e.g. hamburg/hamburg). |
| `page` | integer | No | Result page, 1-based. Pages beyond the last page return a non-billable 404. |
| `sort` | string | No | price_asc, price_desc, date_asc, or date_desc. |
| `price_min` | integer | No | Minimum price in EUR. |
| `price_max` | integer | No | Maximum price in EUR. |
| `area_min` | integer | No | Minimum living area in m². |
| `area_max` | integer | No | Maximum living area in m². |
| `radius` | integer | No | Umkreis in km: 0, 10, 25, 50, or 100. Applies when q resolves to a city or with state + city. Postal codes, Kreise, and states have no Umkreis on the site (422 radius_requires_city). |

## Example request

```bash
#!/bin/bash

curl -X GET \
    -H "x-api-key: YOUR_API_KEY_HERE" \
    "https://scrappa.co/api/ohne-makler/search?type=wohnung&transaction=mieten&state=hamburg&city=hamburg&page=1"
```

## Example response

```json
{
    "success": true,
    "mode": "category",
    "page": 1,
    "page_size": 24,
    "total_results": 92,
    "last_page": 4,
    "next_page": 2,
    "sort": null,
    "results": [
        {
            "id": 499372,
            "object_number": "OM-499372",
            "title": "Helle 2-Zimmer-Dachgeschosswohnung mit Balkon in Hamburg",
            "url": "https://www.ohne-makler.net/immobilie/499372/",
            "price": 1200,
            "price_on_request": false,
            "postal_code": "22179",
            "city": "Hamburg",
            "district": "Bramfeld",
            "street": null,
            "rooms": 2,
            "living_area_m2": 55,
            "plot_area_m2": null,
            "preview_image": "https://media.ohne-makler.net/...",
            "seller_type": null,
            "latitude": 53.60283,
            "longitude": 10.09564
        }
    ],
    "price_histogram": {
        "min": 0,
        "max": 2600,
        "buckets": [
            {
                "x": 500,
                "count": 3,
                "label": "400 - 500 \u20ac"
            }
        ]
    },
    "area_histogram": {
        "min": 0,
        "max": 650,
        "buckets": [
            {
                "x": 50,
                "count": 13,
                "label": "25 - 50 m\u00b2"
            }
        ]
    },
    "breadcrumbs": [
        {
            "name": "Immobilien",
            "url": "/immobilien/"
        }
    ],
    "search_form": {
        "location": {
            "model": "CITY",
            "id": 109316,
            "name": "Hamburg"
        }
    },
    "aggregate_rating": {
        "rating_value": "4.65",
        "best_rating": "5",
        "rating_count": 4993
    },
    "geojson": {
        "type": "FeatureCollection",
        "features": []
    }
}
```

## Errors

| Status | Error | Description |
| --- | --- | --- |
| 404 | End Of Pagination / Unknown Segment | The requested page is beyond the last page (`end_of_pagination`) or the state/city slug does not exist (`not_found`). Non-billable. |
| 422 | Validation Error | A query parameter failed validation. Non-billable. |
| 404 | Location Not Found | `q` matched no German postal code, city, Kreis, or state (`location_not_found`). Non-billable. |
| 422 | Radius Needs A City | `radius` was combined with a `q` that resolved to a postal code, Kreis, or state (`radius_requires_city`), or `q` is a listing object number (`object_number_query`). Non-billable. |
| 503 | Upstream Unavailable | The upstream request failed or could not be parsed after retries. `error.code` is one of `upstream_unavailable`, `parser_drift`, `upstream_blocked`, or `transport_error`. Retryable and never billed. Clients are never rate limited. |

## Frequently asked questions

### Does ohne-makler.net offer an official API?

No. ohne-makler.net has no public developer API. Scrappa provides a documented read-only API over its public listing surfaces and returns normalized JSON with API key authentication.

### What is the difference between structured and free-text search?

Structured search reads one category URL built from type, transaction, state, and city segments. Free-text search (q) first resolves the postal code or place name through the site's location autocomplete, then runs the platform's own search for exactly that place. Either way one request costs one credit and listing details are never fetched in the background.

### Are empty results charged?

A 200 response with zero listings is a valid answer and is billed. Failures — out-of-range pages, unknown slugs, validation errors, and upstream outages — never consume credits.

## Related endpoints

- [Listing Details](https://scrappa.co/docs/ohne-makler-api/ohne_makler_listing)
- [Location Autocomplete](https://scrappa.co/docs/ohne-makler-api/ohne_makler_locations)
- [Rent Index](https://scrappa.co/docs/ohne-makler-api/ohne_makler_mietspiegel)

## 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)
