KupujemProdajem API & Scraper
The KupujemProdajem API returns Serbia's largest general classifieds site as clean JSON, in four actions: search, listing, count and categories.
🤖 Using an AI assistant? Copy this link into ChatGPT / Claude / Cursor — it reads every endpoint and parameter instantly and tells you if this API fits your use case.
KupujemProdajem is where Serbia actually buys and sells: on 2026-10-01 its live catalogue held 127,585 white-goods and home-appliance ads, 76,629 under sport and leisure, 64,090 in womenswear, 53,103 records and CDs, 49,267 flats and houses for sale, 48,904 furniture ads, 39,959 women's shoes, 35,174 mobile phones, 14,337 bicycles, 11,129 cars, 11,033 magazines and comics, 10,289 properties to rent, 3,116 commercial vehicles, 505 job posts and 365 ads in a single one of its 22 service categories. search narrows that by keywords, category, sub-group, town, seller, a price band in EUR or RSD, item condition, whether the ad is an offer or a wanted ad, and whether it has a price, has a photo, accepts a swap or is available immediately; it sorts by newest, cheapest or most expensive and returns up to 500 rows per page. Every row carries the ad id, the public ad URL, the title, the description snippet, the price with the currency the seller chose, the category and sub-group, the town, the condition, whether it is an offer or a wanted ad, whether it is goods, a service or a job, when it was posted and last renewed, its view and favourite counts, the seller id, whether it is a paid placement, and a photo. listing adds the full description, every photo at full size and as a thumbnail, the ad status and expiry date, courier-delivery and local-pickup flags, ISBN, OEM code, VIN, mileage and registration status where the category has them, and the seller's public profile: display name, person or company, town, member-since date, positive and negative review counts, phone-verified and bank-account-verified badges and company tax and registry ids. count answers how many live ads match a filter set in one tiny call, and it is the uncapped number. categories returns all 89 live categories with the flags that tell you whether the condition and swap filters mean anything inside each one. Verified on 2026-10-01: 22 of 22 live checks behaved as expected on two separate runs, all 13 exposed filters measurably narrowed the same-run control, all 10 main categories returned rows and resolved their first ad to a full detail record, and on 15 ads the price and currency matched the figure KupujemProdajem prints on its own page 15 of 15, with zero mismatches. No KupujemProdajem account — one ReefAPI key and the standard { ok, data, meta, error } envelope.
What is actually on the site — live ad counts on 2026-10-01
These are counts read off the live index, one call each, not estimates. They move as ads are posted and expire; the count action returns today's number the same way, and every search response carries the matching total in the response body.
| category | category id | live ads | price published? |
|---|---|---|---|
| Bela tehnika i kućni aparati (white goods, home appliances) | 15 | 127,585 | yes, EUR or RSD |
| Sport i razonoda (sport and leisure) | 34 | 76,629 | yes, EUR or RSD |
| Odeća | Ženska (womenswear) | 743 | 64,090 | yes, EUR or RSD |
| Audio | Vinili, CD i kasete (records, CDs, tapes) | 1176 | 53,103 | yes, EUR or RSD |
| Nekretnine | Prodaja (property for sale) | 2821 | 49,267 | yes, usually EUR |
| Nameštaj (furniture) | 1268 | 48,904 | yes, EUR or RSD |
| Obuća | Ženska (women's shoes) | 1077 | 39,959 | yes, EUR or RSD |
| Mobilni telefoni (mobile phones) | 23 | 35,174 | yes, EUR or RSD |
| Bicikli (bicycles) | 912 | 14,337 | yes, EUR or RSD |
| Automobili (cars) | 2919 | 11,129 | yes, usually EUR |
| Časopisi i stripovi (magazines, comics) | 1296 | 11,033 | yes, mostly RSD |
| Nekretnine | Izdavanje (property to rent) | 2850 | 10,289 | yes, monthly rent |
| Transportna vozila (commercial vehicles) | 2329 | 3,116 | yes, EUR or RSD |
| Poslovi (jobs) | 2546 | 505 | no — a job ad has no price |
| Usluge | Auto-moto (one of 22 service categories) | 1410 | 365 | yes, usually RSD |
That is 15 of the 89 live categories; the categories action lists all of them (66 goods, 22 services, 1 jobs) with their ids. Keyword searches are much larger than any single category: audi 309,442 live ads, iphone 84,726, stan (flat) 72,115, bicikl 41,714, ps5 9,754 on the same day. 🔴 A keyword search can only be paged to 20,000 rows: iphone reports 20,000 over 667 pages and page 668 is empty, while count says 84,726 for the same words — so the API returns both figures and flags which one is capped. A category search has no such ceiling: category 743 reported 64,085 rows over 2,137 pages and page 2,137 served its last 7 rows.
Real request and response JSON
Captured from the indexed primary action, search, on .
{
"method": "POST",
"url": "https://api.reefapi.com/kupujemprodajem/v1/search",
"headers": {
"x-api-key": "$REEF_KEY",
"content-type": "application/json"
},
"body": {
"query": "iphone",
"max_results": 20
}
}{
"ok": true,
"meta": {
"api": "kupujemprodajem",
"endpoint": "search",
"mode": "live",
"latency_ms": 3660.4,
"record_count": 30,
"bytes": 61399,
"cache_hit": false,
"upstream_requests": 2,
"source_url": "https://www.kupujemprodajem.com/api/web/v1/search?keywords=iphone&page=1&perPage=30",
"charged_credits": 1,
"version": "1.0.0",
"request_id": "1a680e9465754bff",
"queue_ms": 1.5
},
"data": {
"total_results": 20000,
"total_capped": true,
"total_available": 84936,
"page": 1,
"page_count": 667,
"page_size": 30,
"returned": 30,
"page_clamped": false,
"has_more": true,
"dropped_non_listing_tiles": 0,
"filter_id": 9047815648,
"listings": [
{
"listing_id": "152902916",
"url": "https://www.kupujemprodajem.com/mobilni-telefoni/apple-iphone/iphone-18-pro-18-pro-max-17-pro-max-17-16-plus-15/oglas/152902916",
"title": "iPhone 18 PRO/ 18 PRO MAX/ 17 PRO MAX/ 17/ 16 PLUS/ 15/",
"description_excerpt": "INFORMACIJE: POZIV i PORUKA (WA, Viber, Telegram, SMS) iPHONE ...",
"category_id": 23,
"category_name": "Mobilni telefoni",
"group_id": 489,
"group_name": "Apple iPhone",
"location_id": 1,
"location_name": "Beograd",
"condition": "as-new",
"listing_type": "sell",
"listing_kind": "goods",
"posted_at": "2026-10-01 16:44:23",
"renewed_at": "2026-10-01 16:44:23",
"view_count": 501971,
"favorite_count": 1357,
"seller_id": 1396317,
"promoted": true,
"highlighted": false,
"immediate_available": true,
"has_video": false,
"is_vehicle": false,
"image": "https://images.kupujemprodajem.com/photos/oglasi/6/91/152902916/152902916_6ab25298f19a28-98318075image.webp",
"image_thumbnail": "https://images.kupujemprodajem.com/photos/oglasi/6/91/152902916/tmb-300x300-152902916_6ab25298f19a28-98318075image.webp",
"attributes": [],
"price": 1460,
"price_currency": "EUR",
"price_kind": "negotiable",
"price_display": null,
"price_label": null,
"price_suffix": null,
"exchange_accepted": false
},
{
"listing_id": "137465119",
"url": "https://www.kupujemprodajem.com/mobilni-telefoni/apple-iphone/iphone-18-17-pro-max-16-15-13-512gb-256gb-plus-17e-16-air/oglas/137465119",
"title": "Iphone 18/17/Pro/Max/16/15/13/512gb/256gb/Plus/17e/16+/Air",
"description_excerpt": ". (EN) New Original Apple iPhone Unpacked -- Vacuum ...",
"category_id": 23,
"category_name": "Mobilni telefoni",
"group_id": 489,
"group_name": "Apple iPhone",
"location_id": 16,
"location_name": "Novi Sad",
"condition": "as-new",
"listing_type": "sell",
"listing_kind": "goods",
"posted_at": "2026-10-01 09:57:24",
"renewed_at": "2026-10-01 09:57:24",
"view_count": 477120,
"favorite_count": 1950,
"seller_id": 929958,
"promoted": true,
"highlighted": false,
"immediate_available": true,
"has_video": false,
"is_vehicle": false,
"image": "https://images.kupujemprodajem.com/photos/oglasi/9/11/137465119/137465119_6a156c27a21089-25392871image.webp",
"image_thumbnail": "https://images.kupujemprodajem.com/photos/oglasi/9/11/137465119/tmb-300x300-137465119_6a156c27a21089-25392871image.webp",
"attributes": [],
"price": 999,
"price_currency": "EUR",
"price_kind": "fixed",
"price_display": null,
"price_label": null,
"price_suffix": null,
"exchange_accepted": false
},
{
"listing_id": "137463316",
"url": "https://www.kupujemprodajem.com/mobilni-telefoni/ostalo/otkup-vakum-telefona-ns-samo-novo-iphone-samsung-novi-sad/oglas/137463316",
"title": "Otkup VAKUM Telefona NS samo NOVO Iphone Samsung Novi Sad",
"description_excerpt": "100% Brzo i Sigurno! ------------... NUŽNO! OBOSTRANO ZADOVOLJSTVO! MAKSIMALNO ...",
"category_id": 23,
"category_name": "Mobilni telefoni",
"group_id": 111,
"group_name": "Ostalo",
"location_id": 16,
"location_name": "Novi Sad",
"condition": "as-new",
"listing_type": "buy",
"listing_kind": "goods",
"posted_at": "2026-09-28 08:56:07",
"renewed_at": "2026-09-28 08:56:07",
"view_count": 32247,
"favorite_count": 98,
"seller_id": 929958,
"promoted": true,
"highlighted": false,
"immediate_available": false,
"has_video": false,
"is_vehicle": false,
"image": "https://images.kupujemprodajem.com/photos/oglasi/6/31/137463316/137463316_6986fcb278c433-22464378image.webp",
"image_thumbnail": "https://images.kupujemprodajem.com/photos/oglasi/6/31/137463316/tmb-300x300-137463316_6986fcb278c433-22464378image.webp",
"attributes": [],
"price": null,
"price_currency": "EUR",
"price_kind": "not_priced_wanted",
"price_display": null,
"price_label": null,
"price_suffix": null,
"exchange_accepted": false
}
],
"query": "iphone",
"sort": "default",
"filters": {
"keywords": "iphone"
}
}
}What the KupujemProdajem API does
| Action | Description | Concrete use case | Key params |
|---|---|---|---|
| search | Search KupujemProdajem classifieds. Needs `query` (keywords) OR `category` (a numeric id from the `categories` action) — either alone is enough, and the rest of the filters narrow it: sub-group, town, seller, price band with currency, condition, offer direction, has-price, has-photo, accepts-swap, available-immediately. Returns the source's own total plus, when that total is capped at 20 000, its uncapped count in `total_available`. | Price-intelligence teams call search to search KupujemProdajem classifieds. | query, category, group, location, seller_id, ... |
| listing | Full detail of one ad by id or URL: the complete description, every photo at full size and as a thumbnail, price with the source's own printed string, condition, location, posting and renewal time, view and favourite counts, delivery and local-pickup flags, vehicle/ISBN/OEM fields where the category has them, and the seller's public profile (display name, person or company, town, member-since, review counts, phone-verified and bank-account-verified badges, company tax and registry ids). A removed or non-existent ad returns NOT_FOUND. The seller's phone number is never requested and never returned — only the source's own `has_phone`. | Classifieds aggregators call listing to get full detail of one ad by id or URL. | listing_id, url |
| count | How many live ads match a filter set, without downloading any of them. One ~60-byte upstream call, and it is the source's UNCAPPED number: the same query whose `search` total stops at 20 000 counts 84 718 here. Takes exactly the same filters as `search` (page and sort are ignored). | Resale and arbitrage tools call count to get how many live ads match a filter set, without downloading any of them. | query, category, group, location, seller_id, ... |
| categories | The site's live category table — the resolver `search` needs, because `category` takes a numeric id. 89 categories across three kinds (66 goods, 22 services, 1 jobs), each with the flags that tell you whether the condition and swap filters mean anything in it. | Lead-generation teams call categories to get the site's live category table. | kind |
Call search from your stack
curl -X POST https://api.reefapi.com/kupujemprodajem/v1/search \
-H "x-api-key: $REEF_KEY" \
-H "content-type: application/json" \
-d '{"query":"iphone","max_results":20}'import requests
r = requests.post(
"https://api.reefapi.com/kupujemprodajem/v1/search",
headers={"x-api-key": REEF_KEY},
json={
"query": "iphone",
"max_results": 20
},
)
print(r.json()["data"])const res = await fetch("https://api.reefapi.com/kupujemprodajem/v1/search", {
method: "POST",
headers: {
"x-api-key": process.env.REEF_KEY,
"content-type": "application/json",
},
body: JSON.stringify({
"query": "iphone",
"max_results": 20
}),
});
const { ok, data, meta, error } = await res.json();Ask your MCP-connected assistant: call reefapi.kupujemprodajem.search with {"query":"iphone","max_results":20}.Who uses this API and why
- Track Serbian residential property supply and asking prices: 49,267 flats and houses for sale and 10,289 to rent on 2026-10-01, filterable by town and price band, each with the full description, every photo and the lister's review history.
- Price used stock for a Balkan e-commerce or repair business: pull the same model across 35,174 phone ads or 127,585 appliance ads, keep each seller's own currency, and use has_price and condition to compare like with like.
- Find demand instead of supply: 2,487 of a 4,595-ad control were people asking to buy. Filter listing_type=buy by category and town to see what the market is short of before you stock it.
- Monitor a competitor or a shop account: pass their seller_id to search for every ad they have live, and read their public review counts and verification badges from any one of their ads.
- Build a saved-search alert service: sort by newest, page the first results on a schedule, and use count as a cheap heartbeat that tells you the matching total moved before you pay for a full page.
Questions developers ask before integrating
Which currency are the prices in?
The one the seller chose, per ad — KupujemProdajem lets a seller quote in EUR or in RSD, and both appear in the same result set. We measured furniture rows coming back as 7,899 RSD and 150 EUR side by side. So every row carries price together with price_currency, and the listing action also returns price_display, which is the exact string KupujemProdajem prints on the ad page ("8.500 €", "2.500 din"), so you can check our number against the source's own rendering. On 15 ads sampled across ten categories the two agreed 15 of 15, with zero mismatches. We do not convert anything: a converted price is our arithmetic, not the seller's offer.
Why is price null on some ads?
Because those ads have no price, and a zero would be a lie. Across 300 sampled rows in ten categories, 227 carried a price and 73 did not: job posts, wanted ads where the poster is buying rather than selling, and ads where the seller asks you to enquire. KupujemProdajem writes a 0 into those, so the API returns null and puts the reason in price_kind — not_priced_job, not_priced_wanted or not_priced — alongside the site's own label ("Posao" for a job, "Kupujem" for a wanted ad). If you only want ads with a real number, set has_price: it narrowed a 4,595-ad control to 1,989.
Does a filter actually do anything, or does the site just ignore it?
Every filter we expose was measured against the same-run unfiltered control, and only the ones that moved the total are offered. Against a control of 4,595 ads (keywords telefon, category 23): location=1 (Belgrade) 2,842, a 100-200 EUR band 137, condition=new 481, condition=as-new 1,909, condition=used 1,956, condition=damaged 47, listing_type=sell 2,108, listing_type=buy 2,487, has_price 1,989, has_photo 4,565, exchange_only 128, immediate_available 1,249, group=489 (Apple iPhone) 721. Thirteen for thirteen. Two filters the site accepts but does not usefully apply are deliberately not exposed, because a handle that does nothing is worse than no handle. And an unknown condition or sort value is rejected with INVALID_PARAM rather than passed through, because the site would answer a typo with a convincing empty page.
How many ads can I actually page through?
All of them in a category search, and 20,000 in a keyword search. Measured both ways: category 743 reported 64,085 matching ads over 2,137 pages and page 2,137 really did serve the last 7 rows, with the site's own counter agreeing to the row. The keyword search keywords=iphone reported 20,000 over 667 pages, page 667 returned 19 rows and page 668 returned none — exactly 20,000 — while count for the same words said 84,726. So search returns total_results, total_capped and total_available, and you can see at a glance whether you are looking at the whole set. If you need past 20,000 on a keyword, add a category, a town or a price band and the ceiling stops mattering.
How many rows per page, and is it fixed?
Thirty by default, and it is a real parameter, not a fixed page size: we measured 30, 60, 100, 120, 200 and 500 rows per page all honoured exactly, at roughly 59 KB, 118 KB, 193 KB, 231 KB, 384 KB and 956 KB of response. One search credit covers whichever you pick, so a bulk pull is cheaper at 200 or 500 and a UI feed is leaner at 30.
What do I get about the seller?
What the ad page itself shows, unmodified: the display name, whether the account is a person or a company, their town, the date they joined, their positive and negative review counts, whether their phone and their bank account are verified, their stated reply habit, their profile note, and — for company accounts — the Serbian tax (PIB) and registry (MBR) ids. On one sampled seller that was 1,326 reviews, 1,326 positive, 0 negative, a member since 2017-06-03, phone and bank account both verified. The seller block was present on 10 of 10 details we sampled. You can also pull every ad one seller has: pass their seller_id to search.
Do I get the seller's phone number?
No. KupujemProdajem keeps the phone behind a separate click on its own site, and this API never asks for it and never returns it. What you do get is the site's own has_phone flag, so you know whether a phone exists on the ad without us going after it.
What does listing add over a search row?
The full description instead of the snippet — 221 to 3,635 characters on the ads we sampled, with the site's bold tags and HTML entities already cleaned out — plus every photo at full size and as a 300x300 thumbnail (1 to 15 per ad in our sample), the ad status and its expiry date, courier-delivery and local-pickup availability, the paid-placement type, and the category-specific fields where they exist: ISBN for books, OEM code for parts, VIN, mileage and registration status for vehicles. Everything in the search row is in there too, and an ad id taken from search resolved to the same record 15 of 15 times.
Why is condition empty on some ads?
Because the site only offers a condition where it makes sense. Property, jobs and services have no condition field at all, so it came back on 180 of 300 sampled rows and we return null rather than inventing "used". You do not have to guess which categories have it: the categories action returns a shows_condition flag (and a shows_exchange flag) per category, so you can tell before you filter.
Are category-specific attributes included?
Where the site publishes them, and we are honest about how often that is: the attributes array was filled on 4 of the 10 details we sampled — vehicles and property — and empty on most goods ads, where the detail lives in the free-text description instead. We return the empty array as an empty array. Mileage, VIN and registration status for vehicles come back as their own named fields regardless.
Can I search for people who are BUYING, not selling?
Yes, and on this site that is a real market rather than a rounding error: in a 4,595-ad control, 2,487 were wanted ads and 2,108 were offers. Set listing_type to buy to get only the wanted ads, sell for only the offers, or leave it off for both. Wanted ads carry no price by definition, and the API labels them not_priced_wanted instead of showing a zero.
What happens if I ask for an ad that has been removed?
You get NOT_FOUND with a message saying the ad is removed or never existed, never a blank success you have to interpret. A search that genuinely matches nothing is a different thing and is treated as one: ok with zero rows and total_results 0, because an empty answer is still an answer. We measured both — a dead id and a nonsense keyword — on two separate runs.
How do I find the right category or town id?
The categories action returns all 89 category ids with their names and kinds, so category needs no guessing. Town and sub-group are different, and we say so rather than implying a catalogue exists: KupujemProdajem publishes no public list of either, so location_id and group_id come back on every search row next to their names, and you take the id from there. Both filters are measured working — Belgrade narrowed a 4,595-ad control to 2,842, and the Apple iPhone sub-group to 721.
What is the KupujemProdajem API?
KupujemProdajem API is a ReefAPI endpoint group for serbia's biggest classifieds site as json: cars, flats for sale and to rent, phones, furniture, clothing, services and jobs, with the seller's own currency, review history and verification badges. It returns live JSON through POST requests under /kupujemprodajem/v1.