Responses and errors
The shape of a tool result, how failures come back, and every error code you can act on.
API v1
A successful call
Every tools/call answers with one text block holding the tool's result as JSON:
Parse content[0].text as JSON to get the result. The tool reference shows each tool's inputs; results are described in each tool's text.
Every response also carries two headers:
| Header | Meaning |
|---|---|
Pink-Version | The API version that answered |
Pink-Request-Id | This request's id. Quote it when you contact support. |
Three kinds of failure
| Kind | How it arrives | Example |
|---|---|---|
| The request was refused before it reached the server's JSON-RPC handling | An HTTP error status with a small JSON body | 401 without a valid key |
| A tool call failed | HTTP 200, a normal JSON-RPC result with isError: true | Out of credits, a bad argument, a cap reached |
| The JSON-RPC message was malformed | A JSON-RPC error object | Unknown method |
Tool failures are results, not protocol errors, so the model sees them and can act: retry with a fixed argument, pick another site, wait and try again.
Refused requests (HTTP)
| Status | Body error | Meaning |
|---|---|---|
401 | unauthorized | No credential. The WWW-Authenticate header points to the sign-in; see Authentication. |
401 | invalid_token | The key is unknown, revoked or expired. |
503 | temporarily_unavailable | The key could not be checked just now. Retry with the same key. |
400 | JSON-RPC -32600 | Unsupported MCP-Protocol-Version header, an empty batch, or not a JSON-RPC message |
400 | JSON-RPC -32700 | The body is not valid JSON |
Failed tool calls
A tool that rejects a call returns a result with an error code, a message and, for some codes, more fields:
Account and access failures come back as plain text in the same isError result, ending with the code:
Either way, branch on the code, not the message: messages are written for the model and may change.
Malformed JSON-RPC
| Code | Meaning |
|---|---|
-32700 | Parse error |
-32600 | Invalid request |
-32601 | Method not found |
-32602 | Invalid params, such as a tools/call without a tool name, or an unknown prompt |
-32000 | The server failed while handling the request |
Error codes
Access and account
| Code | Meaning | What to do |
|---|---|---|
unauthenticated | No usable credential reached the tool | Sign in again, or check the key |
site_not_found | select_site named a site that does not exist | list_sites, then select a listed id |
site_access_revoked | You are not an owner or member of that site | list_sites; ask the owner to invite you |
pinned_site_invalid | The bound site is gone or you lost access to it | list_sites, then select_site |
site_read_only | This session may read the site but not change it | Ask the owner to add you as a member |
credits_exhausted | The site owner's balance is below the call's price | Top up on the Billing page, or turn on auto-recharge there |
rate_limited | Over the per-minute or daily request limit. Carries retry_after_ms. | Wait retry_after_ms, then retry |
unsupported_api_version | The Pink-Version header or api_version value is not one the server speaks. Lists the supported versions. | Use a listed version, or send none for v1 |
Tool input and state
| Code | Meaning | Extra fields |
|---|---|---|
bad_input | An argument is missing, of the wrong type, not an allowed value, or over a documented cap | allowed[] when the argument is a fixed list; code names the rule, e.g. slug_taken |
bad_input with code: "slug_taken" | On a Pink-hosted site, this site stored a page at the same address a moment ago (two publishes of one new page ran at once). Slugs are unique per site, so another site's page never causes it. Publish again to update the page. | suggested_slug (articles; may be null) |
not_found | The page, question, topic or row you named doesn't exist on this site | |
missing_keyword_binding | db_insert of a topic or page without keyword_ids | Get keyword ids with get_keywords first |
unknown_keyword_ids | keyword_ids that don't exist | |
column_not_writable | db_insert / db_update on a column the tools may not write, such as published | Use the matching tool (add_article, set_article) |
use_add_competitor_tool | db_insert into the competitors table | Use add_competitor |
delete_blocked_published | db_delete of a published page or question | Unpublish with set_article / set_answer status: "draft" first |
not_undoable | undo found nothing it can reverse | |
no_copy | set_article / set_answer without copy, on a page or answer that has no published prose to re-publish | First publish with add_article; for an answer, send copy |
no_research | add_answer on a question that has not been researched | Research it first |
no_context | The page's context could not be loaded | Retry; contact support with the request id if it persists |
Caps and upstream data
| Code | Meaning | Extra fields |
|---|---|---|
concurrency_limit_exceeded | Too many web, reddit or URL research calls are running at once across Pink | max_concurrent, retry_after_ms, current_count |
daily_refresh_cap_reached | serp_keyword_refresh used its daily per-site allowance | cap |
gsc_not_connected | The site has no Search Console property linked | Link one in Pink Editor |
gsc_bq_not_configured | gsc_hidden_winners / gsc_forecast need the BigQuery bulk export | Set it up in Pink Editor |
backlink_fetch_failed | Backlink data could not be fetched | Nothing is charged; retry later |
Calls refused with bad_input, not_found, concurrency_limit_exceeded, daily_refresh_cap_reached, gsc_not_connected, gsc_bq_not_configured, no_copy, no_research or no_context are refunded in full. See Credits.
Other codes can appear for faults on our side (for example db_error). Treat any code you don't recognise as a failure, show the message, and quote Pink-Request-Id if you contact us.