Rate limits & errors Pro
Rate limits
The API is a Pro feature. Requests are rate limited per workspace (all of a workspace's tokens share one quota - minting more doesn't raise it), and MCP tool calls count against the same quota:
Writes are held about ten times tighter than reads. A write is the expensive side: updating a map replaces its whole node and edge set, so one call rewrites many rows. It is also the destructive side. Nothing legitimate needs write volume - importing a repo is a single create, an editing session is a handful - so the ceiling is set well above real use and well below what a looping client can burn through. This mirrors how GitHub caps content-generating requests far below its general budget.
MCP tools draw from the same two buckets: the read tools count as
reads, and create_map / update_map / delete_map count as writes. The
same operation costs the same whether it arrives over REST or MCP.
The per-minute and burst limits cap how fast you can call; the daily total
caps overall volume and resets daily at 00:00 UTC - the same instant for
every workspace. Hitting the daily cap returns 429 with
"Daily request quota exceeded." and a Retry-After counting down to midnight
UTC. Running into these ceilings at scale? Talk to support and we'll
raise them.
Every response carries the current limit state in its headers, so you can back off before you're throttled:
When you exceed the limit the API responds with 429 Too Many Requests and a
JSON body describing when to retry:
{
"error": "rate_limited",
"message": "Rate limit exceeded. Retry after 41s.",
"retryAfter": 41
}Honor the Retry-After header with exponential backoff and jitter rather than
retrying immediately - tight retry loops just burn your remaining budget.
Batch writes where you can.
You can watch today's usage (REST API vs MCP, split out) and the daily history in Dashboard → Settings → Workspace → API.
Embeds are not rate limited
Embedded maps are a public, cached, credential-free surface - they carry no token, so they never touch these quotas. A widely-embedded map costs you nothing against your API limits.
Abuse protection & auto-disable
A 429 is not a penalty - it just means "slow down." A well-behaved client that
honors Retry-After is never punished. But a client that keeps hammering
through its own 429s at a high sustained rate (ignoring Retry-After) is
treated as abuse, and the token is automatically disabled.
- Detection is rate-based: it only trips on a sustained flood of requests sent while already rate-limited (on the order of tens per second). Normal usage, and even an occasional retry after a limit, never trigger it.
- A disabled token stops authenticating immediately (
401 invalid_token), exactly like a revoked one. - There is no re-enable. The token shows as Disabled in Settings → Workspace → API with the reason; the fix is to delete it and create a new one. (Rotating is also the right move if a token has leaked - the disable usually means someone is misusing it.)
Honor the Retry-After header with exponential backoff and jitter. Tight
retry loops don't just burn your budget - sustained ones will get the token
disabled.
Log retention
Each request (REST and MCP) is recorded to a per-workspace activity log, surfaced as Recent requests on the API page: what ran (the endpoint, or the MCP tool name), which map it touched, who authorized it, the status, and how long it took. Logs are retained for 30 days, then automatically deleted, and can be filtered and exported as CSV. The log is for observability only; it's never used for billing.
Two things it deliberately doesn't capture: rejected sign-ins (a 401 never
reaches the log) and requests turned away during a burst (those are refused
before any database work, which is what keeps a flood cheap). Logging is also
best-effort, so a database blip can drop a row. A quiet log is not proof that
nothing happened - for abuse specifically, rely on the auto-disable behaviour
above rather than on log volume.
Errors
Every error shares the same JSON envelope - a machine-readable error code, a
human message, and sometimes extra fields (fields, limit, retryAfter).
Always branch on the HTTP status and the error code, never on message text.
{
"error": "invalid_token",
"message": "The API token is invalid, expired, or revoked."
}A 500 carries an X-Request-Id header - include it when you contact support so
we can trace the failure. These are transient; retry with backoff.