Each plan has a monthly request quota and a per-second rate limit per key. Over quota, requests stop with 429 quota_exceeded until the month ends or you upgrade. There are no overage charges.
Quotas
| Plan | Price | Requests a month |
|---|---|---|
| Free | $0 | 100,000 |
| Starter | $19/month | 1,000,000 |
| Growth | $99/month | 7,500,000 |
| Scale | $299/month | 25,000,000 |
Every metered endpoint counts as one request: search, autocomplete, structured, reverse, nearby, place, TileJSON and tiles. GET /health and OPTIONS preflight requests are free. The month is the calendar month in UTC. Card checkout is not live yet; for a paid plan, email contact@geolith.app.
Every answered request carries two headers:
| Header | Meaning |
|---|---|
X-Quota-Limit | Requests a month on your plan |
X-Quota-Remaining | Requests left this month |
Errors
Key, origin, rate and quota errors are JSON with a stable code:
{ "error": { "code": "missing_api_key", "message": "Pass an API key as ?api_key= or an Authorization: Bearer header." } }
| Status | Code | When | What to do |
|---|---|---|---|
| 400 | (geocoding.errors) | A parameter is missing or invalid | Fix the request |
| 401 | missing_api_key | No key | Send api_key or Authorization: Bearer |
| 401 | invalid_api_key | Malformed, unknown or revoked key | Check the key in the dashboard |
| 403 | origin_not_allowed | Publishable key from an origin it does not list | Add the origin, or use a secret key on a server |
| 404 | not_found | Unknown path or tile set | Check the path |
| 405 | method_not_allowed | Not GET, HEAD or OPTIONS | Use GET |
| 429 | rate_limited | Too many requests per second on one key | Wait for Retry-After (1 s) and retry |
| 429 | quota_exceeded | The monthly quota is used up | Wait for the next month or upgrade |
| 502 | (geocoding.errors) | The index could not be read | Retry later |
Parameter errors keep the GeoJSON shape, so a client that reads features gets an empty list:
{ "geocoding": { "errors": ["missing param 'text'"] }, "type": "FeatureCollection", "features": [] }
Retrying
Retry 429 rate_limited and 502 with a short back-off. Do not retry 400, 401, 403 or 429 quota_exceeded.
async function geolith(url, init, tries = 4) {
for (let i = 0; i < tries; i++) {
const res = await fetch(url, init);
if (res.status === 429) {
const body = await res.clone().json();
if (body.error?.code === 'quota_exceeded') return res;
await new Promise((r) => setTimeout(r, Number(res.headers.get('Retry-After') ?? 1) * 1000));
continue;
}
if (res.status >= 500) {
await new Promise((r) => setTimeout(r, 500 * 2 ** i));
continue;
}
return res;
}
throw new Error('geolith: gave up after retries');
}