Blog / API guides

Kleinanzeigen API: Search German Classifieds as JSON

To get Kleinanzeigen listings as JSON, call GET /v1/kleinanzeigen/search with a query and, optionally, location, category, price_min, price_max and page. Each ad comes back with its id, title, price, location, image and a description excerpt. A successful call costs 1 credit, a failed call costs nothing, and a daily check of one search is 30 credits a month.

Which Kleinanzeigen endpoints can you call?

The Kleinanzeigen API has three endpoints, all GET, all 1 credit per successful call. Search finds ads by keyword, Details returns one ad by its id, and Shipping Options takes no parameters at all. For monitoring, Search does nearly all the work, and you reach for Details only when a result is worth a closer look.

Method Path Returns Credits Required Optional
GET /v1/kleinanzeigen/search Search 1 query page, location, category, price_min, price_max
GET /v1/kleinanzeigen/details Details 1 ad_id none
GET /v1/kleinanzeigen/shipping-options Shipping Options 1 none none

The pages for Search, Details and Shipping Options each have a playground where you can run a real call with your key. Kleinanzeigen sits in the Marketplaces area of the catalogue, alongside sources like Vinted and Craigslist.

How do you authenticate and make the first request?

Send your key in the X-API-Key header. Keys start with sk_live_, and you get one by signing up and verifying an email address. There's no credit card, and the free tier gives you 1,000 credits every month. Where a header isn't possible, the api_key query parameter works too. The authentication docs cover both.

Here is a search for iPhones in Berlin, in the category elektronik, priced between 50 and 500:

curl "https://api.perceptlayer.com/v1/kleinanzeigen/search?query=iphone&page=1&location=Berlin&category=elektronik&price_min=50&price_max=500" \
  -H "X-API-Key: $API_KEY"

Every successful response carries an X-Credits-Used header (1 here) and X-Credits-Remaining, the balance after the call. Check them once while testing so you know what a run costs before you schedule it.

Which parameters does the search endpoint take?

Search takes one required parameter and five optional ones. query is a string of up to 500 characters. location and category are strings of up to 100 characters. price_min, price_max and page are integers. Nothing else about them is documented, so test unusual values in the playground before you build on them.

Parameter Type Required Notes
query string yes Up to 500 characters
page integer no Request page=2 and onward while has_next_page is true
location string no Up to 100 characters; Berlin in the example
category string no Up to 100 characters; elektronik in the example
price_min integer no Lower price bound
price_max integer no Upper price bound

Invalid input returns a 422 with the code invalid_parameters and costs nothing. See the errors docs for the error codes.

What does a search response look like?

A search returns success, a data array of ads and a meta object that echoes your filters and reports paging. This is a trimmed excerpt of a recorded response for the request above; the ad itself is cut to the fields you'd store.

{
  "success": true,
  "data": [
    {
      "id": "3547273212",
      "title": "Apple iPhone 13 Pro Max 256GB Silber/Weiß Zustand Sehr gut OVP",
      "url": "https://www.kleinanzeigen.de/s-anzeige/apple-iphone-13-pro-max-256gb-silber-weiss-zustand-sehr-gut-ovp/3547273212-173-3473",
      "price": "399 €",
      "price_numeric": 399,
      "location": "13187 Pankow",
      "description": "Hallo zusammen, ich verkaufe mein iPhone 13 Pro Max mit 256 GB in der Farbe Silber/Weiß. Das..."
    },
    …
  ],
  "meta": {
    "query": "iphone",
    "page": 1,
    "location": "Berlin",
    "category": "elektronik",
    "results_count": 27,
    "has_next_page": true,
    "duration_ms": 1327.64
  }
}

Three fields matter for monitoring. id identifies the ad across runs. price_numeric is a number, so you can sort and compare. has_next_page says whether another page exists. Note that id is a string here, while ad_id on the Details endpoint is an integer, so convert it with int() before you pass it on.

The title and description fields come back in German, as the sellers wrote them. The description is an excerpt that ends in an ellipsis, not the full ad text.

How do you monitor local prices with a script?

A price monitor needs three things: a fixed set of searches, a store of what you've already seen, and a rule for what counts as news. The script below runs one search, walks the pages, compares each ad with a local JSON file and prints new ads and price drops. It uses only the endpoint, parameters and fields shown above.

import json
import os
import time
from pathlib import Path

import requests

BASE = "https://api.perceptlayer.com/v1/kleinanzeigen/search"
HEADERS = {"X-API-Key": os.environ["API_KEY"]}
STATE = Path("seen.json")
PARAMS = {
    "query": "iphone",
    "location": "Berlin",
    "category": "elektronik",
    "price_min": 50,
    "price_max": 500,
}
MAX_PAGES = 2

seen = json.loads(STATE.read_text()) if STATE.exists() else {}

page = 1
while page <= MAX_PAGES:
    resp = requests.get(BASE, headers=HEADERS, params={**PARAMS, "page": page}, timeout=30)
    if resp.status_code in (429, 503):
        time.sleep(int(resp.headers.get("Retry-After", "5")))
        continue
    resp.raise_for_status()
    body = resp.json()

    for ad in body["data"]:
        price = ad.get("price_numeric")
        if price is None:
            continue
        old = seen.get(ad["id"])
        if old is None:
            print(f"NEW   {price:>5} EUR  {ad['title']}  {ad['url']}")
        elif price < old["price"]:
            print(f"DROP  {old['price']} -> {price} EUR  {ad['title']}  {ad['url']}")
        seen[ad["id"]] = {"price": price, "title": ad["title"]}

    if not body["meta"]["has_next_page"]:
        break
    page += 1

STATE.write_text(json.dumps(seen, ensure_ascii=False))

Run it once to build the baseline; every NEW line on that first run is just the current inventory. After that, only changes print. Because seen.json keeps every ad it has met, an ad that disappears from the results simply stops updating. If you want a closer look at a stored ad, the Details endpoint takes its id as ad_id.

The script uses the requests library and waits and retries on 429 and 503 using the Retry-After header, and it raises on other errors. Requests have no idempotency key, so a call that timed out on your side may still have been billed. For proper retry rules see handling web data API errors in Python.

How do you run it every day?

Cron is enough. A line like 0 7 * * * cd /path/to/monitor && python monitor.py runs the check at 07:00 daily. If you want queues or hosted workflows instead, scheduling web data pulls compares the options. To turn the printed lines into an agent that reasons about them, see building a price tracking agent in Python.

What does a daily check cost?

Each page you fetch is one call, and each call is 1 credit, so cost is searches × pages × days. Failed calls return X-Credits-Used: 0 and are never billed. The table works the arithmetic for a 30-day month; credits are the unit, and the pricing page shows what credits cost.

Setup Calls a day Calls × 1 credit, 30 days Credits a month
1 search, 1 page 1 30 × 1 30
1 search, 2 pages 2 60 × 1 60
5 searches, 2 pages each 10 300 × 1 300
20 searches, 3 pages each 60 1,800 × 1 1,800

The first three setups fit inside the 1,000 free credits a month. The fourth exceeds them by 800 credits, which the smallest prepaid pack, the 10,000-credit Starter, covers many times over. Purchased credits last 12 months, and there's no subscription. A Details call per new ad adds 1 credit each, so budget for it separately. The credits docs explain how debits work.

Speed isn't a constraint at this scale. Free accounts get 1 request a second, and a daily job makes a handful of calls. If you ever fan out many searches at once, read the rate limits first.

When is the official site a better fit?

If you only want to browse or watch a handful of searches yourself, the Kleinanzeigen site itself is the simpler tool, with no code to maintain. The API earns its place when you need the data in your own system: price history across weeks, joins with other sources, or an agent that acts on new ads.

Two limits are worth stating. Data is collected from publicly available sources and returned as found at the time of the call, so a listing can change or vanish between your check and a human's click. And Search gives you an excerpt of the description, not the full text. If you combine Kleinanzeigen with other marketplaces, deduplicating listings across sources covers matching ads that have no shared id.

Next step: get a free API key, then paste the cURL call above into your terminal and read X-Credits-Used in the response headers. The Kleinanzeigen Search page lets you try other filters in the playground first.

APIs in this post

Frequently asked questions

Is there an official Kleinanzeigen API?

Check Kleinanzeigen's own site for any official or partner interface; it isn't something we document here. The Perceptlayer Kleinanzeigen API is a separate route to the data and gives you three endpoints (Search, Details and Shipping Options) as JSON with one X-API-Key header. Each successful call costs 1 credit, and the free tier includes 1,000 credits every month with no credit card.

How much does a Kleinanzeigen search cost?

One search call costs 1 credit, and each extra page is another call. Checking one search over 2 pages daily is 60 calls × 1 credit = 60 credits in a month. Failed calls cost nothing and return X-Credits-Used: 0. Credits are prepaid, and the pricing page shows what a pack of credits costs.

How do I get the next page of results?

Read meta.has_next_page in the response. When it is true, repeat the same request with page increased by one. Each page is its own 1-credit call, so cap the number of pages in your script to keep a daily run's cost fixed, as the example does with MAX_PAGES.

What happens if a call fails or I hit the rate limit?

Errors return an HTTP status and a JSON body with code, message and request_id, and nothing is debited. A 429 rate_limited or 503 temporarily_unavailable response includes a Retry-After header with the seconds to wait. A 402 insufficient_credits means the prepaid balance is empty; there is no automatic top-up.

Can an AI agent search Kleinanzeigen?

Yes. The MCP server at https://api.perceptlayer.com/mcp exposes search_endpoints, get_endpoint and call_endpoint. An agent can look up the Search endpoint for free, then call it with a query; only the call itself spends 1 credit on success. The AI agents docs show the setup.

  1. Craigslist API: Get Classified Postings as JSON

    Search Craigslist postings by site, category and price as JSON, then poll for new ones. A real response, the parameters and the credit cost.

  2. Vinted API: Search Second-Hand Items as JSON

    Search Vinted as JSON with 14 optional parameters, then price-check one query across 19 countries. Each call costs 1 credit; failed calls cost nothing.

  3. Walmart Product Search API: Prices and Product Data

    Search Walmart by keyword and get price, rating, seller and stock as JSON with GET /v1/walmart/search. Parameters, a real response and a credit count.

Start with 1,000 free credits

No password to set. We email you a sign-in link, and your key works on every API.

No credit card. 1,000 free credits a month.