Developer docs menu

Quickstart: markets, metrics and an estimate

Beginner

Make your first calls in Python or JavaScript and chain the core endpoints together.

Before you start

You need a JokerPrice account, an API key from Data & API, and Python 3.9+ with requests or Node 18+. Store the key in an environment variable so it never appears in your code.

Shell
export JOKERPRICE_API_KEY="ss_your_key_here"
pip install requests

Create a small client

Every request needs the same header and the same error handling, so put them in one place. The helper also waits and retries when the per-minute limit (120 during the free trial) is reached; the daily limit (25,000 during the free trial) raises an error instead.

jokerprice.py
# jokerprice.py: a small client for the JokerPrice API.
import os, time, requests

BASE = "https://jokerprice.com"
session = requests.Session()
session.headers["Authorization"] = "Bearer " + os.environ["JOKERPRICE_API_KEY"]

def api(method, path, **kwargs):
    """Call an /api/v1 endpoint and return its JSON. Waits and retries when the per-minute limit is hit."""
    while True:
        res = session.request(method, BASE + path, timeout=60, **kwargs)
        body = res.json()
        if res.status_code == 429 and body["error"]["code"] == "RATE_LIMIT":
            time.sleep(int(res.headers.get("Retry-After", "60")))
            continue
        if not res.ok:
            raise RuntimeError(f"{res.status_code} {body['error']['code']}: {body['error']['message']}")
        return body

def all_listings(city):
    """Every listing for a city, following nextCursor page by page (500 per request)."""
    out, cursor = [], None
    while True:
        params = {"city": city, "limit": 500}
        if cursor:
            params["cursor"] = cursor
        page = api("GET", "/api/v1/listings", params=params)
        out += page["listings"]
        cursor = page["nextCursor"]
        if not cursor:
            return out
jokerprice.mjs
// jokerprice.mjs: a small client for the JokerPrice API (Node 18+).
const BASE = "https://jokerprice.com";
const KEY = process.env.JOKERPRICE_API_KEY;
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

export async function api(path, { method = "GET", query = {}, body } = {}) {
  const url = new URL(BASE + path);
  for (const [name, value] of Object.entries(query)) url.searchParams.set(name, String(value));
  for (;;) {
    const res = await fetch(url, {
      method,
      headers: { Authorization: "Bearer " + KEY, ...(body ? { "Content-Type": "application/json" } : {}) },
      body: body ? JSON.stringify(body) : undefined,
    });
    const data = await res.json();
    if (res.status === 429 && data.error.code === "RATE_LIMIT") { await sleep(Number(res.headers.get("Retry-After") || 60) * 1000); continue; }
    if (!res.ok) throw new Error(res.status + " " + data.error.code + ": " + data.error.message);
    return data;
  }
}

List the markets

Start with /api/v1/markets. It returns every destination with at least 20 listings and the slug the other endpoints need.

Python
from jokerprice import api

markets = api("GET", "/api/v1/markets")["markets"]
for m in markets[:10]:
    t = m["tracker"]
    occupancy = f"{t['occupancy']:.0%}" if t and t["maturity"] == "estimating" else "collecting"
    median = m["nightly"]["median"] or 0
    print(f"{m['slug']:<26} {m['listings']:>6} listings   median SAR {median:>5.0f}   occupancy {occupancy}")

A market's tracker is null until JokerPrice tracks calendars there, and its maturity is "collecting" until there is enough history. Treat occupancy as meaningful only when it says "estimating".

Get performance for one city

/api/v1/metrics returns occupancy, ADR and revenue for a window you choose. past covers nights before today; forward.onTheBooks is the share of upcoming nights already booked.

JavaScript (Node 18+)
import { api } from "./jokerprice.mjs";

const { metrics } = await api("/api/v1/metrics", { query: { city: "riyadh", past_days: 30, forward_days: 60 } });
console.log("maturity", metrics.maturity);
console.log("occupancy", metrics.past.occupancy, "ADR", metrics.past.adr, "SAR");
console.log("revenue per listing per month", metrics.past.monthlyRevenuePerListing);
console.log("next 60 nights already booked", metrics.forward.onTheBooks);

Estimate revenue for a property

Send coordinates, bedrooms and a property type id to POST /api/v1/estimate. When JokerPrice has measured occupancy nearby you get one projection; otherwise you get three occupancy scenarios.

Python
from jokerprice import api

est = api("POST", "/api/v1/estimate", json={
    "latitude": 24.7743, "longitude": 46.7386,   # a point in north Riyadh
    "bedrooms": 2, "typeId": "6",                # 2-bedroom apartment
})["estimate"]

print("comparables:", est["asking"]["count"], " ADR basis:", est["adr"]["basis"])
if est["projection"]:
    print(f"Projected: SAR {est['projection']['annual']:,.0f} a year at {est['projection']['occupancy']:.0%}")
elif est["scenarios"]:
    for sc in est["scenarios"]:
        print(f"At {sc['occupancy']:.0%} occupancy: SAR {sc['annual']:,.0f} a year")
else:
    print("No comparables within the radius. Try a larger radiusKm.")

Next steps