View .md

Rate limits and errors

The FeatherPanel API response envelope, the common error codes, how rate limiting works, which limits are on by default and how admins change them.

On this page

Response envelope

Every /api route answers with the same JSON shape. A success:

{
  "success": true,
  "message": "OK",
  "data": { },
  "error": false,
  "error_message": null,
  "error_code": null
}

An error keeps the same keys and adds an errors array:

{
  "success": false,
  "message": "Invalid API key",
  "data": [],
  "error": true,
  "error_message": "Invalid API key",
  "error_code": "INVALID_API_KEY",
  "errors": [{ "code": "INVALID_API_KEY", "detail": "Invalid API key", "status": 401 }]
}

Branch on error_code, not on message: messages can change, codes are what the code checks for. When a route does not set a status, the panel uses 400.

Common error codes

These come from the middleware that runs before every controller, so you can meet them on almost any route.

CodeStatusWhen
NOT_AUTHENTICATED401No session and no API key.
INVALID_API_KEY401The Bearer token matches no key.
PUBLIC_API_KEY_NOT_ALLOWED401A public key was sent while public-key auth is off.
INVALID_ACCOUNT_TOKEN400The session cookie is invalid or expired.
API_KEY_IP_NOT_ALLOWED403The key has an IP allow list and the request came from elsewhere.
ACCOUNT_DELETED403The account is deleted or scheduled for deletion.
USER_BANNED400The account is banned.
USER_NOT_FOUND404The key’s owner no longer exists.
NOT_AUTHORIZED403Your role lacks the admin permission the route needs.
SERVER_NOT_FOUND404The server UUID or short ID does not exist.
ACCESS_DENIED403You do not own the server or VM and are not a subuser on it.
SERVER_SUSPENDED403The server is suspended.
RATE_LIMITED429See below.

Controllers add their own codes for validation, for example MISSING_REQUIRED_FIELDS, INVALID_DATA_TYPE and INVALID_DATA_LENGTH. The endpoint index lists the routes, and the panel serves its full OpenAPI document at /api/openapi.json.

Rate limiting

When a limit is hit the panel answers 429:

{
  "success": false,
  "message": "You are being rate limited! Retry after 42 seconds.",
  "error_code": "RATE_LIMITED",
  "data": { "error_code": "RATE_LIMITED", "retry_after": 42 }
}

The response also has a Retry-After header in seconds. Wait that long before retrying.

How limits are applied

  • Limits are counted per client IP in Redis. Without Redis the panel falls back to a file-based limiter.
  • Rate limiting is on globally unless an admin turns it off.
  • Each route ships with a suggested limit in code, such as 10 per minute on /api/user/auth/login and 5 per minute on /api/user/auth/register. The endpoint index shows the suggested limit of every route.
  • Only the sign-in routes, the e-mail verification route, the API key routes and the public webhooks enforce their suggested limit by default. All other limits stay off until an admin enables them, so the UI’s polling routes do not start returning 429 after an update.
  • Users whose role has admin.api.bypass_restrictions are never rate limited.

Changing limits

Open Admin > Rate Limits (needs admin.settings.edit). You can switch rate limiting off globally, and give each route a limit per second, minute, hour or day. The page writes to backend/storage/config/ratelimit.json:

{
  "_enabled": true,
  "routes": {
    "login": { "_enabled": true, "per_minute": 10 }
  }
}

Keys are route names (the login route is called login). A route entry with "_enabled": false turns that route’s limit off even if it is one of the enforced defaults. The same changes are available through /api/admin/rate-limits.

Frequently asked questions

Which routes are rate limited out of the box?

Rate limiting is on globally, but only the authentication, API key and public webhook routes enforce their built-in limits by default. Every other route has a suggested limit that an admin can switch on.

Can an integration skip rate limits?

Requests from a user whose role has the admin.api.bypass_restrictions permission are not rate limited.

Where are the limits stored?

In backend/storage/config/ratelimit.json. The Rate Limits page in the admin area edits that file.