DarakPlatform
Use casesDocsGuidesPricing
DocsGuides

Changelog

Changes to the Darak API. Within /v1 we only add; anything deprecated is announced here first. See the versioning policy.

RSS feed
  1. September 25, 2026

    Reference data is free

    • changedGET /cities, /cities/{city}/neighborhoods, /cities/{city}/directions and /enums now cost 0 units, like /limits. Resolve neighborhood names and valid filter values as often as you need without spending quota.
  2. September 25, 2026

    Timestamp inputs now require seconds

    • changedBreaking validation change in v1: ISO date-time inputs must include seconds. For example, 2026-09-21T10:00:00Z is accepted; the previously accepted 2026-09-21T10:00Z is now rejected. This affects listings updated_since, organization key expires_at, and request-log since/until filters. Zod 4.6 also narrows the OpenAPI date-time patterns on responses; the server continues to emit timestamps with seconds.
    • changedOpenAPI nullable fields now use JSON Schema type arrays (for example, [string, null]) instead of equivalent anyOf unions. Generated example responses may show representative non-null values where they previously showed null; runtime response fields remain nullable.
  3. September 23, 2026

    Webhooks

    • addedSubscribe an HTTPS URL to listing events instead of polling for them. POST /organization/webhooks takes a URL, the event types you want and the listing filters that decide which listings count — the same vocabulary GET /listings takes. A city is required: without one the subscription is every new listing in the Kingdom, and neither of us finds that out until the volume arrives. listing.created delivers today; listing.price_changed and listing.delisted are accepted on a subscription now and start arriving when they land.
    • addedDeliveries are signed with Darak-Signature: t=…,v1=…, HMAC-SHA256 over {timestamp}.{body}, in the scheme Stripe popularised — enough libraries and enough people already know it. The timestamp is inside what was signed, so a captured request can't be replayed later. The webhooks guide carries the verification code rather than describing it.
    • addedDelivery is at least once: retry six times over about eight hours, deduplicate on Darak-Event-Id, which is stable across retries and replays. A 4xx is retried as well as a 5xx, since a 404 usually means a deploy is in flight rather than that the event is unwanted. Twenty deliveries that exhaust their attempts disable the endpoint rather than keep posting at something long gone.
    • addedGET /organization/webhooks/{id}/deliveries shows what was sent and what your server answered, including the first 500 characters of its reply, and …/replay sends one again once you've fixed whatever rejected it.
  4. September 23, 2026

    Test keys

    • addedA dk_test_… key reads the same live data as a live key, under a fixed allowance of 1,000 units a month at 30 units a minute, 25 results a page and 250 deep. Create one on the API keys page; an organization can hold three, and they don't count against the live-key limit your plan sets.
    • addedTest usage is metered against its own counters, so a test key left running in CI cannot eat the quota your production integration depends on, and it never appears in usage or billing. Overage is off on a test key whatever your plan allows, so it can't generate a charge.
    • changedA test key reaches the same APIs your plan does — a sandbox that answered 403 where production answers 200 would send you debugging the wrong thing. It cannot reach /v1/organization, which still needs an admin key.
  5. September 23, 2026

    Read your own limits, and your own request log

    • addedGET /limits returns the plan, rate limit, monthly quota, page size and paging depth that apply to the key making the call, and the APIs that key can reach. It costs no units and is on every plan. GET /organization has carried the same numbers all along, but it takes an admin key and admin keys are owners-only — so a running service had no way to ask what its own page size was, and found out by requesting too many and reading the 400. Read this at start-up instead of hard-coding a page size, and the same code works on every plan.
    • addedGET /organization/request-logs returns the calls your organization made, newest first, within your retention window. Filter by key_id, route, status or a time range — or by request_id to look up one call, which ignores every other filter. Until now the log existed but only the dashboard could read it, so answering "my job got a 400 at 03:00, what was wrong with it" meant a person opening a web page.
    • addedFailed calls now record the message and the parameter at fault alongside the error code, so the log can say *which* value was rejected rather than only that something was. Both follow the same privacy switch as request parameters and IP addresses: organizations that turn off request details get neither.
  6. September 23, 2026

    Listing and Pagination are named schemas

    • changedListing and Pagination are defined once under components.schemas and referenced, instead of being written out in full in every response that returns them. Nothing about the responses changes — the same fields, in the same shape — but a client generated from the spec now gets a named Listing type rather than an anonymous inline one repeated per endpoint, and the document is 61% smaller (750 KB to 295 KB), which is most of a second off loading the reference.
    • fixedThe three copies of the pagination schema had already drifted apart in their wording; there is now one.
  7. September 23, 2026

    Filters that didn't do what they said

    • addedA path that exists, called with a method it doesn't take, now returns 405 method_not_allowed in the usual error shape with an Allow header naming the methods that work. POST /v1/listings used to get an empty 405 from the framework — no body, no request_id. A CORS preflight to any endpoint now gets a 204 rather than HTML.
    • fixedq is documented as matching the advertiser's title and description, which is what it has always done — it was described as matching headline, a field Darak composes for the response and never searches. Its query syntax is documented too: several words must all appear, "a quoted phrase" matches whole, and | means either.
    • fixedThe docs claimed GET /listings/{id} and /listings/batch return any live listing. They don't: a listing is served once Darak has copied its photos to its own CDN, so a photo-less listing is a 404 and comes back in missing_ids. Land is exempt, being routinely advertised without photos. What those endpoints actually skip is the quality filter, so they return listings search won't.
    • fixedadvertiser.type showed company as its example — a value the column never holds. It returns the source's own wording (agency, developer, individual_owner and so on); company and individual are what the *filter* takes, grouping those, so a response value can't be passed straight back as a filter.
    • changedThe quality filter is described accurately: per-tier price fences from the median and MAD of log price, with bedroom and area caps and a floor at 30% of the neighborhood median. The coverage page had been calling it winsorized percentiles, which it was until March 2026 and hasn't been since.
    • changedEight filters had no description at all (beds_min, beds_max, bathrooms_min, bathrooms_max, price_max, area_max, furnished, days_on_market_max), and four described behaviour loosely enough to mislead: beds is exact at every value including 5 (darak.app treats 5 as five-or-more); floor=ground and floor=upper don't partition, since a listing with no stated floor is in neither; verified=false includes listings the source said nothing about; neighborhood_id_exclude keeps listings with no neighborhood while neighborhood_id drops them; and a misspelled source narrows to nothing rather than erroring.
  8. September 23, 2026

    The guide has its own pages, and says what your plan allows

    • addedThe written guide is now at [platform.darak.app/docs/guides](https://platform.darak.app/docs/guides/quickstart) — quickstart, authentication, limits, pagination, retrying, errors, data notes, versioning and terms, one page each. The same text still opens the API reference, so there is one copy of it; the pages just have room for tables and are readable without JavaScript.
    • addedEvery plan's limits are published: units per minute, units per month, page size, **paging depth** and active keys, for Free, Starter, Growth and Pro. Paging depth in particular appeared nowhere except Pro's, so there was no way to size a sync without hitting result_window_exceeded in production.
    • changeddoc_url in an error body now links to that specific code — …/docs/guides/errors#invalid-cursor — rather than to the whole errors section.
    • addedAuthentication documents the two key kinds and what each reaches, how to store a key, and OAuth access tokens: what they can do, and which organization they act for when someone belongs to several.
    • addedTerms now states in the docs the four rules that shape how you build: storage is capped at 30 days from retrieval, delisted listings come down within 7 days, takedowns are 3 business days, and attribution is required where you display data. They were only in the terms of use, one link away from the sync recipe that makes a local copy.
    • addedThe quickstart links the OpenAPI document, both SDKs, the changelog feed, the status page and an address to write to with a request_id.
  9. September 23, 2026

    The reference now describes what the API actually sends

    • fixedThe Administration endpoints are documented as taking an admin key (dk_admin_…). The spec declared a single dk_live_… scheme for everything, so a client generated from it failed every Administration call with nothing in the reference to explain why.
    • addedEvery endpoint shows an example response, built from the schema so it can never describe a shape the API doesn't return. Nullable fields with nothing characteristic to show appear as null rather than invented values.
    • fixedTimestamps are marked format: date-time. They have always been ISO 8601 in UTC, but nothing said so, so generated clients typed them as plain strings.
    • fixedThe documented response headers match the ones sent: X-Request-Units, X-Quota-Limit and X-Quota-Reset were missing, Retry-After was described only in prose, and the Deprecation, Sunset and Link headers a deprecated endpoint sends were undocumented. Administration endpoints no longer list rate-limit headers, which they never sent — they are unmetered.
    • addedEach section of the reference now says what it covers and which plan reaches it, and the spec links the terms of use.
  10. September 23, 2026

    Pagination cursors are signed

    • changedCursors now carry a signature, so the API accepts only cursors it issued. **Cursors issued before this change no longer work** and return 400 invalid_cursor; a paging loop that was mid-query when this shipped starts again from its first page. Nothing else changes: keep passing next_cursor back exactly as you received it.
    • changedA malformed, edited or mismatched cursor now reports invalid_cursor rather than the generic invalid_value. It still arrives as a 400 with param: "cursor".
    • fixedA cursor's contents could previously be edited to page to any offset, which sidestepped the paging depth your plan allows. Cursors have always been documented as opaque values to pass back unchanged, and that is now enforced rather than assumed — which also means their contents can change when pagination does, without breaking anyone.
  11. September 23, 2026

    Idempotency keys, and conflicts that say so

    • addedPOST endpoints accept an Idempotency-Key header. Send the same key with the same body and you get the first call's response back — marked Idempotent-Replay: true — instead of the work happening twice. A key reused with a different body is rejected with 409 idempotency_key_reuse. This makes POST /organization/keys safe to retry: before it, a request that timed out after the key was created minted a second key on retry, and its secret was only ever shown once. Keys are remembered for 24 hours.
    • fixedCreating keys faster than the hourly limit allows now returns 429 rate_limited with a Retry-After header. It used to return 400 limit_reached with no wait hint, so a caller that should have backed off read it as a permanent bad request.
    • changedRequests that clash with the current state of an organization — the active-key limit, the project limit, a name already taken — now return 409 with a conflict-type error naming which limit (key_limit, project_limit, name_taken), instead of a single 400 limit_reached. Only /v1/organization endpoints are affected.
    • addedinvalid_cursor distinguishes a malformed or mismatched cursor from other bad values; it used to report as invalid_value.
    • changedThe errors section of these docs now explains every code — what it means, whether retrying helps, and what to do — rather than listing code names. Notably rate_limited (wait seconds) and monthly_quota_exceeded (wait for the next UTC month) no longer read as the same instruction.
    • fixedA request to an unknown /v1 path now returns the X-Request-Id header as well as request_id in the body, and a CORS preflight to one gets a JSON-shaped answer instead of HTML.
  12. September 23, 2026

    Broker listings endpoint removed

    • removedPOST /broker-listings and the brokers scope are removed. The endpoint returned advertisers' names and phone numbers, which Darak no longer shares outside the listing page, and no client had called it.
  13. September 23, 2026

    Sign in with OAuth, as well as an API key

    • addedAuthorization: Bearer now accepts an OAuth access token as well as an API key. Tokens are issued by platform.darak.app, last an hour, and act for the person who granted them, so a tool can read data on their behalf without ever holding a key. API keys are unchanged and remain the way a server, script or scheduled job authenticates.
    • changedAn OAuth token can read listings, market data, analytics, projects and reference data. It cannot submit broker listings or manage an organization, whatever the plan allows — those need a key.
  14. September 23, 2026

    Project filters, and enums for all of them

    • addedGET /projects and GET /project-units accept features and banks (a project must have all the ids you ask for), and /project-units accepts bathrooms and bathrooms_min.
    • addedGET /enums now lists amenities, project_features, project_banks and advertiser_types, so every enumerated filter has a source of truth.
    • changedadvertiser_type on GET /listings takes company or individual. The underlying column holds eight source-specific values, so these two group them; an intermediary who does not say whether they are a firm or a person matches neither.
  15. September 23, 2026

    sort=recommended

    • addedGET /listings accepts sort=recommended, the ranking darak.app itself defaults to: asking price against comparable listings, how long the ad has been up, how complete it is and how sought-after the neighborhood is, with no single source allowed to fill the top of the page. It is a ranking rather than an ordering, so it is retuned from time to time and the same query can reorder between releases — page through it freely, but keep using sort=updated_asc with updated_since to sync. The default sort is unchanged (newest).
  16. September 23, 2026

    The rest of the listing filters

    • addedGET /listings and GET /listings/count accept q (free text over the headline and description), amenities and amenities_exclude, floor, verified, advertiser_type, compound and in_compound, max_age, rent_frequency, source_exclude, neighborhood_id_exclude, livings_min, days_on_market_min/days_on_market_max, and bathrooms/bathrooms_max alongside the existing bathrooms_min. These have always been part of darak.app's own search; they were simply never exposed here.
    • changedbathrooms is an exact number and livings_min is a minimum, each said explicitly rather than by convention — the same way beds is exact and beds_min is a minimum. No existing parameter changed meaning.
  17. September 22, 2026

    New plans, and units priced by results

    • changedList endpoints now cost 1 unit per 25 results asked for with limit (up to your plan's page size), and listing batch 1 unit per 25 ids. A request for 25 results still costs 1 unit. Analytics endpoints cost 2–5 units. Each endpoint's cost is x-units in the reference, and every response's X-Request-Units header.
    • addedA Growth plan (200,000 units a month, analytics included). Pro now includes 750,000 units a month and paging up to 20,000 results. Plans can be paid yearly (2 months free) and in SAR or USD.
    • addedGET /listings/{id}/price-history returns history_window_days: the Free plan sees the last 90 days of price changes, paid plans the full history.
  18. September 22, 2026

    Market data, analytics and off-plan projects

    • addedMarket data (Starter and up): GET /market/summary, /market/price-distribution, /market/area-distribution, /market/supply, /market/vacancy and /market/neighborhoods. Every figure uses one sample: live listings updated in the last 30 days, with the same outlier filter as darak.app, and each response states its window.
    • addedAnalytics: GET /listings/{id}/comparables, /listings/{id}/market-position, /market/deals, /market/rental-yield, /market/trends (monthly medians from real listing history since March 2026) and /market/neighborhoods/compare.
    • addedOff-plan projects: GET /projects, /projects/{id}, /projects/{id}/units, /project-units and /developers.
    • addedError codes neighborhood_not_found and project_not_found (404).
  19. September 22, 2026

    Listing headlines

    • addedListings have a headline: a short English description built from their fields (for example "3-bedroom apartment for rent in Al Malqa, Riyadh"), always present. title is still the advertiser's own title and is null for sources that don't have one.
  20. September 22, 2026

    Self-serve plans and spend caps

    • addedSubscribe to Starter or Pro from the dashboard, paying monthly by card in SAR. Past the monthly quota, requests keep working and are billed per 1,000 units, up to a monthly spend cap you set on the Billing page.
    • addedError code 429 spend_cap_reached: this month's extra usage reached your spend cap. Raise the cap, or wait for the quota to reset on the 1st.
  21. September 21, 2026

    Admin API

    • addedAdmin keys (dk_admin_…), created by owners in the dashboard, manage your organization from code: GET /organization, GET/POST /organization/keys, POST /organization/keys/{key_id}/revoke, GET /organization/projects, /organization/usage, /organization/members and /organization/audit-events. Admin calls aren't metered.
    • addedError codes admin_key_required and admin_key_not_allowed (admin and data keys each call only their own endpoints), plus forbidden_action and limit_reached for admin API requests your role or plan doesn't allow.
  22. September 21, 2026

    Restricted keys, key expiry and project caps

    • addedKeys can be limited to some APIs. Calls outside them fail with 403 scope_not_in_key.
    • addedProjects can have a monthly unit cap. Past it, that project's keys get 429 project_cap_exceeded until the 1st.
    • changedAn expired key now gets 401 expired_api_key (it used to get revoked_api_key). Revoked keys are unchanged.
  23. September 21, 2026

    Listings endpoints

    • addedGET /listings, GET /listings/count, GET /listings/batch, GET /listings/{id} and GET /listings/{id}/price-history.
    • addedSync a copy with sort=updated_asc and updated_since. Paging depth per query depends on your plan (result_window_reached).
    • addedListings carry financing details where the source publishes them: rent-now-pay-later, bank financing and landlord payment plans.
  24. September 21, 2026

    Darak API v1

    • addedAPI keys, plans with rate limits and monthly quotas, and the RateLimit-* and X-Quota-* headers.
    • addedReference data: GET /cities, GET /cities/{city}/neighborhoods, GET /cities/{city}/directions and GET /enums.
    • addedBroker listings for enterprise plans: POST /broker-listings.