Errors
Every refusal carries a stable code: act on it, never on the message text, which is written for people and may change. The HTTP status says the class, the code says what happened. Each operation page lists the codes it can return.
Over REST
A JSON body with error.code and error.message; retryAfterMs on 429, details on not_publishable.
HTTP/1.1 409 Conflict
Content-Type: application/json
{
"error": {
"code": "not_publishable",
"message": "Not ready to publish: 1 spot has no photo (Tunnel View)",
"details": {
"blocking": [
{ "code": "missing_photos", "message": "1 spot has no photo (Tunnel View)" }
]
}
}
}Over MCP
A tool result with isError: true and the code at the start of the text. A refused call is never a JSON-RPC protocol error.
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"content": [
{ "type": "text", "text": "Error rate_limited: Too many attempts. Try again later." }
],
"isError": true
}
}400
A required field is missing, a field is unknown, or a value does not match its type (the Convex validator's path and value are in the message).
What to do: Fix the field named in the message. The operation's parameters list every accepted field with its type.
A writer refused a value: a map link on a host that is not allowed, a title that is too long, a date that does not exist, too many items in a list. The message is the writer's own sentence.
What to do: Change the value the message names and send the request again. Do not retry unchanged.
A POST, PUT or PATCH body is not valid JSON, or is JSON but not an object.
What to do: Send a JSON object with Content-Type: application/json.
An upload's bytes are not a JPEG, PNG or WebP file, or their dimensions cannot be read. The bytes are checked, not the Content-Type header.
What to do: Convert the photo to JPEG or WebP and upload it again.
An upload is more than 6,000 px on a side.
What to do: Resize to 2,048 px on the long side and compress before uploading.
tools/call names a tool the server does not have.
What to do: Call tools/list and use a listed name.
401
The request carries no key. Send it as Authorization: Bearer sk_sceniq_... (or X-Api-Key).
What to do: Add the header. MCP clients take it in their server config.
The key is unknown, revoked or expired.
What to do: Ask the creator for a new key (studio, Settings, API keys). Keys are shown once, when they are created.
403
A read-only key called a write operation, or a key without the publish option called publish_changes.
What to do: Ask the creator for a key with the scope the message names. Never work around it.
The action is interactive only: putting a guide on sale, store visibility, archiving a guide, payouts, the account. A key never reaches it.
What to do: Hand the step to the creator with its studio link (get_guide_readiness lists them in handoff).
The key belongs to a creator account that is no longer active.
What to do: Stop and tell the creator; the account needs attention in the studio.
404
The id does not exist or belongs to another creator. The two answers are identical on purpose, so ids never reveal what exists.
What to do: Read the id again from a list operation (list_guides, list_spots, ...).
No operation answers at this method and path.
What to do: Check the path against the reference or the OpenAPI document.
405
The path exists for other methods (the Allow header lists them), or a GET or DELETE reached the MCP endpoint.
What to do: Use a method from the Allow header. The MCP endpoint only takes POST.
409
The row changed after the expectedUpdatedAt you sent (update_chapter, save_sales_page_draft).
What to do: Read the row again, apply your change to the new version, and send it with the new updatedAt.
delete_spot on a guide someone bought. Buyers keep what they paid for.
What to do: Archive the spot instead: update_spot with archived true.
The request clashes with the row's current state. The message says what to change.
What to do: Read the row again and follow the message.
publish_changes while a gate is open: the agreement, no live spot, a live spot without a pin or a photo, no price. details.blocking lists each gate as { code, message }.
What to do: Fix each blocking item (get_publish_status lists them), then publish only if the creator asks.
publish_changes on a draft or archived guide. A draft's first publish happens in the studio.
What to do: Hand the creator the reviewUrl; putting a guide on sale is their click.
publish_changes when buyers already see every change.
What to do: Nothing to do. get_publish_status shows the live version.
A price change or a publish before the creator accepted the current creator agreement.
What to do: Ask the creator to accept it in the studio, then try again.
413
A JSON body over 2 MB, an image over 6 MB, a batch upload over 19 MB, or an MCP message over 2 MB.
What to do: Split the request (upsert_spots takes 25 spots per call) or compress the photo.
429
Over a limit: requests per key per minute, per creator per hour, uploads per key per minute, or check_links per guide per hour.
What to do: Wait for Retry-After (seconds) or retryAfterMs, then continue. Do not retry in a tight loop.
500
An unexpected failure. The message is hidden on purpose.
What to do: Try once more after a short wait. If it repeats, tell the creator and write to creators@sceniq.earth with the time and the operation.
MCP protocol errors
A malformed message is answered as JSON-RPC: -32700 (not JSON), -32600 (not a JSON-RPC 2.0 message), -32601 (a method the server does not offer: it offers initialize, ping, tools/list and tools/call), -32602 (tools/call without a known tool name). A missing or invalid key answers HTTP 401 with -32000 and the error code in error.data.code.
The SCN- reference beside each code is what the Sceniq apps show people for the same class of error; quote it when writing to creators@sceniq.earth.