Divar uses a server-driven widget API (JSON over HTTPS, protobuf type annotations embedded via @type).
All responses are lists of typed widget_type objects. Base URL: https://api.divar.ir.
Every filter field uses a typed wrapper. Wrong type = HTTP 400. Use this table exactly:
FieldWrapperExamplecategory``str``{ "str": { "value": "residential-rent" } }``sort``str``{ "str": { "value": "sort_date" } }``districts``repeated_string``{ "repeated_string": { "value": ["920", "82"] } }``rooms``repeated_string``{ "repeated_string": { "value": ["یک", "دو"] } }``rent``number_range``{ "number_range": { "minimum": "20000000", "maximum": "60000000" } }``credit``number_range``{ "number_range": { "minimum": "300000000", "maximum": "600000000" } }``size``number_range``{ "number_range": { "minimum": "80", "maximum": "120" } }``balcony``boolean``{ "boolean": { "value": true } }``parking``boolean``{ "boolean": { "value": true } }
number_rangemin/max values are strings, not numbers.booleanvalue is a JS boolean (true/false), not a string.
The API slug differs from the divar.ir URL path segment. Never use the URL segment as the slug.
API slug ( category.str.value)URL pathPersianresidential-rent``rent-residentialاجاره مسکونیapartment-sell``apartment-sellفروش آپارتمانapartment-rent``rent-apartmentاجاره آپارتمانbuy-residential``buy-residentialفروش مسکونیreal-estate``real-estateهمه ملکbuy-commercial-property``buy-commercial-propertyفروش اداری/تجاریrent-commercial-property``rent-commercial-propertyاجاره اداری/تجاریrent-temporary``rent-temporaryاجاره کوتاهمدت
When in doubt, intercept a real browser request via DevTools to read the actual category.str.value.
POST /v8/postlist/w/search
Verified working payload (residential rental, Tehran, tested 2026-04-01 → returned 24 listings):
json
{
"city_ids": ["1"],
"source_view": "SEARCH",
"disable_recommendation": false,
"search_data": {
"form_data": {
"data": {
"category": { "str": { "value": "residential-rent" } },
"districts": { "repeated_string": { "value": ["139","143","145","4162","75","78","82","920","921"] } },
"rooms": { "repeated_string": { "value": ["دو","یک"] } },
"rent": { "number_range": { "minimum": "20000000", "maximum": "60000000" } },
"credit": { "number_range": { "minimum": "300000000", "maximum": "600000000" } },
"size": { "number_range": { "minimum": "80", "maximum": "120" } },
"balcony": { "boolean": { "value": true } },
"parking": { "boolean": { "value": true } }
}
},
"server_payload": {
"@type": "type.googleapis.com/widgets.SearchData.ServerPayload",
"additional_form_data": {
"data": { "sort": { "str": { "value": "sort_date" } } }
}
}
}
}
Response: { "list_top_widgets": [...], "list_widgets": [...] }
Filter to widget_type === "POST_ROW" to get listings. Each POST_ROW has:
data.title — listing title (Persian)data.middle_description_text — price stringdata.bottom_description_text — relative time ("دقایقی پیش")data.action.payload.token — unique post ID (use for detail/contact endpoints)data.action.payload.web_info.district_persian — district namedata.image_count — number of photosCursor-based. On page 1, no pagination_data. On page 2+, add:
json
{
"pagination_data": {
"@type": "type.googleapis.com/post_list.PaginationData",
"last_post_date": "<ISO8601 from previous last post>",
"page": 2,
"layer_page": 2,
"search_uid": "<uuid — keep constant for the session>",
"cumulative_widgets_count": 24
}
}
viewed_tokens is an optional gzip+base64 bloom filter for server-side deduplication — safe to omit.
GET /v8/posts-v2/web/{token}
Returns sections ( BREADCRUMB, HEADER, IMAGES, DETAILS) each containing widget arrays.
Common detail widget types: KEY_VALUE (room count, floor, etc.), DESCRIPTION, IMAGE_GALLERY, MAP.
POST /v8/postcontact/web/contact_info_v2/{token}
Body: {}. Returns widget_list with UNEXPANDABLE_ROW containing phone number.
Returns RBAC: access denied without authentication.
token cookie + did (device ID) cookie, set by divar.ir on login.Authorization: Basic <jwt> — same JWT value as the token cookie.divar.ir.Authorization header explicitly.When a user pastes a URL like:
https://divar.ir/s/tehran/rent-residential/almahdi?balcony=true&credit=300000000-600000000&districts=920%2C82&rent=20000000-60000000&rooms=%D8%AF%D9%88%2C%DB%8C%DA%A9&size=80-120
Map query params to form_data fields:
URL paramform_data fieldWrapperURL path segment (e.g. rent-residential)category → look up API slugstr``districts=920,82,...``districts``repeated_string (split on ,)rooms=دو,یک``rooms``repeated_string (split on ,)rent=min-max``rent``number_range (split on -)credit=min-max``credit``number_range (split on -)size=min-max``size``number_range (split on -)balcony=true``balcony``boolean``parking=true``parking``boolean``map_bbox=...not in form_data — belongs in /v8/mapview/viewport camera_info.bbox—map_place_hash=...not in form_data—
For full API reference (all endpoints, widget system, map/geo, autocomplete, filters):
→ read references/api-reference.md