PageWeave / Docs

API

Markdown

API errors

RFC 9457 application/problem+json for every non-2xx response.

Status map

Status type suffix When
400 bad-request Malformed request, unknown or non-scalar query parameter, unknown body parameter, invalid/expired cursor, invalid Idempotency-Key, invalid limit
401 unauthorized Missing or invalid credential; the response carries WWW-Authenticate with error="invalid_token" when a credential was presented
403 forbidden Authenticated but not permitted (body carries required_permission / required_permission_scope, plus required_credential_scope when a bearer credential lacks the needed scope)
404 not-found Unknown resource, or a resource outside the caller's tenancy
405 method-not-allowed Wrong method for a known API path; the response carries Allow, and OPTIONS on a known path answers 204 with Allow
409 conflict A request with the same Idempotency-Key is in flight (Retry-After), state conflict (role still assigned, environment in use)
410 gone Endpoint past its sunset date; keeps the Deprecation/Sunset/Link headers
412 precondition-failed If-Match did not match
413 content-too-large Upload exceeds the size limit (RFC 9110's "Content Too Large")
415 unsupported-media-type Wrong request content type
422 validation Body or query failed validation (errors[] enumerated)
422 unprocessable Semantically invalid request that is not field validation (RFC 9110's "Unprocessable Content"); also a reused Idempotency-Key with a different payload
429 too-many-requests Rate limit exceeded (Retry-After, RateLimit-Policy, RateLimit in draft-11 syntax)
500 internal-error Unhandled server error
503 service-unavailable Upstream provider unavailable

Body

{
  "type": "https://pageweave.dev/errors/forbidden",
  "title": "Forbidden",
  "status": 403,
  "detail": "Insufficient permissions",
  "instance": "/api/v1/websites/acme/pages",
  "request_id": "01a1b2c3-...",
  "required_permission": "page.content.update",
  "required_permission_scope": "website"
}
  • type is a stable URI identifying the error class (https://pageweave.dev/errors/<suffix>); clients branch on it, not on detail. Dereferencing one redirects to the API error reference (/docs/api/errors).
  • detail is human-readable and may change.
  • instance is the request path that produced the problem.
  • request_id matches the X-Request-Id response header and the server log line.
  • errors[] appears only for validation problems: { "field": "...", "detail": "..." }. field names the request body key (path, source_path, ...), or base when the model reports an error that belongs to no single field.
  • required_permission / required_permission_scope name the endpoint's declared permission and the context its assignment is resolved in (workspace, website, environment). This is not a credential scope. Account-level endpoints (for example POST /websites and API-key management) declare no resource permission, so both fields are null; required_credential_scope is the field to act on there.
  • required_credential_scope appears only when a bearer credential is present but lacks the read/write scope the endpoint needs — check this first when a request that used to work starts returning 403.
  • An unverified account is a separate 403: the body detail is Email verification is required and it carries no required_permission/required_credential_scope. It applies to a second website, publishing one, and website access invitations; API keys and the first non-indexable website remain available. The account check runs before the permission and credential checks, so an unverified caller sees this 403 even when they also lack the permission or the write scope. An invalid body still wins: request validation runs before authorization, so a malformed request is 400, not the verification 403.
  • Self-edit guards are 403: changing your own membership/access role or granting yourself access. Revoking your own website/environment grant is allowed without the manage permission (de-escalation). Workspace self-removal uses the leave endpoint.
  • Direct website grants and email invitations differ by design: a direct grant may override a member's implicit website role; an invitation to an address that already has access is 422 (no silent demotion).
  • 409 also covers a custom role still assigned to a membership or access, a blocked website/workspace teardown, restoring a page or snippet whose path is now taken, deleting a table a live template page still uses, a release whose mine-scope items were edited by someone else, and a domain cancellation that is already scheduled, no longer undoable, or whose deletion is under way. Domain-specific conflicts: a pending purchase for the same domain with a different period, a registrar deletion already under way or already confirmed and scheduled, and a renewal charge in flight during confirmation. Structured extensions: suggested_path on a restore (the first free path, never for template paths; nothing is renamed automatically), template_pages: [{id, path}] on a table delete, and conflicts: [{identifier, mine_version, latest_version, latest_author}] on a release conflict. A refused DNS zone deletion adds nameservers: ["ns1.pageweave.dev", ...] — the live parent answer that still delegates the name to PageWeave.
  • 422 covers an unknown role key for the context, an unknown email target for a direct grant, the last owner leaving/demoting, provider sync validation that failed before any registry call, an ambiguous visitor-feedback short id (matches: [short_id, ...]), and invalid release selections (items names the offending identifiers). For content it covers HTML/head validation, a duplicate page/snippet path, an unknown table slug, an invalid theme config, a redirect source that collides with a page or homepage, and a page title on a versionless page. For domains it covers an invalid, unsupported, or indeterminate availability answer, settings or lifecycle commands on an unregistered (released) row, and a missing registrant handle. Purchases add an invalid name, a taken or premium name, a TLD-disallowed period, a test-mode workspace, an unknown or mismatched workspace/website, and a different period for an already pending purchase. Deletion adds a confirmation that does not match the exact name, an unregistered (released) domain, and an invalid, expired, superseded, or already-used token (the token is never echoed in the problem body). DNS adds an invalid, duplicate, or subdomain zone name, a name registered by another workspace, the zone allowance, an unknown or mismatched workspace/website, a provider rejection, an unknown per-type record field, a missing structural record field, an exact duplicate record, an apex NS record, a CNAME at the apex, and an edit/delete of the provider-managed SOA or apex NS records. A record id the zone does not hold is 404 — the id is the provider's, not a local row.
  • Content requests are strict about types and keys: an undeclared body key, a non-string html_content/path/content, a non-boolean indexable, a non-object conditions/placeholder_defaults, an unsupported redirect status_code, an empty PATCH body, an empty or oversized replacement list, or overwrite plus replacements for the same field is 400 before the service runs.
  • 503 covers a registrant-contact, billing-portal, registrar, or DNS provider failure (zone create/delete, record reads and writes, and a provider that answers with an authentication or throttling failure rather than rejecting the request). A domain settings or cancellation sync that fails leaves the local row unchanged; availability and auth-code fetches change nothing. A failed purchase start and a failed deletion confirmation both answer 503 without leaving a claimed token behind — retry the same request/link. Re-read before retrying.
  • Unhandled errors are reported to the error tracker and rendered as internal-error, never as the framework's default body.
  • Query-string and cursor errors carry the offending parameter or a cursor message in detail; clients recover by dropping the parameter or restarting from the first page.
  • detail strings are English regardless of the account language; clients branch on type and the structured fields.

Cross-tenant access is always 404, never 403: the API does not confirm that a resource exists outside the caller's scope.