Developer docs: all pages

Developers · MCP server

Errors and limits

Every way PorchQ refuses or limits a call, what each answer looks like, and how to retry safely.

How PorchQ answers when something is wrong

HTTP Where Meaning What to do
400 JSON-RPC error The request breaks the protocol: a header that doesn’t match the body, a missing _meta field, an unsupported version. Fix the request; see Protocol.
401 WWW-Authenticate No token, or one that isn’t valid (error="invalid_token"). Refresh, or sign the person in again.
403 WWW-Authenticate The connection lacks a scope (error="insufficient_scope"), or a web page’s Origin isn’t allowed. Ask for the scope named in the header.
404 JSON-RPC error Unknown method, revision 2026-07-28.  
405 Allow: POST GET or DELETE on /mcp. Use POST.
429 JSON body, Retry-After Too many requests. Wait the seconds in Retry-After.
200 result.isError: true The tool ran into a problem it can explain. Show the sentence to the person, or correct the arguments.

A tool call without a token

tools/call without a valid token is answered 401 with the WWW-Authenticate header and also a result that ChatGPT reads:

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "isError": true,
    "content": [{ "type": "text", "text": "Connect PorchQ via OAuth to run this tool." }],
    "_meta": { "mcp/www_authenticate": "Bearer realm=\"porchq\", resource_metadata=\"…\", scope=\"…\", error=\"invalid_token\", error_description=\"Connect PorchQ to use this tool.\"" },
    "resultType": "complete"
  }
}

A missing scope is answered the same way with 403 and error="insufficient_scope".

Tool errors

A tool that can’t do what was asked answers HTTP 200 with isError: true, one sentence in content, and, for a refusal, structuredContent.error with a code:

{
  "isError": true,
  "content": [{ "type": "text", "text": "You belong to more than one PorchQ community. Which one do you mean: Evergreen Estates or Sunset Hills?" }],
  "structuredContent": {
    "error": {
      "code": "property_required",
      "message": "…",
      "retryable": false,
      "communities": [
        { "property_id": "prop_web9z8y7x6w5v4u3t2s1r0q9p8o", "property_name": "Evergreen Estates" },
        { "property_id": "prop_web4k2m9q7x1v8c3n5b6z0a2s4d", "property_name": "Sunset Hills" }
      ]
    }
  }
}
Code Means
property_required The connection reaches more than one community and the call named none. The sentence names them, so an assistant can ask the person as it is; communities has their ids.
not_found The community, or the record the call names, isn’t one this connection can see.
forbidden_role The person’s role there can’t use this tool.
conflict The change can’t be made as asked.
unavailable A service PorchQ depends on is down; retryable is true.

These are the sentences to expect:

Sentence Why
This action is available only to owner and manager roles. The person is a resident in that community (forbidden_role).
You belong to more than one PorchQ community. Which one do you mean: … See property_required above.
Requested resource was not found. The property_id, event_id or meeting_note_id doesn’t exist, or isn’t in a community this connection reaches.
No property membership found for this user. The person has no role in any community the connection reaches.
Invalid tool arguments. Provide a valid JSON object. arguments isn’t a JSON object.
A validation message, such as “Title can’t be blank” PorchQ refused the change, for the same reason it would in the app.
Tool execution failed unexpectedly. Please retry. Something failed on PorchQ’s side. It is logged; retrying with the same idempotency_key is safe.

A community the connection doesn’t reach is answered exactly like one that doesn’t exist, so an app can’t learn which communities exist.

Rate limits

PorchQ counts requests at its edge and answers 429 past these limits:

What is counted Limit Per
Calls to /mcp per access token 120 minute
Calls to /mcp per access token 5,000 day
Calls to /mcp without a token, per address 120 minute
Token, revoke and introspect requests, per address 300 minute
Consent page loads, per address 30 minute
Client registrations, per address 60 hour

ChatGPT, Claude and other assistants call PorchQ from a few shared server addresses, so /mcp is counted per access token, not per address.

A 429 looks like this:

HTTP/2 429
Retry-After: 23
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1792188123
Content-Type: application/json

{"error":"Too many requests to PorchQ's MCP server — try again in a few seconds. Please slow down.","code":"rate_limited","retry_after":23}

Limits on changes

Separately, each person can make at most 30 changes a minute and 500 a day through connected apps, all apps together. Reading is not counted. Past the limit, the tool answers (HTTP 200):

{
  "isError": true,
  "content": [{ "type": "text", "text": "Too many changes through connected apps for now. Try again in 41 seconds." }],
  "structuredContent": { "error": { "code": "rate_limited", "message": "…", "retryable": true, "retry_after_seconds": 41 } }
}

Idempotency keys

Every tool that changes something accepts an optional idempotency_key argument: any string up to 200 characters. Use a new random value (a UUID) for each change the person asked for, and send the same value again when you retry it.

  • A key is remembered per app and per person. A second call with a key already used does nothing and answers:
{
  "content": [{ "type": "text", "text": "This request was already handled at 2026-11-02T18:04:11Z. Nothing was done again." }],
  "structuredContent": { "status": "duplicate", "original_call": { "id": "mcpcall_web…", "at": "2026-11-02T18:04:11Z", "outcome": "completed" } }
}
  • Two copies of one request racing each other can’t both act: one does, the other is answered as a duplicate.
  • Without a key, every call acts. Retry a change without a key only when you know it didn’t happen.

The record of calls

PorchQ records every tool call: the person, the app, the tool, the community, its effect, when it started and ended, how it ended (completed, awaiting_confirmation, tool_error, refused, duplicate or rate_limited), and the IP address the call came from. Free text is never kept: titles, descriptions, bodies and search words are stored as [removed]; only ids, settings and numbers are kept. See Security and data.

This page as Markdown: /developers/mcp/errors-and-limits.md

Loading PorchQ…