Browse docsErrors and limits
Errors and limits
What each kind of failure looks like, and how to retry safely.
Connection errors#
| Response | Meaning | What to do |
|---|---|---|
HTTP 401 with WWW-Authenticate | No token, or the token expired or was revoked | Let your client refresh, or sign in again. |
HTTP 401 for a member who left Gravity | Access ended with their membership | Nothing. The account can't be used. |
HTTP 403 with error="insufficient_scope" | The token lacks the read or edit permission for this operation | Continue within the granted permissions, or authorize the scopes in the challenge if you want the additional capability. |
HTTP 429 with error="rate_limit_exceeded" | The member's read or write limit was exceeded | Respect Retry-After before retrying. No tool in the request ran. |
HTTP 503 with "MCP authorization is temporarily unavailable." | Gravity's sign-in service is down | Try again later. |
Tool errors#
Input validation failures return isError: true with text beginning Input validation error; nothing runs. Unknown tools produce a protocol error. These protocol failures do not use the application error envelope.
Executed-tool errors return { status: "error", code, message, recovery } as both structuredContent and JSON text, with isError: true. Conflicts include currentState when the owned goal is available. Known goal/run IDs may also be included. Messages explain the situation; codes and recovery values are stable.
| Code | Recovery | Meaning and next step |
|---|---|---|
unavailable | resolve_id | An exact group or goal is missing or inaccessible. Resolve from current authorized lists; do not guess IDs. |
version_conflict | read_before_retry | The goal's scope or context revision changed. The edit was rejected without changing wording, private context, or audience. Read current state before preparing a revised action. |
request_id_conflict | new_request | The member already used this request ID with different arguments. Nothing changed. Recover the original action with its original arguments, or use a new ID for a new action. |
clarification_required | clarify_goal | The goal needs clarification before a search can start. Read its question and help answer it; no search started. |
request_rejected | read_before_retry | A goal or search rule rejected the request before changes. Use the message to correct the request after reading current state. |
operation_failed | retry_read for reads; read_before_retry for writes | An unexpected failure left the outcome unconfirmed. Reads can be retried. Read current state before considering another write. |
invalid_response | retry_read for reads; read_before_retry for writes | The application's response did not meet the tool contract. No malformed/private payload is returned. The outcome is unconfirmed; use the same recovery as an unexpected failure. |
An accessible empty list is successful. Missing and inaccessible exact resources both return unavailable, without disclosing which condition applies.
Example conflict:
{
"status": "error",
"code": "version_conflict",
"message": "The goal has changed since this edit was prepared. Review its current state.",
"recovery": "read_before_retry"
}
Retries and request IDs#
Use a fresh UUID requestId per new action. Request identity is scoped to the authenticated member and operation; reuse with different arguments returns request_id_conflict.
A goal save compares only supplied fields. If they already match current state, return no_change with current state and perform no write or search. If an already-applied request would now change the goal, return version_conflict and current state. A supplied stale version also rejects a new edit that would change state. Otherwise apply the patch atomically. There is no historical receipt replay.
An edit's expectedVersion is optional and checks the complete editable state. scopeVersion and contextRevisionId identify matching inputs and support optional narrower preconditions. Omitting a field preserves its current value, including when other actors edit concurrently.
Search requests capture current matching inputs atomically. Repeating the same request reuses its admitted run without another workflow, even if the first admission reused a different request's run. Changed arguments conflict; superseded goal inputs conflict. A separately authorized search retry is a new action.
Goal wording, private details, audience, and visibility commit together. Rejected edits change nothing. MCP saving starts no search, so search failure cannot turn a save into a partial save/search outcome.
Limits#
| Limit | Value |
|---|---|
Goal text | 2,000 characters |
Private details (additionalContext) | 4,000 characters |
| Groups per goal | 100 |
| Running searches per goal | 1 |
| MCP reads per member | 10 per second, shared across clients |
| MCP writes per member | 1 per second, shared across clients |
| Access token lifetime | 1 hour |
Staying connected (offline_access) | Until 90 days without use |
Sign-in endpoints are rate limited. If you see HTTP 429 while signing in, wait a minute and try again.
MCP discovery and transport messages count toward the read limit. Each operation in a batch counts separately; a batch above either limit is rejected before any tool runs. MCP 429 responses include Retry-After: 1.
Each search does real work for every network it covers. Start one only when you're asked, and never in a loop. A full search on a goal that was already searched replaces matches nobody has acted on.
Results are plain JSON. get_goal returns goal state and versions; get_goal_matches returns saved matches. Reads reflect the current search lifecycle, including failed or cancelled background executions, without starting new matching work. If a request is rate limited, respect the returned retry guidance and do not start searches in a loop.
Private note changes#
save_person_notes requires the exact person's current expectedVersion and 1–12 changes. Each saved fact is one single-line claim of at most 280 characters. A rejected batch saves nothing. Notes stay private and saving starts no search. find_people requires a 2–200 character name or email query and returns at most 20 identities from your own Rolodex.
Note conflicts can include personId and noteState with the current owned identity, version, and note history. Re-read before preparing a new action. Reusing a request ID with different arguments returns request_id_conflict. An identical retry returns no_change only while its original effects still hold; an intervening correction or forget returns version_conflict. A new action needs a new ID and current version. Forget removes a fact from active use without restoring a predecessor; retained history is not erased.
Person saves#
get_person reads one exact owned person with an opaque version. save_person requires that version for an edit and uses a member-scoped UUID request ID. Create with a name and how the member knows them. Supplied sections replace whole sections; source basis is required. Email addresses are additive, up to 20 per call. Profile claim sections allow up to 40 single-line claims of 400 characters; summary allows 4,000 characters. Name allows 120 characters; LinkedIn URL 2,048; company and position 200 each; basis 500.
Identity conflicts reject the atomic save; an existing email on creation returns the owned current personState for review rather than merging. A stale version or changed original retry effect returns version_conflict. Changed arguments with an existing ID return request_id_conflict. Review current details and prepare a new action rather than restoring older values. See save_person for clearing and reset semantics.