# Njuškalo + Bolha API — Croatia's and Slovenia's largest classifieds (cars, real estate, second-hand, jobs): keyword search, category browse with the site's own filters, category tree and full listing detail

> Keyword search across all of njuskalo.hr (site=hr) or bolha.com (site=si), with the source's own filters: category id, price range, condition, photos only, title-only matching, PayProtect (online payment) only, and 5 sort orders. Each row: listing id, title, URL, image, price (parsed + as printed), location, condition, the card's attributes (year, mileage, m², memory …), dates, promoted flag and the seller (id, name, private/business, rating). Plus the source's total, page count and the last page it actually serves.
> ReefAPI engine `njuskalo` · 4 endpoints · clean JSON, no scraping or browsers to manage.

## How to call
- **Endpoint:** `POST https://api.reefapi.com/njuskalo/v1/<action>` with a JSON body.
- **Auth:** header `x-api-key: <YOUR_REEFAPI_KEY>` — create one free (1,000 credits, no card): https://reefapi.com/signup
- **Response (every call):** `{ ok: boolean, data: ..., meta: { record_count, credits, ... }, error: { code, message } }` — branch on `ok`. Failed calls are free except verified SHEIN NOT_FOUND on product/detail and price (4 credits).
- **One key + one shared credit pool** across every ReefAPI API. Per-call credits are listed on each endpoint below.
- **Use it from an AI agent (MCP):** connect `https://api.reefapi.com/mcp` (remote streamable-http). Send the key as `Authorization: Bearer <key>`, or put it in the URL (`?key=<key>`) when the client has no header field, as ChatGPT does.

## Endpoints

### POST https://api.reefapi.com/njuskalo/v1/search — 2 credits
Keyword search across all of njuskalo.hr (site=hr) or bolha.com (site=si), with the source's own filters: category id, price range, condition, photos only, title-only matching, PayProtect (online payment) only, and 5 sort orders. Each row: listing id, title, URL, image, price (parsed + as printed), location, condition, the card's attributes (year, mileage, m², memory …), dates, promoted flag and the seller (id, name, private/business, rating). Plus the source's total, page count and the last page it actually serves.

**Parameters:**
- `query` (string, required) — Search words (Croatian / Slovenian, e.g. `bicikl`, `stolica za dijete`, `kolo`). Matched in title and description unless `title_only`.
- `site` (enum, optional, default "hr") — Which site: `hr` = njuskalo.hr (Croatia), `si` = bolha.com (Slovenia). Same platform, same fields. [one of: hr, si]
- `category_id` (integer, optional) — Numeric category id to narrow the search (e.g. 9580 = Prodaja stanova on njuskalo). Ids come from `categories` (`category_id`) or a listing's `category`. An id the site does not know is rejected (INVALID_PARAM).
- `min_price` (number, optional) — Minimum price in EUR.
- `max_price` (number, optional) — Maximum price in EUR.
- `condition` (enum, optional) — Item condition (one value). Listings with no condition set are in none of the three classes. [one of: new, used, defective]
- `with_images` (boolean, optional) — Only listings that have at least one photo.
- `title_only` (boolean, optional) — Match the words in the listing TITLE only (bicikl: 31,097 → 11,195).
- `payprotect_only` (boolean, optional) — Only listings that can be bought with the site's escrow payment (PayProtect on njuskalo, BREZ SKRBI on bolha).
- `sort` (enum, optional, default "relevance") — Result order (the site's own options). [one of: relevance, new, old, cheap, expensive]
- `page` (integer, optional, default 1) — Result page: 25 regular rows on most pages (36 measured on `mobiteli`) plus up to 6 promoted rows on page 1. The source serves at most 400 pages (10,000 rows) per query even when its own page count is higher; see `last_reachable_page`.

**Returns:** results[]{listing_id, title, url, category_slug, image, price (EUR; null when on request / hidden / 0), price_text (as printed), price_min, price_max, price_on_request, price_hidden, currency, location, condition, attributes[]{id,label,value} (year, mileage, m², memory …), summary_lines[]{caption,text,highlight}, created_at, is_promoted, payprotect, is_new_car, has_car_warranty, has_video, has_virtual_tour, has_ground_plan, is_luxury_real_estate, financing_text (cars: the dealer's monthly-instalment line), seller{seller_id, name, seller_type (private|business), rating, review_count, logo}} + total, pages, last_reachable_page, page, has_more, promoted_count, adapted_category_id

**Example request body:**
```json
{
  "query": "iphone",
  "site": "hr"
}
```

### POST https://api.reefapi.com/njuskalo/v1/browse — 3 credits
List one category page (e.g. `auti`, `auti/audi`, `prodaja-stanova/zagreb`, `mobiteli`) with the price range, seller type (private / business), condition, photos-only and sort — plus any of the category's OWN filters (car year, mileage, fuel, m², rooms …) through `filters`, using the parameter names the response lists in `available_filters`. Rows have the same shape as search. Also returns the category's sub-categories with counts.

**Parameters:**
- `category` (string, required) — Category path as in the site URL, without the domain: `auti`, `auti/audi`, `prodaja-stanova`, `prodaja-stanova/zagreb`, `mobiteli` (njuskalo); `avto-oglasi`, `nadomestni-deli` (bolha). Slugs come from `categories`. Landing pages without listings (`auto-moto`, `marketplace`) are refused — list their children with `categories`.
- `site` (enum, optional, default "hr") — Which site: `hr` = njuskalo.hr (Croatia), `si` = bolha.com (Slovenia). Same platform, same fields. [one of: hr, si]
- `min_price` (number, optional) — Minimum price in EUR.
- `max_price` (number, optional) — Maximum price in EUR.
- `seller_type` (enum, optional) — Private sellers or business sellers. The two add up exactly to the category total (auti: 17,740 + 36,035 = 53,775). [one of: private, business]
- `condition` (enum, optional) — Item condition (one value). Listings with no condition set are in none of the three classes. [one of: new, used, defective]
- `with_images` (boolean, optional) — Only listings that have at least one photo.
- `filters` (object, optional) — The category's own filters as `{parameter: value}`, named exactly as listed in `available_filters[].params` (e.g. on `auti`: `yearManufactured[min]`, `mileage[max]`, `fuelTypeId` = 602 (Diesel), `bodyTypeId`; on `prodaja-stanova`: `livingArea[min]`). A parameter the category does not have is rejected, never silently ignored.
- `sort` (enum, optional, default "new") — Result order (category pages default to newest). [one of: new, old, cheap, expensive]
- `page` (integer, optional, default 1) — Result page: 25 regular rows on most pages (36 measured on `mobiteli`) plus up to 6 promoted rows on page 1. The source serves at most 400 pages (10,000 rows) per query even when its own page count is higher; see `last_reachable_page`.

**Returns:** results[]{listing_id, title, url, category_slug, image, price (EUR; null when on request / hidden / 0), price_text (as printed), price_min, price_max, price_on_request, price_hidden, currency, location, condition, attributes[]{id,label,value} (year, mileage, m², memory …), summary_lines[]{caption,text,highlight}, created_at, is_promoted, payprotect, is_new_car, has_car_warranty, has_video, has_virtual_tour, has_ground_plan, is_luxury_real_estate, financing_text (cars: the dealer's monthly-instalment line), seller{seller_id, name, seller_type (private|business), rating, review_count, logo}} + total, pages, last_reachable_page, page, has_more, page_clamped, category{category_id, name, url}, subcategories[]{category_id, name, slug, url, listing_count}, available_filters[]{filter, label, type, params[], choices[]{value,label,param}}

**Example request body:**
```json
{
  "category": "auti",
  "site": "hr",
  "seller_type": "private"
}
```

### POST https://api.reefapi.com/njuskalo/v1/categories — 2 credits
The category tree, one level at a time: without `category` the site's top-level sections and their main categories; with a category path its child categories (brands, models, regions, sub-types) with the live listing count of each. Feed a `slug` to `browse`, a `category_id` to `search`.

**Parameters:**
- `site` (enum, optional, default "hr") — Which site: `hr` = njuskalo.hr (Croatia), `si` = bolha.com (Slovenia). Same platform, same fields. [one of: hr, si]
- `category` (string, optional) — Category path whose children to list (`auti`, `prodaja-stanova`, `avto-moto`). Omit for the top level.

**Returns:** categories[]{category_id, name, slug, url, listing_count?, children[]?} + category (the requested one, when given)

**Example request body:**
```json
{
  "category": "auti",
  "site": "hr"
}
```

### POST https://api.reefapi.com/njuskalo/v1/listing — 2 credits
Full listing by listing_id or URL: title, price (parsed + printed, range for new-build projects, on-request flag), state, created / expiry dates, view count, category path, location + map coordinates (approximate flag), every detail field the listing shows (car: make, model, year, mileage, power, VIN, owners …; real estate: m², rooms, floor, energy class …), equipment groups, full description, all photos, PayProtect fees, and the seller (id, username, display name, private / business / store, profile URL, region / town, website, phone-verified flag, rating).

**Parameters:**
- `listing_id` (string, optional) — Listing id: the digits after `-oglas-` in a listing URL, returned as `listing_id` in every search/browse row. Provide listing_id OR url.
- `url` (string, optional) — A njuskalo.hr or bolha.com listing URL; alternative to listing_id (the site is taken from the URL).
- `site` (enum, optional, default "hr") — Which site: `hr` = njuskalo.hr (Croatia), `si` = bolha.com (Slovenia). Same platform, same fields. [one of: hr, si]

**Returns:** listing{listing_id, site, title, headline, url, state, listing_type, created_at, expires_at, view_count, price, price_text, price_min, price_max, price_on_request, currency, price_structured_data, price_mismatch, availability, item_condition_schema, category{}, category_ids{}, category_path[], location, coordinates{lat,lng,approximate}, highlights[], details[]{field,label,value}, additional_details[]{group, items[]|values[]}, description, images[], video_count, has_ground_plan, payprotect{enabled, lowest_delivery_fee_text, buyer_protection_fee_text, total_with_min_delivery_text}, is_paid_promotion, new_build_project{title,url}?, seller{seller_id, username, name, seller_type (private|business|store), profile_url, logo, phone_verified, country, region, town, zip_code, street, website, email, additional_contact, rating, review_count, is_active}}

**Example request body:**
```json
{
  "url": "https://www.njuskalo.hr/iphone-16/iphone-oglas-51742531"
}
```

## At scale
- **Volume:** 5M+ requests a day, measured at 60 requests a second across the fleet with no
  central bottleneck. Per-key limits are raised for high-volume accounts; volume pricing on request.
- **Missing a source:** tell us a site we do not cover and it becomes an engine. A customer asked
  for bestprice.gr on 21 Sep 2026 and it was in the catalog on 22 Sep.
- **Support:** 2 minute median time from a question in the live chat to the first answer. Setup
  help included, no support tier to buy.
- **One key, one credit pool** across every API. No per-site plans, no separate subscriptions.

## More
- Try it live, no code: https://reefapi.com/playground?engine=njuskalo
- Human docs page: https://reefapi.com/docs/njuskalo
- Overview page: https://reefapi.com/njuskalo-api
- Every ReefAPI API in one file (for your AI): https://reefapi.com/llms-full.txt
