Skip to content
Fermium

Errors

Status codes and JSON shapes from the REST API, the MCP server, the content host and the OAuth endpoints.

Fermium has a small number of error shapes. Which one you get depends on which part of the server produced it.

REST API

Service errors (validation, not found, quota) come as:

{ "error": "document not found" }

Framework errors (authentication, request validation, rate limits raised by routes) come as:

{ "detail": "authentication required" }

Request-body validation failures return 422 with detail as a list of field errors.

Status Meaning Examples
400 invalid request visibility must be one of ('unlisted', 'password', 'workspace'), password visibility requires a password, retention_days must be >= 0 (0 = keep forever), invalid tag 'X': ..., bad path '...', file type .exe not allowed; allowed: ..., doc is a snippet; use update_snippet, use a subdomain (docs.yourcompany.com), not your apex domain, gmail.com is a public email provider and cannot be a viewer domain, alias must be a lowercase slug: ..., old_string matches 3 times in 'source.md'; ..., hunk 2 in 'source.md' failed at line 41: expected '...', found '...', workspace policy does not allow visibility 'unlisted'; allowed: workspace, workspace policy caps retention at 365 days; 'keep forever' (0) is not allowed, this content looks like it contains credentials, and your workspace policy blocks publishing secrets: ..., webhook url must be publicly reachable; ..., type your email address to confirm account deletion
401 no valid session or token authentication required
402 plan does not allow it visibility 'workspace' (sign-in gate) needs a Team plan or above; upgrade at https://fermium.app/pricing ..., custom domains need a Team plan or above ..., your plan allows 1 custom domain(s); remove one or upgrade, workspace policy requires the Business plan or above, the audit log requires the Business plan or above, webhooks requires the Business plan or above, legal hold requires the Enterprise plan, the IP allowlist requires the Enterprise plan, the team plan allows 25 members and 24 members plus 1 pending invites already fill it; ..., SCIM provisioning is an Enterprise feature; ...
403 forbidden cross-origin request rejected, invalid or expired upload ticket, admin only, only the document owner can change its visibility (an editor trying an owner-only change), only the document owner or a workspace admin can share it, workspace owner or admin role required, this action requires the owner role, groups and group grants need a Team plan or above; ..., comments need a paid plan (Team or above); ..., you can read this document but not comment on it, your workspace only allows access from approved networks; <ip> is not on the allowlist, this invite was sent to bo@acme.com; sign in with that account to accept it
404 not found or not yours document not found, token not found, domain not found, no billing account yet; start a plan first, grant not found, group not found, 'bo@acme.com' is not a member of this workspace, no file at 'notes.md', doc has no version 9
409 conflict that hostname is already registered, acme.com is already claimed by another workspace, alias 'q3-roadmap' already points at '...' (01J7…); pass force=true to move it, bo@acme.com is already a member of this workspace, bo@acme.com already has a pending invite, a group with slug 'design' already exists, you own a workspace with other members; transfer ownership before deleting your account, acme.com is already used for single sign-on by another workspace
410 gone this invite has expired or was already used
413 too large or over quota request body too large, storage quota exceeded: ..., doc limit reached (5000 docs per user), file 'x' is N bytes; limit is 20971520, body exceeds the 2147483648-byte upload limit
423 locked this document is under legal hold and cannot be deleted; an admin must release the hold first
429 rate limited publish rate limit exceeded, too many requests, too many attempts
503 dependency not configured or unavailable billing is not configured, readiness check degraded

Documents that exist but belong to another account return 404, not 403, so the API never confirms that a document exists. Someone who holds an access grant on a document and tries an owner-only change gets the 403 instead, since they already know the document exists.

Endpoints under /scim/v2 return the SCIM error schema (application/scim+json with schemas, status, scimType, detail) instead; see SCIM.

MCP tools

A failed tool call returns an MCP tool error whose message is the same text the REST API would put in error. There is no separate status code; the agent reads the message and acts on it. The messages are written for that: they name the limit and, where an upgrade would help, include the pricing URL.

publish rate limit exceeded (120/hour); slow down and retry later
storage quota exceeded: 1 GiB on the free plan; free space by deleting docs or upgrade at https://fermium.app/pricing
upload visibility must be 'unlisted' or 'workspace'; for a password doc, upload unlisted then call set_visibility
provide exactly one of 'content' or 'content_base64'
content_base64 is not valid base64
decoded content would exceed the 20971520-byte file cap
authentication lost; retry the request

The last one means the token was valid when the request started but the account was disabled or the token revoked in between; a retry gets a clean 401.

Unauthenticated MCP requests

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://fermium.app/.well-known/oauth-protected-resource/mcp", error="invalid_token"
Content-Type: application/json

{"error":"unauthorized","detail":"authenticate via the MCP OAuth flow (your client should open a browser), or supply 'Authorization: Bearer dsk_...'"}

error="invalid_token" is present only when a token was sent. Clients use the resource_metadata URL to start OAuth; see Authentication.

OAuth endpoints

RFC 6749 shape, with CORS headers and Cache-Control: no-store:

{ "error": "invalid_grant", "error_description": "authorization code is invalid or expired" }
Status error
400 invalid_client_metadata, invalid_redirect_uri, invalid_request, invalid_grant, unsupported_grant_type
401 invalid_client
429 temporarily_unavailable

At /oauth/authorize, an unknown client_id or unregistered redirect_uri is a plain 400 with detail; the server never redirects to an unverified URL. Other authorize errors (unsupported_response_type, invalid_request, access_denied) are delivered as query parameters on the registered redirect_uri.

The content host

Readers see as little as possible.

Status When Body
404 missing, expired or deleted document; a path not in the document; another workspace's document on a custom domain a neutral HTML page, "This link isn't available", for browsers; {"error":"not found"} otherwise
401 password document without or with wrong credentials empty, with WWW-Authenticate: Basic realm="fermium-<slug>"
302 workspace document without a viewer cookie redirect to https://fermium.app/viewer/authorize
403 signed-in reader outside the publisher's workspace {"detail":"this document is limited to members of the publisher's workspace"} from the app host
403 workspace document read from outside an Enterprise IP allowlist {"error":"this document is restricted to approved networks"}
302 /x/<ref> that resolves redirect to /d/<slug>/<path> on the same host; an unresolvable reference is the neutral 404 page
416 unsatisfiable Range empty, with Content-Range: bytes */<size>
429 too many 404s or too many password failures from one IP {"error":"too many requests"} or {"error":"too many attempts"}
304 If-None-Match matches an unlisted file's ETag empty

Anything other than /d/*, /x/*, health probes and two shared assets on the content host is a 404 with {"error":"not found"}.

The upload endpoints

PUT /u/<ticket> and PUT /api/upload use detail:

Status Detail
400 request body is empty, bad content-length, client disconnected, query parameter 'filename' required
401 authentication required (/api/upload only)
403 invalid or expired upload ticket, uploader account unavailable
413 body is N bytes; upload limit is 2147483648, or the storage quota message
429 publish rate limit exceeded, too many requests

If a quota check fails after the bytes have landed, the orphaned object is deleted before the error is returned.