Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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")             # note: `keyword`, not `query`, upstream
he.beauty.search_by_brand("gucci")          # brand name, not an id
he.beauty.reviews(product_id)
he.beauty.availability(sku_id, latitude, longitude)
he.beauty.stores(latitude, longitude, radius=50)

he.home.search("desk", country_code="us")
he.home.search_filters("desk")              # facets, for building filter UIs
he.home.countries()

Home endpoints require both a country and a language code. language_code defaults to "en"; omitting it upstream returns a validation error rather than falling back, so the client always sends it.

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.2

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.

Licence

MIT. See LICENSE.

About

Python client for real-time UAE property, retail, and travel data. Search by area name, computed rental yields.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages