Errors
Every error code shiply.now returns, its HTTP status, what it means, and what to do next
Every error response — REST, MCP, and site-relative endpoints alike — uses the same envelope:
{ "error": { "code": "not_found", "message": "no site with that slug" } }Site-relative endpoints (/.shiply/data/*, /.shiply/email, proxy routes)
also mirror the message at the top level for older callers:
{ "error": { "code", "message" }, "message": "..." }. Always parse
error.code — it's the stable, machine-checked field. message is
human-readable and may change wording over time.
This page is the canonical reference. Every error response's code appears
in one of the two tables below.
Core codes (/api/v1/*, MCP, and shared by site-relative endpoints)
These come from the shared CoreErrorCode set and apply across the whole
platform — REST, MCP tool errors, and the site-relative endpoints below all
reuse them alongside their own ad-hoc codes.
| Code | HTTP | Meaning | Next action |
|---|---|---|---|
invalid_request | 400 | Malformed body, bad path segment, or a field failed validation. Zod validation errors list every invalid field in one message, not just the first. | Fix the field(s) named in message and retry. |
invalid_transition | 409 | The resource exists but is in the wrong state for this action (e.g. signing an already-signed contract, archiving a closed project). | GET the resource to read its current state, then retry from a valid one. |
unauthorized | 401 | Missing, invalid, or revoked credentials. | Authenticate: Authorization: Bearer shp_..., the device flow (POST /api/v1/auth/device/start), or persist a key to ~/.shiply/credentials. Never "sign in" — this is a machine auth path. |
forbidden | 403 | Authenticated, but not allowed to do this (wrong owner, a non-public Site Data collection, etc). | Use the resource owner's key, or check the resource's access policy. |
not_found | 404 | Unknown slug, id, version, or path. | Verify the identifier; list the parent collection to confirm it exists. |
method_not_allowed | 405 | The /api/v1/* path exists but doesn't support this HTTP verb. | Use one of the verbs in the Allow header (also listed in message). |
conflict | 409 | The request collides with existing state (uploads incomplete, version not pending, handle already taken). | Read message for what's colliding; retry with a different value or after resolving it. |
payment_required | 402 | A plan limit was reached (sites/drives/handles/domains) or the feature is paid-only (analytics, password/invite-only access). | Upgrade at /dashboard/plan, or reduce usage to fit the current plan. |
rate_limit_exceeded | 429 | Too many requests to an /api/v1/* endpoint. | Back off. A Retry-After header (seconds) is set when available. |
quota_exceeded | 422 | A quantity limit was hit that isn't a plan gate (distinct from payment_required). | Reduce the size/scope of the request, or free up quota first. |
service_unavailable | 503 | Transient failure — email delivery, an upstream AI provider not configured, or an unhandled internal error. | Retry with backoff. If it persists, it's worth reporting. |
Site-relative ad-hoc codes
These are emitted only by the site-relative surfaces — Site Data
(/.shiply/data/*), Agent Email capture (/.shiply/email), and Proxy
routes (arbitrary paths declared in .shiply/proxy.json) — layered on top
of the core codes above (invalid_request, forbidden, not_found, etc.
also appear from these endpoints).
| Code | HTTP | Meaning | Next action |
|---|---|---|---|
rate_limited | 429 | Too many requests to a site-relative endpoint (Site Data insert, a proxy route). | Back off; raise limits on a paid plan. |
data_manifest_invalid | 400 | The site's .shiply/data.json manifest failed validation. | Fix the manifest per message and republish. |
collection_full | 409 | A Site Data collection hit its 25,000-record cap. | Delete records, or write to a different collection. |
method_not_allowed | 405 | Wrong HTTP verb for this site-relative endpoint. | Check the documented verb — capture endpoints are POST, most reads are GET. |
bad_request | 400 | Malformed site-relative request — most commonly a .. path-traversal segment in a proxy route path. | Remove the offending path segment and retry. |
body_too_large | 413 | Request body exceeded the 10 MB proxy-route cap. | Shrink the payload. |
proxy_requires_account | 403 | Proxy routes only work on an owned site. | Claim the site (or publish with an API key) first. |
proxy_var_missing | 502 | A ${VAR_NAME} token in .shiply/proxy.json has no matching Variable. | Set it: PUT /api/v1/variables {"name","value"} (or shiply dashboard → Variables). |
proxy_upstream_unreachable | 502 | The proxy route's upstream URL didn't respond. | Check the upstream URL and that the service is reachable over HTTPS. |
data_requires_account | 403 | Site Data only works on an owned site. | Claim the site first. |
email_requires_account | 403 | Agent Email capture (/.shiply/email) only works on an owned site. | Claim the site first. |
A note on rate_limited vs rate_limit_exceeded
This split is intentional, not drift: /api/v1/* endpoints (the owner-auth
REST surface) return rate_limit_exceeded; the site-relative surfaces
(/.shiply/data/*, /.shiply/email, proxy routes) return rate_limited.
Both mean the same thing — too many requests, back off — check for either
code if you talk to both surfaces from the same client.
Machine-readable pointers
Full field-by-field endpoint shapes: /docs/reference ·
/openapi.json. Agent-facing summary:
/llms.txt and
/llms-full.txt.