Metadata-Version: 2.4
Name: happyendpoint
Version: 0.2.2
Summary: Python client for real-time real estate, ecommerce, retail, and travel data APIs
Author-email: Happy Endpoint <happyendpointhq@gmail.com>
License: MIT
Project-URL: Homepage, https://happyendpoint.com
Project-URL: Documentation, https://docs.happyendpoint.com
Project-URL: Repository, https://github.com/happyendpointhq/happyendpoint-python
Project-URL: Issues, https://github.com/happyendpointhq/happyendpoint-python/issues
Keywords: real-estate,property-data,real-estate-api,property-api,ecommerce,product-data,retail-data,price-tracking,travel-api,rental-yield,market-data,api-client,sdk
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Internet :: WWW/HTTP
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests<3,>=2.28
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: responses>=0.24; extra == "dev"
Dynamic: license-file

# happyendpoint

Python client for [Happy Endpoint](https://happyendpoint.com) 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.

```bash
pip install happyendpoint
```

```python
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

```bash
pip install happyendpoint
```

Get a key at [rapidapi.com/user/happyendpoint](https://rapidapi.com/user/happyendpoint).
One key works across every API and each has a free tier, but you subscribe to
each API separately.

```python
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:

```python
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:

```python
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:

```python
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:

```python
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:

```python
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:

```python
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`

```python
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:

```python
for country in ("us", "gb", "de", "se"):
    print(country, he.home.search("bookcase", country_code=country))
```

### Travel, `he.travel`

```python
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

```python
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

```python
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](https://github.com/happyendpointhq/happyendpoint-js). There is
also an MCP server for AI assistants,
[happyendpoint-mcp](https://github.com/happyendpointhq/happyendpoint-mcp).

---

## Development

```bash
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](https://happyendpoint.com) builds and maintains real-time data
APIs across real estate, ecommerce, retail, and travel.

- Catalogue: [happyendpoint.com/library](https://happyendpoint.com/library)
- Datasets: [happyendpoint.com/datasets](https://happyendpoint.com/datasets)
- Documentation: [docs.happyendpoint.com](https://docs.happyendpoint.com)
- Contact: happyendpointhq@gmail.com

## Licence

MIT. See [LICENSE](LICENSE).
