Skip to main content
All API errors use a consistent JSON envelope. The HTTP status code indicates the category of failure; the error field provides a human-readable description.

Response format

Every error response has this shape:
{
  "error": "message describing what went wrong"
}
Successful responses use the same envelope with a data field instead:
{
  "data": { ... }
}
The error and data fields are mutually exclusive — only one will be present.

HTTP status codes

4xx Client errors

CodeNameWhen it occurs
400Bad RequestInvalid request body, missing required fields, or validation failure
401UnauthorizedMissing or invalid session cookie / bot token
403ForbiddenValid credentials but insufficient permissions for the resource
404Not FoundRequested resource does not exist or is not visible to the caller
409ConflictCreating a resource would violate a uniqueness constraint
410GoneResource existed but is intentionally no longer available (e.g. expired invite)
429Too Many RequestsRate limit exceeded

5xx Server errors

CodeNameWhen it occurs
500Internal Server ErrorUnexpected server-side failure

Common error messages

400 Bad Request

{ "error": "invalid request body" }
{ "error": "message must be 1-4000 characters" }
{ "error": "channel name must be 1-64 characters" }
{ "error": "file too large" }
{ "error": "missing file" }
{ "error": "cannot DM yourself" }

401 Unauthorized

Returned when a protected endpoint is accessed without a valid session or token.
{ "error": "unauthorized" }

403 Forbidden

{ "error": "must be a server member" }
{ "error": "only owners and admins can perform this action" }
{ "error": "not your upload" }

404 Not Found

{ "error": "server not found" }
{ "error": "channel not found" }
{ "error": "message not found" }
{ "error": "user not found" }
{ "error": "invite not found" }

409 Conflict

{ "error": "username already taken" }
{ "error": "channel name already exists in this server" }
{ "error": "group name already taken" }

410 Gone

Returned for invite links that are no longer valid. Unlike 404, this means the resource once existed but is intentionally unavailable.
{ "error": "this invite has been revoked" }
{ "error": "this invite has expired" }
{ "error": "this invite has reached its maximum uses" }

429 Too Many Requests

Rate limit exceeded. The response includes a Retry-After: 1 header.
{ "error": "rate limit exceeded" }
See Rate Limiting for limits and retry strategies.

500 Internal Server Error

{ "error": "failed to create server" }
{ "error": "failed to send message" }
{ "error": "failed to save file" }

Handling errors

StatusAction
400Fix the request — do not retry as-is
401Re-authenticate, then retry
403Do not retry — user lacks permission
404Do not retry — resource does not exist
409Resolve the conflict (e.g. pick a different name)
410Do not retry — resource is permanently gone
429Wait then retry with exponential backoff (start at 1 second)
500Retry with exponential backoff
The error string is safe to display directly in a UI. For internationalization, match specific error strings to localized messages.
async function apiRequest<T>(url: string, options?: RequestInit): Promise<T> {
  const res = await fetch(url, {
    ...options,
    headers: { 'Content-Type': 'application/json', ...options?.headers },
  });

  const body = await res.json();

  if (!res.ok) {
    const err = new Error(body.error ?? 'Unknown error') as any;
    err.status = res.status;
    throw err;
  }

  return body.data;
}