Rate limits
The API throttles in two ways, both enforced per API key: a per-minute rate limit on every request, and daily quotas on three write operations.
The per-minute rate limit
- Default: 30 requests per minute.
- Per-key override: a key can be issued with a higher (or lower) limit. Read a key’s
effective limit from
rate_limitinGET /v1/me. - One limit covers all
/v1/*endpoints. A bulk import is a single request that kicks off an async job, so it counts as one — not one per row. - The window is a fixed one-minute bucket per key, shared consistently across all ZeeMaps servers.
Response headers
Every response includes the current limit state:
| Header | Meaning |
|---|---|
X-RateLimit-Limit |
Requests allowed per minute for this key. |
X-RateLimit-Remaining |
Requests left in the current window. |
X-RateLimit-Reset |
Unix timestamp (seconds) when the window resets. |
HTTP/1.1 200 OK
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 1782403260
When you exceed the limit
The API returns 429 RATE_LIMITED with a Retry-After header (seconds until the window
resets):
HTTP/1.1 429 Too Many Requests
Retry-After: 24
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1782403260
{ "error": { "code": "RATE_LIMITED", "message": "Rate limit exceeded. Retry after 24s." } }
Daily quotas
Separately from the per-minute limit, three write operations are capped per key, per UTC day:
| Operation | Endpoint | Default per day |
|---|---|---|
| Map creates | POST /v1/maps |
10 |
| Single marker adds | POST /v1/maps/{map_id}/markers |
1,000 |
| Bulk imports | POST /v1/maps/{map_id}/markers/bulk |
10 |
- Only these three operations are quota’d. Reads, updates, and deletes never count against a daily quota.
- Quotas reset at UTC midnight. Counters are kept per UTC calendar day.
- A bulk import counts as 1 bulk unit no matter how many rows it carries. A single bulk import of 10,000 rows spends 1 of the day’s 10 bulk units — not 10,000 marker units — which makes bulk import the intended path for large ingests.
- Per-key override: like
rate_limit, each quota can be raised (or lowered) for a specific key — contact support if you need higher limits.
Read your current standing at any time from
GET /v1/me, which returns
a quotas object with each operation’s effective limit and today’s (UTC) usage:
"quotas": {
"maps": { "limit": 10, "used": 2 },
"markers": { "limit": 1000, "used": 148 },
"bulk": { "limit": 10, "used": 0 }
}
When you exceed a quota
The API returns 429 with error.code = QUOTA_EXCEEDED — distinct from
RATE_LIMITED, so a client can tell “pause a few seconds” from “done with this write
until tomorrow”. Retry-After is the number of seconds until the next UTC midnight,
which can be many hours — don’t spin-retry a quota 429:
HTTP/1.1 429 Too Many Requests
Retry-After: 20516
{ "error": { "code": "QUOTA_EXCEEDED", "message": "Daily quota exceeded for bulk imports (10 per key per day). The quota resets at UTC midnight." } }
Backoff guidance
- Honor
Retry-After. On a429, wait at least that many seconds before retrying. Checkerror.codefirst: onQUOTA_EXCEEDEDthe wait runs to UTC midnight, so queue the work rather than sleeping on it. - Watch
X-RateLimit-Remainingand slow down as it approaches0instead of sprinting into a429. - Use exponential backoff with jitter for repeated
429s (e.g. 1s, 2s, 4s, 8s, each with a small random offset) so concurrent clients don’t retry in lockstep. - Serialize or pace bursts. For large jobs, prefer a single bulk import over thousands of single-marker calls — it’s one request against your rate limit, one unit of the daily bulk quota, and far faster.
Related
- Errors for the full status-code table.
- Pagination — paging large result sets without burning your quota.