# ikman.lk API scraper — search Sri Lanka's largest classifieds marketplace (353 000+ live ads measured 2026-10-02) across all 299 categories: cars, vans, motorbikes and three-wheelers, land and houses for sale and to rent, mobile phones, electronics, jobs, services, animals, fashion and farm equipment. Filter by keyword, category, any of the 27 districts or 304 cities, and offer direction (for sale / wanted / for rent / wanted to rent), with the source's own two sorts and its own facet counts per category, city and type returned alongside the rows. Full ad detail returns the complete description, every photo, the category's own attribute list, the view count, the job salary band and application deadline where the ad is a vacancy, and the advertiser's public contact card and dealer page. Prices come back parsed with the currency read off the source's own printed string (LKR), and an ad with no price — a wanted ad — returns null instead of a fake zero. No account, logged-out public data only.

> Search ikman.lk classifieds. Needs at least one of `query` (keywords), `category` (a numeric id from the `categories` action) or `location` (a numeric id from the `locations` action); any combination also works, and `listing_type`, `sort`, `order` and `page` narrow or reorder it. Every response also carries `catalogue_counts` — the source's live totals per category, city and offer type. 🔴 They follow the id filters (`category`, `location`, `listing_type`) and IGNORE `query`: measured, `query=toyota` (10 465 matches) still reported 89 274 for category 391, while `category=392` reported 6 732 for Colombo, which is the real Cars-in-Colombo figure. So they size a category or district, they do not split your keyword result. 🔴 Page size is fixed by the source at 25 and no override is accepted; a full page measured 25 or 26 rows because the source adds a paid insert at the top. The source reports more pages than it serves, so `reachable_results` caps at 10 000 per query however large `total_results` is.
> ReefAPI engine `ikman` · 4 endpoints · clean JSON, no scraping or browsers to manage.

## How to call
- **Endpoint:** `POST https://api.reefapi.com/ikman/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/ikman/v1/search — 2 credits
Search ikman.lk classifieds. Needs at least one of `query` (keywords), `category` (a numeric id from the `categories` action) or `location` (a numeric id from the `locations` action); any combination also works, and `listing_type`, `sort`, `order` and `page` narrow or reorder it. Every response also carries `catalogue_counts` — the source's live totals per category, city and offer type. 🔴 They follow the id filters (`category`, `location`, `listing_type`) and IGNORE `query`: measured, `query=toyota` (10 465 matches) still reported 89 274 for category 391, while `category=392` reported 6 732 for Colombo, which is the real Cars-in-Colombo figure. So they size a category or district, they do not split your keyword result. 🔴 Page size is fixed by the source at 25 and no override is accepted; a full page measured 25 or 26 rows because the source adds a paid insert at the top. The source reports more pages than it serves, so `reachable_results` caps at 10 000 per query however large `total_results` is.

**Parameters:**
- `query` (string, optional) — Free-text keywords, exactly as typed into the site's own search box. Measured bite: 353 089 live ads nationwide → 10 465 for `toyota`. Give this, `category` or `location` — any one is enough, and they combine.
- `category` (integer, optional) — Numeric category id. Top level: 391 Vehicles, 409 Property, 428 Electronics, 2000 Mobiles, 700 Jobs, 537 Services, 444 Home & Garden, 489 Animals, 470 Fashion & Beauty, 503 Hobby/Sport/Kids, 535 Business & Industry, 587 Agriculture, 568 Education, 1100 Work Overseas. Leaf examples: 392 Cars, 402 Motorbikes, 415 Houses For Sale, 2003 Mobile Phones. The `categories` action lists all 299 with their parents. Measured bite: 353 089 → 89 276 for 391 (the source's own facet said 89 277 in the same second).
- `location` (integer, optional) — Numeric location id — a district or any city inside it. Districts: 1506 Colombo, 1577 Gampaha, 1636 Kandy, 1620 Kalutara, 1559 Galle, 1605 Jaffna, 1674 Kurunegala. The `locations` action lists all 331 (27 districts + their cities). Measured bite: 353 089 → 207 303 for Colombo district.
- `listing_type` (enum, optional) — Offer direction, using the source's own keys. Omit for all four. Measured against a 353 089-row control, and every number below matched the source's own facet count to the row. [one of: for_sale, to_buy, for_rent, to_rent]
- `sort` (enum, optional) — Field to sort on — the source publishes exactly these two. Omit for the source's own mixed relevance order. 🔴 The source inserts one PAID ad at the top of each page and that insert ignores the sort (measured: `sort=price&order=asc` on Cars put a Rs 19 500 000 ad above the cheapest rows). Rows carry `promoted: true` so you can see it, and `exclude_promoted` drops them. A seller's own `bump_up` is NOT counted as promotion — it only re-dates the ad and is reported separately as `bumped_at`. [one of: date, price]
- `order` (enum, optional, default "desc") — Sort direction. Only meaningful with `sort`; passing it alone is rejected rather than silently ignored. With `sort=price` the ads that carry NO price sort first on `asc`. [one of: desc, asc]
- `page` (integer, optional, default 1) — 1-based page number, 1-400. 🔴 The source REPORTS more pages than it serves: a query reporting 14 124 pages answers HTTP 500 from page 401 on (measured on three different result sets). So at most 10 000 ads are reachable per query however large `total_results` is — narrow with category, location or keywords instead of paging deeper. A page above the ceiling is rejected here instead of becoming a 500.
- `exclude_promoted` (boolean, optional, default false) — Drop the rows the source has sold placement for (`top_ad`, `featured_ad`, `spotlight`, `urgent_ad`). A full page measured 25 or 26 rows because the source adds one paid insert at the top, outside the sort you asked for. With this on you get the organic rows only, and `promoted_rows` still tells you how many were removed.

**Returns:** total_results (the source's figure), reachable_results (what you can actually page to), reported_page_count, reachable_page_count, page_ceiling, total_truncated_by_ceiling, page, page_size, returned, promoted_rows, leading_promoted_insert, dropped_non_matching_promoted (paid rows the source attached to a zero-match answer), dropped_non_listing_tiles, has_more, sort_option, sort_order, query, filters (the query string actually sent), catalogue_counts {categories[], cities[], listing_types[] — the source's live total for each, following the id filters and ignoring `query`} and listings[] with listing_id, slug, url, title, listing_type (for_sale | to_buy | for_rent | to_rent), status, condition (the source's own token, e.g. used | new | reconditioned | import — only categories that define it), posted_at, expires_at, bumped_at, category_id, category_name, district_id, district, city_id, city, highlights[] (the source's own SERP summary chips), properties[] (label/value/key/value_key — the category's own attributes; measured filled on 11 of 12 ads in `listing` but only 18 of 312 search rows, because the source attaches most of them to the ad page), promoted, promotions{} (the source's own five flags, raw), bumped_at, buy_now, price, price_min, price_max, price_currency (read off the source's own token), price_currency_raw, price_display (the source's own string), price_label, price_kind (fixed | range | not_priced_wanted | not_priced | unparsed), price_display_source_echo and price_display_matches_info (the source's second printing of the same amount and whether it agrees), image_count, image_ids[], images[] (780x585), image_thumbnails[] (158x88), image, image_thumbnail, image_base_uri, image_url_template, seller{account_id, name, account_type, phone_numbers[], email, email_verified, chat_enabled, contact_opt_out, delivery_methods[], membership_level, paying_member, authorized_dealer, featured_member} and shop{} (null unless the ad belongs to a dealer page).

**Example request body:**
```json
{
  "query": "car",
  "max_results": 20
}
```

### POST https://api.reefapi.com/ikman/v1/listing — 1 credit
Full detail of one ad by id, slug or URL: the complete description as the advertiser wrote it, every photo at full size and as a thumbnail, the price with the source's own printed string, the category's own attribute list (year, mileage, bedrooms, employer, job type…), the ad's view count, posting and expiry time, the advertiser's public contact card, the dealer page when the ad belongs to one, the job salary band and application deadline when the ad is a vacancy, and the ids of the ads the source itself calls similar. A removed or non-existent ad returns NOT_FOUND.

**Parameters:**
- `listing_id` (string, optional) — The ad's 24-character id OR its slug — `search` returns both and the source resolves either to the same record (verified byte-for-byte on the same ad). Give this or `url`.
- `url` (string, optional) — Full ad URL exactly as `search` returns it; the slug is taken from its `/ad/<slug>` tail.

**Returns:** the same row shape as `search` plus description (full text), view_count, job{application_deadline, apply_phone_numbers[], apply_via_dashboard, apply_email} (null when the ad is not a vacancy), similar_listing_ids[] and safety_notice. Row shape: listing_id, slug, url, title, listing_type (for_sale | to_buy | for_rent | to_rent), status, condition (the source's own token, e.g. used | new | reconditioned | import — only categories that define it), posted_at, expires_at, bumped_at, category_id, category_name, district_id, district, city_id, city, highlights[] (the source's own SERP summary chips), properties[] (label/value/key/value_key — the category's own attributes; measured filled on 11 of 12 ads in `listing` but only 18 of 312 search rows, because the source attaches most of them to the ad page), promoted, promotions{} (the source's own five flags, raw), bumped_at, buy_now, price, price_min, price_max, price_currency (read off the source's own token), price_currency_raw, price_display (the source's own string), price_label, price_kind (fixed | range | not_priced_wanted | not_priced | unparsed), price_display_source_echo and price_display_matches_info (the source's second printing of the same amount and whether it agrees), image_count, image_ids[], images[] (780x585), image_thumbnails[] (158x88), image, image_thumbnail, image_base_uri, image_url_template, seller{account_id, name, account_type, phone_numbers[], email, email_verified, chat_enabled, contact_opt_out, delivery_methods[], membership_level, paying_member, authorized_dealer, featured_member} and shop{} (null unless the ad belongs to a dealer page).

**Example request body:**
```json
{
  "url": "https://ikman.lk/en/ad/toyota-allion-2010-for-sale-gampaha-281"
}
```

### POST https://api.reefapi.com/ikman/v1/categories — 1 credit
The site's live category table — the resolver `search` needs, because `category` takes a numeric id. 299 categories in a two-level tree under 17 top-level sections, each with its parent, its children, the offer directions it accepts and its public URL.

**Parameters:**
- `parent` (integer, optional) — Return only the direct children of this category id. Omit for the whole table.
- `top_level_only` (boolean, optional, default false) — Return only the 17 top-level categories instead of all 299.

**Returns:** category_count and categories[] with category_id, name, slug, parent_category_id, is_top_level, child_category_ids[], listing_types[] (the directions this category accepts), serp_type, type and url.

**Example request body:**
```json
{
  "parent": 391
}
```

### POST https://api.reefapi.com/ikman/v1/locations — 1 credit
The site's live location table — the resolver `search` needs, because `location` takes a numeric id. 331 rows: Sri Lanka's 27 districts and the 304 cities inside them, each with its parent and, on request, the boundary polygon the source publishes.

**Parameters:**
- `parent` (integer, optional) — Return only the cities inside this district id. Omit for the whole table.
- `districts_only` (boolean, optional, default false) — Return only the 27 districts instead of all 331 rows.
- `include_geography` (boolean, optional, default false) — Include each location's boundary polygon as the source publishes it. Off by default because it is the bulk of the payload (measured 288 KB with, 44 KB without).

**Returns:** location_count and locations[] with location_id, name, slug, geo_region, parent_location_id, is_district, child_location_ids[] and (only with include_geography) geography.

**Example request body:**
```json
{
  "parent": 1506
}
```

## 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=ikman
- Human docs page: https://reefapi.com/docs/ikman
- Overview page: https://reefapi.com/ikman-api
- Every ReefAPI API in one file (for your AI): https://reefapi.com/llms-full.txt
