Developers › Apps & OAuth

OAuth & MCP errors & limits

Developer Reference OAuth & MCP errors & limits Every refusal from the authorization server and the MCP endpoint, and the rate limits behind a 429. Each carries a stable machine-readable reason alongside the human sentence: branch on the reason. The sentences are rewritten whenever they can be made clearer, so anything that parses them breaks on an improvement.

Two dialects, and which one you get

Two dialects, and which one you get Where it failed Shape The authorization server, /idp/* OAuth 2.0 errors — error plus error_description , in the redirect query string or the token response body (e.g. {"error":"invalid_grant"} ). The resource server, POST /mcp , not authorized HTTP 401/403, a WWW-Authenticate header, and a JSON body carrying error , error_description and error_reason . A 401 carries resource_metadata , which is how a client that has never seen LeadLogiX discovers where to authorize. A tool failing at /mcp A JSON-RPC result with isError: true , inside HTTP 200 — not a transport failure. The ingestion API, /api/v1 RFC 9457 application/problem+json with a code — a different page: Ingestion errors & limits . A failed tool call is not retryable A tool that refuses answers HTTP 200 with isError: true — deliberately, because a 4xx would look like a broken transport and invite a retry. Retry only genuine transport errors and 429 .

Three refusals that are not what they look like

Three refusals that are not what they look like You see It is usually Fix 403 mcp_not_enabled on a token that introspects as valid Application access is off for that workspace An administrator's switch, not your credential — the workspace switch 403 on a call that used to work A scope that shrank under a live token (a role change, a feature turned off) Scopes can shrink ; no_effective_scope when nothing remains invalid_grant on refresh The rotation was already consumed Re-authorize; never retry — refresh rotation

error_reason — the full list

error_reason — the full list Stable tokens. This is what to branch on. error_reason HTTP What to do no_token 401 Send the bearer token. Header only — LeadLogiX does not read it from the query string or the body. token_no_subject 401 The token names nobody. Re-authorize; do not retry. token_expired 401 Refresh, then retry once. token_no_audience 401 The token is not bound to a resource. Send resource on the token request. token_wrong_audience 401 You are calling the other plane — two planes . grant_missing 401 The authorization is gone — revoked, or superseded by a newer consent for the same client. Re-authorize. grant_revoked 401 Re-authorize the user. Somebody disconnected your application deliberately — do not retry, and do not treat it as an outage. grant_suspended 403 Wait. The workspace is suspended. Retrying will not help and re-authorizing will not either. grant_mismatch 401 Token and authorization disagree. Re-authorize. tenant_inoperable 403 The workspace is suspended or scheduled for deletion. Stop calling until it returns. user_missing 403 The account that authorized you no longer exists. Ask someone else to authorize. user_suspended 403 That account is suspended. Another member must authorize. role_unresolved 403 The authorizing account has no recognised role. An administrator must fix it. user_left_tenant 403 That person left the workspace. Somebody still in it must authorize. mcp_not_enabled 403 An operator, not a retry. The workspace has not enabled application access — Connected Apps is the admin's side. no_effective_scope 403 The authorization no longer grants anything, usually after a role change. Re-authorize. grant_revoked and grant_suspended need opposite responses Both arrive as refusals and look identical to a client that only reads error . One means the customer withdrew your access and you must ask again; the other means their workspace is paused and asking again does nothing. A client that treats them alike either spams a consent screen at a suspended account, or quietly stops working after a revocation it never told anyone about.

invalid_grant on refresh means re-authorize

invalid_grant on refresh means re-authorize It is terminal: the refresh token was already used, and replaying it has revoked the authorization. Start a new authorization; never loop on the refresh. The rotation rule is the one part of an OAuth client worth writing by hand.

Rate limits

Rate limits Separate budgets, not one ceiling. Each is counted per authorization — one application, for one person, in one workspace. Calls Limit Note /mcp — reads (leads, tags, CRM, profiles, workspace) 600 requests / minute Sized for an agent working normally — one question commonly becomes twenty calls. Reaching it usually means a loop rather than load. identity_lookup 60 / minute Its own, tighter budget: it is an existence check over somebody else's customer list. automation__<id> 60 / minute Separate from the reads. submit_tool 20 / hour Per authorization. Reading notes — memory_get , memory_list , memory_usage 1,200 a minute Writing notes — memory_put , memory_delete 300 a minute Reading files — file_get , file_list 120 a minute Writing files — file_put , file_delete 60 a minute Search and collection writes — knowledge_search , knowledge_write , knowledge_delete 60 a minute and 3,000 an hour Two clocks, either can refuse you. /idp/register 10 / hour per source Plus a platform-wide ceiling. /idp/token — failed client auth only 120 / 5 min per client per source; 600 / 5 min per client Only invalid_client (401) counts. A stale code, a wrong code_verifier , and every successful exchange or refresh cost nothing — a 429 here means a credential problem, not a traffic problem. Going over returns 429 with Retry-After . Honour it rather than retrying through it — the budgets are per minute, so a client that backs off correctly never notices one. These are the defaults a workspace starts with; LeadLogiX can raise them for a customer, and memory_usage reports the storage limits your connection is actually measured against. Search is the one with two ceilings, and the second is hourly A per-minute budget stops a runaway loop; it cannot stop an application that searches steadily all afternoon. If you consult a collection on every turn of every conversation, consult it once and keep what you learned in a note — notes are the cheapest calls on the surface. Reads and writes fail in opposite directions, on purpose If the limiter itself is unavailable, a read is let through and logged — one extra bounded read by a caller already authorized costs little. A write is refused with RATE_LIMIT_UNAVAILABLE , because a bad minute in the limiter would otherwise admit unbounded permanent writes. Retry it after a pause; it is not a rejection of what you sent. Sizing for a busy workspace These assume the customer connected your application once, under one operations account, and runs everything through it — so the whole business shares one allowance. If you expect more than a few hundred conversations an hour through one connection, say so before you launch: raising a ceiling is a change on our side, far easier to arrange in advance than during a first busy week.