Quickstart
Every request goes to one host:
https://api.investviews.ai/public/v1
By the end of this page you will have a token, resolved a place name into an id, pulled market figures for it, and checked what it cost.
1. Get a token
Tokens are created on the API tokens page of your InvestViews account. A token carries a prefix that tells you what it can do:
| Prefix | What it is |
|---|---|
iv_live_rw_ |
read/write |
iv_live_ro_ |
read-only |
Every endpoint in this API is a read, so either kind works here. See Authentication for what each refusal means.
Before you run anything below
Replace {TOKEN} with your own token. Keep it out of source control, out of URLs, and
out of anything that gets logged — it is a bearer credential, so whoever holds it is you.
2. Make a first call that costs nothing
/ping needs no token at all. Use it to check you
can reach the API before you debug anything harder.
curl -s https://api.investviews.ai/public/v1/ping
{
"status": "ok",
"version": "v1"
}
Now try an authenticated call that is still free —
/coverage tells you which countries this API
can answer for:
curl -s https://api.investviews.ai/public/v1/coverage \
-H "Authorization: Bearer {TOKEN}"
If that returns 401, the token is wrong. If it returns 402, the token is fine and the
subscription is not — those are different problems, and
Authentication explains why they are different responses.
3. Turn a place name into something you can query
You know "Valencia". The API works in ids.
/geo/search turns one into the other:
curl -s "https://api.investviews.ai/public/v1/geo/search?q=valencia" \
-H "Authorization: Bearer {TOKEN}"
Each result carries a geo_id, its level (region, province, city, macrozone or
microzone), and prices_available — whether /stats/current can actually answer for it.
Four things worth knowing now rather than later:
prices_available: falsemeans stop. There is nothing in that place. A metered call on it answers200with an emptystatsarray and still costs you quota.- Names are not unique. 13.6% of neighbourhood names repeat inside their own country, so
a search for a common name returns several places.
ancestors— an array of objects, fine to coarse, each carrying its owngeo_id— is how you tell them apart. Match ongeo_id, never onname. - The result gives you
hexes_url, not the cells themselves. A city is thousands of cells at resolution 8, and inlining them would spend your whole context on a lookup. Pass thegeo_idstraight to/stats/current; followhexes_urlonly when you genuinely need the cell list. - If you do not have a name to search for, start at
/geoand walk down: it lists countries, then the children of whatever you name asparent. Geography walks that whole cascade, from nothing to a metered call.
4. Ask for the market
/stats/current is the metered endpoint —
the one that costs quota. Name the territory in exactly one of three ways:
| Selector | Use it when |
|---|---|
geo_id= |
you resolved a place in step 3 |
h3= |
you already hold cell ids, all at one resolution |
lat= + lng= + radius_km= |
you want a circle around a point |
Sending two selectors is refused, not resolved — a request naming both a place and a point has two different answers and no way for the API to know which one you meant.
curl -s "https://api.investviews.ai/public/v1/stats/current?geo_id=R14727511¤cy=EUR" \
-H "Authorization: Bearer {TOKEN}"
{
"as_of": "2026-08-16",
"currency": "EUR",
"resolution": 8,
"territory": {
"selector": "geo_id",
"place": { "name": "Russafa", "level": "microzone", "country": "es" }
},
"filters": {
"ad_sub_type": "buy",
"units": { "size": "m2", "price": "USD" }
},
"stats": [
{
"h3": "613498079267520511",
"estimated": false,
"median_price": 249000,
"median_price_per_sqm": 2790,
"median_area": 88.0,
"name": "València, Eixample, Russafa"
}
],
"snapshot": {
"as_of": "2026-07-01",
"built": "monthly",
"window_months": 1,
"outlier_trim_pct": 2
}
}
5. Read the answer correctly
This is the step people skip, and it is the one that produces wrong numbers in a report.
filters is what was actually applied, defaults included. You did not send
ad_sub_type above, and it defaulted to buy — sale prices, not rents. Cite the population
the response describes, not the one you meant to ask for.
The figures describe one complete period, not today. The snapshot block says how they
were built: one calendar month at resolutions 4–6, a rolling three-month window at 7–8. The
table is built monthly over complete periods only, so as_of can be up to 34 days old. The
sentence to write is "in July 2026, over that month" — never "the median price today".
"estimated": true means the figures are borrowed. Too few listings fell in that cell,
so they are pooled from its six immediate neighbours, about 1.2 km out. Report those as an
estimate for the area, never as that cell's own market.
Price filters are USD. currency=EUR changed how the figures are displayed. It did
not change which listings were selected. min_price_usd and max_price_usd filter USD bins
whatever currency says, and the bare min_price / max_price are refused rather than
ignored — a filter that was silently dropped would hand you a complete, plausible,
unfiltered answer that you would then report as a filtered one.
Data covers all of this properly.
6. Check what it cost
curl -s https://api.investviews.ai/public/v1/usage \
-H "Authorization: Bearer {TOKEN}"
Every response also carries the answer in its headers:
| Header | Meaning |
|---|---|
X-Quota-Group |
which budget this request spent from |
X-Quota-Limit |
the allowance — or the word uncounted |
X-Quota-Used |
spent this cycle, absent on an uncounted group |
X-RateLimit-Remaining |
requests left this minute |
Only /stats/current and /stats/history are request-metered. Everything you did in steps
2 and 3 was free. Quota explains why.
7. Where to go next
- Geography — what H3 resolutions 4–8 mean on the ground, and the three ways into the data.
- Data — periods, windows, trimming, estimated cells, and how current a market is.
- Errors — the error shape, every code, and which ones are worth retrying.
- API Reference — every parameter, with a playground that sends real requests.