happyendpoint
Python client for Happy Endpoint real-time data APIs: real estate listings and transactions, ecommerce and retail product data, and travel pricing. One key, one client, every data domain.
Beta. This is a 0.x release. The API surface may change before 1.0.
pip install happyendpoint
from happyendpoint import HappyEndpoint
he = HappyEndpoint() # reads RAPIDAPI_KEY
results = he.realestate.search("dubai marina", bedrooms="1", price_max=1_500_000)
print(f"{results.total} matching, median {results.median_price:,.0f}")
for prop in results[:3]:
print(prop.title)
print(f" {prop.price:,.0f} | {prop.area_sqm}sqm | {prop.price_per_sqm:,}/sqm")
Install and authenticate
pip install happyendpoint
Get a key at rapidapi.com/user/happyendpoint. One key works across every API and each has a free tier, but you subscribe to each API separately.
he = HappyEndpoint() # from RAPIDAPI_KEY
he = HappyEndpoint(api_key="your_key") # explicit
Requires Python 3.9 or newer.
Data domains
Clients are grouped by what the data is about, not by which site it came from.
| Attribute | Covers |
|---|---|
he.realestate |
Property listings, transactions, agents, off-plan developments |
he.beauty |
Beauty and cosmetics product catalogues, pricing, reviews |
he.home |
Home furnishing and furniture catalogues across several countries |
he.travel |
Hotels, flights, and car rental pricing |
Domain aliases are the recommended way in. Source-specific attributes exist for anyone who needs a particular provider, but the aliases read better and stay stable if the underlying source for a domain changes.
Areas, not IDs
Property search endpoints take a numeric location id, and passing a wrong one returns a different area rather than an error. Results look plausible and are quietly about the wrong place.
So this client takes names and resolves them, then reports what it matched:
results = he.realestate.search("jvc")
print(results.location.name, results.location.id)
Ambiguous names resolve to the busiest match, which is almost always the community rather than a building sharing its name. To choose yourself:
for loc in he.realestate.find_locations("marina", limit=5):
print(f"{loc.name:<40} {loc.id:<8} {loc.listings:,} listings")
results = he.realestate.search(he.realestate.find_locations("marina")[2])
Unresolvable names raise LocationNotFound rather than falling back to a guess.
Rental yields
Gross yield across several areas is not a single API call. This computes it:
for row in he.realestate.compare_yields(["jvc", "business bay", "downtown dubai"]):
print(f"{row.area:<34} gross {row.gross_yield_pct:>5}% net {row.net_yield_pct():>5}%")
net_yield_pct() subtracts typical holding costs and takes overrides:
row.net_yield_pct(
service_charge_per_sqm=215,
area_sqm=90,
management_pct=5,
maintenance_pct=1,
vacancy_pct=5,
)
Defaults are deliberately realistic rather than flattering. The gap between gross and net is usually 2 to 3 percentage points.
Transactions versus listings
The distinction that matters most for analysis:
listings = he.realestate.search("dubai marina") # what sellers ASK
txns = he.realestate.transactions("dubai marina") # what buyers PAID
for t in txns[:3]:
print(f"{t.date} {t.amount:>12,.0f} {t.price_per_sqm:>8,.0f}/sqm {t.sale_type}")
sale_type distinguishes a developer's first sale from an owner resale, which
matters when comparing new-build against existing stock.
Normalised results
Upstream endpoints disagree with each other about casing, nesting, and envelope shape. This client hides that behind consistent dataclasses:
prop.title # str, whichever shape the endpoint returned
prop.price_per_sqm # computed
prop.area_sqft # converted
prop.location # readable hierarchy path
prop.amenities # flattened out of nested groups
prop.is_annual_rent # rentals are quoted yearly, easy to misread
prop.raw # the untouched payload, for fields not modelled here
API reference
Real estate, he.realestate
| Method | Returns |
|---|---|
search(location, purpose, property_type, bedrooms, price_min, price_max, page) |
SearchResult |
get_property(property_id) |
Property, slow endpoint |
search_off_plan(location, price_max, max_pre_handover_payment) |
SearchResult |
iter_all(location, max_pages, delay, **kwargs) |
iterator of Property |
transactions(location, purpose, time_period, page) |
list[Transaction] |
find_agents(location, purpose) |
list[Agent] |
find_locations(query, limit) |
list[Location] |
resolve_location(query) |
Location |
rental_yield(location, bedrooms, property_type) |
YieldResult or None |
compare_yields(locations, bedrooms, property_type) |
list[YieldResult], ranked |
Retail, he.beauty and he.home
he.beauty.search("moisturizer")
he.beauty.reviews(product_id)
he.beauty.availability(product_id, store_id)
he.home.search("desk", country_code="us")
he.home.search_filters("desk") # facets, for building filter UIs
he.home.countries()
Home furnishing data covers eight countries, and the same product is priced differently in each, so cross-market comparison is a couple of calls:
for country in ("us", "gb", "de", "se"):
print(country, he.home.search("bookcase", country_code=country))
Travel, he.travel
locations = he.travel.find_location("new york")
hotels = he.travel.search_hotels(
location_id="3000016152",
check_in="2026-03-05", # ISO in, converted internally
check_out="2026-03-07",
adults=2,
)
Pass ISO dates. Hotel endpoints expect MM-DD-YYYY while car endpoints expect
YYYY-MM-DD; the client handles the difference.
Errors
from happyendpoint import (
HappyEndpointError, # base class
AuthenticationError, # 401, key missing or wrong
SubscriptionError, # 403, not subscribed or quota exhausted
RateLimitError, # 429 after retries
LocationNotFound, # no area matched
)
SubscriptionError names the API and links its subscription page, because "403"
alone does not tell you which API you need.
Rate limits and timeouts retry with exponential backoff. Tune with
HappyEndpoint(max_retries=5, timeout=45).
Paging
page_two = he.realestate.search("jvc", page=2)
for prop in he.realestate.iter_all("jvc", max_pages=5, delay=0.5):
print(prop.title)
iter_all sleeps between requests so you stay inside the free tier. Drop
max_pages to fetch everything.
FAQ
Do I need a paid plan?
No. Every API has a free tier, enough to explore and prototype.
Why does a call raise SubscriptionError?
You are subscribed to some APIs but not the one you called. The message names it
and links the page. HappyEndpoint.available_apis() lists all of them.
Are rental prices monthly or annual?
Annual. prop.is_annual_rent tells you, so you do not divide by twelve without
noticing.
Which price should I use for valuation?
Transactions. search() returns asking prices, which run higher than what
property actually sells for.
Can I get the untouched API response?
Yes, prop.raw, txn.raw, and agent.raw hold the original payload.
Is there a JavaScript version?
Yes, happyendpoint-js. There is also an MCP server for AI assistants, happyendpoint-mcp.
Development
git clone https://github.com/happyendpointhq/happyendpoint-python
cd happyendpoint-python
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest
Tests mock HTTP, so they run without a key.
Status
Beta, 0.x. The public API may change before 1.0. Pin a version if you need stability:
happyendpoint==0.2.0
Disclaimer
Happy Endpoint is an independent provider. This package is not affiliated with, endorsed by, sponsored by, or connected to any of the websites, platforms, retailers, or marketplaces whose data may be accessible through the underlying APIs.
All product names, brands, trademarks, and registered trademarks are the property of their respective owners. Any reference to them is descriptive only, to identify the subject matter of the data, and does not imply any association or endorsement.
Users are responsible for ensuring their use of any data complies with applicable laws and the terms of service of the relevant source.
About Happy Endpoint
Happy Endpoint builds and maintains real-time data APIs across real estate, ecommerce, retail, and travel.
- Catalogue: happyendpoint.com/library
- Datasets: happyendpoint.com/datasets
- Documentation: docs.happyendpoint.com
- Contact: happyendpointhq@gmail.com
Licence
MIT. See LICENSE.
Release files for happyendpoint 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| happyendpoint-0.2.0.tar.gz | 22.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| happyendpoint-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 41.4 kB
Release files / happyendpoint-0.2.0.tar.gz
| Download URL | happyendpoint-0.2.0.tar.gz |
|---|---|
| Size | 22.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
372d3e2b27e5958f207727b539d79c1467d80872f0b763891c3fc8e0eea656a6
|
|
BLAKE2b-256 checksum How to use checksums |
1768b36a3c3f216ff011e72825e0764719d7ddf2161c5278a09389d69a4abd1a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|
Release files / happyendpoint-0.2.0-py3-none-any.whl
| Download URL | happyendpoint-0.2.0-py3-none-any.whl |
|---|---|
| Size | 18.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f87af891106e1588fe7253a78214379f82be95b8aecacb279c343f29e415100f
|
|
BLAKE2b-256 checksum How to use checksums |
ced12357420e1bd9bbfd7fa02d8492a220eab1aae6b8520b7963b4be944b63f2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|