返回 Skill 列表
extension
分类: 数据与分析无需 API Key

Divar Api

在用户需要从伊朗最大分类平台Divar(divar.ir)搜索、查询或提取数据时使用此技能。触发词包括:搜索。

person作者: hadifarnoudhubclawhub

Divar API Skill

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.

Critical: form_data field types

Every filter field uses a typed wrapper. Wrong type = HTTP 400. Use this table exactly:

| Field | Wrapper | Example | |-------|---------|---------| | category | 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_range min/max values are strings, not numbers. boolean value is a JS boolean (true/false), not a string.

Critical: category slugs

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 path | Persian | |---------------------------------|----------|---------| | residential-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.

Search endpoint

POST /v8/postlist/w/search

Verified working payload (residential rental, Tehran, tested 2026-04-01 → returned 24 listings):

{
  "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 string
  • data.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 name
  • data.image_count — number of photos

Pagination

Cursor-based. On page 1, no pagination_data. On page 2+, add:

{
  "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.

Post detail

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.

Contact info (requires auth)

POST /v8/postcontact/web/contact_info_v2/{token}

Body: {}. Returns widget_list with UNEXPANDABLE_ROW containing phone number. Returns RBAC: access denied without authentication.

Authentication

  • Cookie-based: token cookie + did (device ID) cookie, set by divar.ir on login.
  • Header: Authorization: Basic <jwt> — same JWT value as the token cookie.
  • The search endpoint works with session cookies alone when called same-origin from divar.ir.
  • Cross-origin calls (e.g. from a script) need the Authorization header explicitly.

Parsing a divar.ir search URL → API payload

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 param | form_data field | Wrapper | |-----------|----------------|---------| | URL path segment (e.g. rent-residential) | category → look up API slug | str | | 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