Errors
Every failure on this API has the same shape — including a wrong path, a wrong method and an unparseable body. There is one envelope to handle, not one per endpoint.
The envelope
{
"error": "invalid_parameter",
"message": "radius_km must be 500 or less.",
"docs_url": "https://docs.investviews.ai/errors.html",
"parameter": "radius_km",
"request_id": "req_01JQ8Z..."
}
| Field | Always present | What it is |
|---|---|---|
error |
yes | the machine-readable code. Branch on this |
message |
yes | a human sentence. Never branch on this |
docs_url |
yes | where to read more |
parameter |
no | which parameter was at fault, on the 400s that name one |
request_id |
no | our handle on this failure. Quote it to support |
Other fields appear where they help — limit, resets_at, upgrade_url, did_you_mean.
They are additive: treat unknown keys as forward compatibility, not as an error in your
client.
Branch on error, never on message
message is written for a person and can be reworded at any time. A client that matches
on its text breaks on a copy edit, and breaks silently — it stops recognising a
condition it used to handle.
Every code
Credentials and billing
| Code | Status | Meaning |
|---|---|---|
invalid_token |
401 | no usable credential: header absent, malformed, unknown, or revoked |
subscription_inactive |
402 | the subscription that owns this token is not active |
quota_exhausted |
402 | this cycle's request allowance for the group is spent |
read_only_token |
403 | a read-only token was used on a write endpoint |
Three different problems with three different fixes. See
Authentication for why a lapsed plan is 402 rather than 401, and
Quota for what to do when the allowance runs out.
The request
| Code | Status | Meaning |
|---|---|---|
missing_parameter |
400 | a required parameter was not sent |
invalid_parameter |
400 | a parameter was sent but its value is unusable |
invalid_cursor |
400 | the pagination cursor is not one we issued for this query |
malformed_request |
4xx | the request could not be processed as sent (method, body, headers) |
too_many_hexes |
422 | the territory exceeds the per-request cell budget |
invalid_cursor usually means a cursor was constructed by hand or reused across a different
query. Follow the cursor the response gives you.
too_many_hexes means the territory is too large to answer in one request. Ask for a
coarser resolution, or split the territory.
Geography
| Code | Status | Meaning |
|---|---|---|
unknown_place |
404 | no place, cell or key matched — inside a market we do serve |
not_covered |
404 | this API holds no geographic data for that country |
resolution_not_in_plan |
403 | the requested H3 resolution is finer than the plan allows |
unknown_place and not_covered are both 404 and they mean different things. The first
says "we serve this country, but that name is not in it" — check the spelling, or browse
with /geo. The second says "we do not serve that
country at all" — check
/coverage before concluding a market does not
exist, because a market can hold listings without being searchable by name.
Limits and faults
| Code | Status | Meaning |
|---|---|---|
rate_limited |
429 | too many requests this minute |
not_found |
404 | no such resource or path |
internal_error |
500 | a fault on our side |
reports_unavailable_for_country |
422 | the report pipeline does not serve that country |
What to retry
Only two of these are worth retrying. The rest describe something about the request that will be just as wrong the second time.
| Code | Retry? | How |
|---|---|---|
rate_limited |
yes | wait Retry-After seconds, then retry |
internal_error |
yes | back off and retry. Quote request_id if it persists |
quota_exhausted |
not until the cycle rolls | X-Quota-Reset says when |
| everything else | no | fix the request, the token, or the billing |
Honour Retry-After rather than guessing
A 429 carries Retry-After in seconds. Backing off by a number you invented is how a
client that hit the limit once keeps hitting it.
Rate limits
Separate from quota. Quota is your allowance for the billing cycle; the rate limit is how fast you may spend it.
| Header | Meaning |
|---|---|
X-RateLimit-Limit |
requests per minute for this group. On every response |
X-RateLimit-Remaining |
requests left in this minute |
Retry-After |
seconds until the window rolls. On 429 only |
The limit applies to uncounted groups too. Free does not mean unmetered in time — see Quota.
Handling failures well
- Switch on
error. Treat an unrecognised code the way you would treat a500: log it with therequest_idand move on, rather than crashing. - Log
request_idon every failure. It is the only handle support has on your specific request. - Do not retry a
400. Nothing about the request will have changed. - Fail loudly on
quota_exhausted. Silently returning empty results makes a billing problem look like an empty market, and someone downstream will report the zero.