API
Errors and limits
HTTP status codes on the Content API, JSON-RPC error codes on MCP, the deprecation headers, and the rate limits that apply to both.
The two surfaces report failure differently, because they speak different protocols. The Content API answers with HTTP status codes; MCP is JSON-RPC, so a failed call is usually an HTTP 200 carrying an error object.
Content API#
Errors come back as { "error": "<message>" } with a matching status.
| Status | Body | What went wrong |
|---|---|---|
400 | Page and limit are required | A category feed was requested without page or limit. |
400 | Lang is required | A category feed was requested without lang. |
401 | Invalid API Key | No credential was sent, or it does not match any key. Check the Authorization: Bearer header. |
403 | Permission denied | The key is real but its permission level does not include reading. Rotate it to a read (2) or read + write (3) key. |
404 | Article not found | No published article matches that handle or article_id. A draft or a future-dated article looks exactly like a missing one from here, on purpose. |
500 | Generic message | Something failed on our side. Retry with backoff. |
An empty array is not an error
Sending no recognized parameter returns [] with a 200. A category feed with no matching articles returns { "articles": [], "has_more": false }, also with a 200. Only a single-article lookup produces a 404.
Deprecation headers#
Passing the API key as ?private_key= or ?api_key= still works, and every such response carries two headers so a client can notice before the form is removed.
Deprecation: true
Warning: 299 - "Passing the API key as a query parameter is deprecated; use the Authorization: Bearer header"The fix is one line: move the key into Authorization: Bearer <key>. See Authentication.
MCP errors#
Two layers can fail, and they are reported differently on purpose.
- The envelope fails -- bad JSON, an unknown method, a missing tool name, an unusable credential. You get a JSON-RPC
errorobject with one of the codes below. - The tool fails -- the article does not exist, the handle is taken, publishing was refused for a missing cover. You get a normal
resultwithisError: trueand a human-readable message in the text content, because the model is meant to read it and correct itself.
| Code | Meaning | Typical cause |
|---|---|---|
-32700 | Parse error | The request body was not valid JSON. |
-32600 | Invalid request | The body was not a JSON-RPC object or batch. |
-32601 | Method not found | A method other than initialize, tools/list, tools/call or ping. |
-32602 | Invalid params | No tool name, an unknown tool, a project_id this connection cannot reach, or a non-scalar argument rejected by the injection guard. |
-32603 | Internal error | An unexpected failure. The detail is suppressed; the call is still logged. |
-32001 | Unauthorized | Missing, expired, revoked or unrecognized credential; or the rate limit. |
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"content": [
{
"type": "text",
"text": "Error: Permission denied: \"delete_article\" needs the \"delete_project_articles\" permission on \"Acme Blog\", and your role there is CONTRIBUTOR. The connection cannot do more than your own account can. Call get_my_capabilities for the full picture of what is allowed here."
}
],
"isError": true
}
}A missing tool is a permission answer
Tools a connection may not use are absent from tools/list rather than present and failing. If an agent reports that a tool "does not exist", the cause is almost always the grant's access level or your role on the project, not a server problem. get_my_capabilities prints the resolved picture.
A 401 on the MCP endpoint#
An unauthenticated MCP request answers with a WWW-Authenticate header pointing at the protected-resource metadata document. That is the standard handshake: a compliant client reads it, finds the authorization server, registers itself and starts the consent flow, all without being configured.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://reblog.so/.well-known/oauth-protected-resource"Rate limits and timeouts#
| Limit | Value | Applies to |
|---|---|---|
| Requests per second | 5 | The MCP endpoint, counted per OAuth grant or per API key. Over the limit, the call is refused and recorded. |
| Request duration | 300 seconds | One MCP request. Long enough for image generation and article writing, which routinely run 90 to 120 seconds. |
| Feed page size | 30 | The Content API limit parameter, capped server-side. |
| Access token | 60 minutes | OAuth access tokens. Clients refresh silently. |
| Refresh token | 60 days | Rotated on every use; replaying a consumed one invalidates the chain. |
| Authorization code | 60 seconds | Single use, PKCE required. |
The rate limit is a per-process sliding window, so it is approximate under heavy parallelism. Treat 5 requests per second as the design target, not a hard ceiling to ride.
Every outcome is logged
Rejected credentials, rate-limited calls and permission refusals all appear in Settings -> MCP activity in the dashboard, next to the calls that succeeded. If something is failing and the reason is not obvious from the client, that page has the server's side of the story.