API Error Codes#
All RAGFlow API error codes are defined in the RetCode enum (common/constants.py), an IntEnum with two categories:
- Application-level codes — RAGFlow-specific integers that appear in the JSON body's
"code"field. - HTTP-mirror codes — values that match standard HTTP status codes (400, 401, 403, 404, 409, 500) and are used as both the JSON body code and the HTTP status in some error paths.
Every API response follows the same JSON envelope, built by get_json_result():
{ "code": <RetCode>, "message": "...", "data": null }
The HTTP status code is not always the same as the body code. Application-level errors (codes < 400) are typically returned with HTTP 200; only UNAUTHORIZED (401) and NOT_FOUND (404) are consistently returned with a matching HTTP status.
Full Code Table#
| Code | Name | Meaning |
|---|---|---|
0 | SUCCESS | Request completed successfully |
10 | NOT_EFFECTIVE | Operation had no effect |
100 | EXCEPTION_ERROR | Unhandled server exception |
101 | ARGUMENT_ERROR | Invalid or missing request argument |
102 | DATA_ERROR | Data not found or invalid state |
103 | OPERATING_ERROR | Operation failed |
105 | CONNECTION_ERROR | External service connection failure |
106 | RUNNING | Operation is still in progress |
108 | PERMISSION_ERROR | Authenticated but insufficient permissions |
109 | AUTHENTICATION_ERROR | Authenticated but resource not owned by caller |
400 | BAD_REQUEST | Malformed request (mirrors HTTP 400) |
401 | UNAUTHORIZED | Missing or invalid credentials (mirrors HTTP 401) |
403 | FORBIDDEN | Access forbidden (mirrors HTTP 403) |
404 | NOT_FOUND | Resource does not exist (mirrors HTTP 404) |
409 | CONFLICT | Resource conflict (mirrors HTTP 409) |
500 | SERVER_ERROR | Internal server error (mirrors HTTP 500) |
401 UNAUTHORIZED vs. 109 AUTHENTICATION_ERROR#
This is the most important semantic distinction in RAGFlow's error model:
| Scenario | HTTP status | Body code | Meaning |
|---|---|---|---|
| No valid credential | 401 | 401 (UNAUTHORIZED) | Token/session missing or unrecognized |
| Valid credential, wrong tenant/owner | 200 | 109 (AUTHENTICATION_ERROR) | Identity confirmed, but resource belongs to another tenant |
tenant_id supplied in request body | 200 | 102 (DATA_ERROR) | Clients must not set tenant_id; server always derives it |
How 401 is produced: login_required calls _load_user(). If no credential is resolved, it raises QuartAuthUnauthorized, which is caught by the app-level 401 error handler and returned as an HTTP 401 with code: 401 in the body.
Special case — AUTH_BETA routes: When a route is decorated with @login_required(auth_types=AUTH_BETA) and authentication fails, the decorator returns HTTP 200 with code: 102 (DATA_ERROR) instead of raising a 401 . This matches the external API convention.
How 109 is produced: After authentication, ownership is verified separately. For chat/session mutation routes (PUT/PATCH/DELETE on /chats/<chat_id> and session sub-routes), _ensure_owned_chat(chat_id) queries DialogService filtering on both tenant_id=current_user.id and id=chat_id. An empty result returns RetCode.AUTHENTICATION_ERROR (109) via get_json_result() with HTTP 200 .
Key Implementation References#
| Concern | File | Key Lines |
|---|---|---|
RetCode enum definition | common/constants.py | 45–61 |
get_json_result() — standard response builder | api/utils/api_utils.py | 252–255 |
get_data_error_result() — DATA_ERROR shorthand | api/utils/api_utils.py | 121–131 |
server_error_response() — global exception handler | api/utils/api_utils.py | 136–153 |
login_required decorator | api/apps/__init__.py | 235–277 |
401 / QuartAuthUnauthorized / WerkzeugUnauthorized error handlers | api/apps/__init__.py | 397–413 |
_ensure_owned_chat() — ownership check returning 109 | api/apps/restful_apis/chat_api.py | 163–164 |
| Example usage of 109 return | api/apps/restful_apis/chat_api.py | 524–526 |