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.
export JOKERPRICE_API_KEY="ss_your_key_here"
pip install requestsCreate 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: 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: 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.
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.
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.
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
- Revenue estimation goes deeper into the estimate response.
- Advanced filters covers paging through listings and filtering them.
- The endpoint reference lists every parameter.