From 18966681a79d10c1a1a4bf7fa6ea7c45f5d81701 Mon Sep 17 00:00:00 2001 From: Taimoor Ahmed Date: Tue, 31 Mar 2026 01:23:01 +0500 Subject: [PATCH] docs: add ADR-0029 standardized error responses decision Add edx-platform/docs/decisions/0029-standardize-error-responses.rst describing objectives and the target contract for REST API error responses. Include implementation requirements, a standardized error payload example, and a DRF exception-handler snippet. --- .../0029-standardize-error-responses.rst | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 docs/decisions/0029-standardize-error-responses.rst diff --git a/docs/decisions/0029-standardize-error-responses.rst b/docs/decisions/0029-standardize-error-responses.rst new file mode 100644 index 000000000000..409d34965b25 --- /dev/null +++ b/docs/decisions/0029-standardize-error-responses.rst @@ -0,0 +1,148 @@ +Standardize Error Responses +============================ + +:Status: Proposed +:Date: 2026-03-31 +:Deciders: API Working Group +:Technical Story: Open edX REST API Standards – Error response interoperability + +Context +------- + +Open edX APIs currently return errors in multiple incompatible shapes (e.g., ``{"error": ...}``, +``{"detail": ...}``, nested field errors, and even HTTP 200 responses containing ``"success": false``). This +inconsistency makes it difficult for external clients and AI systems to reliably detect and map error +states across services. + +Objectives +---------- + +We want error responses that: + +* Use **correct HTTP status codes** (4xx/5xx) for failures, and avoid masking errors behind HTTP 200. +* Provide a **single, predictable JSON shape** so clients can implement one parsing path across services. +* Include **machine-readable identifiers** (e.g. a URI for the error class) so tools and integrations can + classify failures without scraping free-form text. +* Carry a **short human-readable summary** plus a **specific explanation** for this request when helpful. +* Tie errors to the **request** when useful (e.g. request path or URL) for support and logging. +* Represent **validation failures** in a consistent way (e.g. field/path to messages) instead of ad-hoc nesting. +* Are **documented and enforced** in DRF (central exception handling + schema generation). + +Decision +-------- + +We will standardize all Open edX REST APIs to return errors using a **structured JSON error object** for +non-2xx responses that meets the objectives above. + +Implementation requirements: + +* Use appropriate HTTP status codes (4xx/5xx). Avoid returning HTTP 200 for error conditions. +* Return a consistent payload with these core fields: + + * ``type`` (URI identifying the problem type) + * ``title`` (short, human-readable summary) + * ``status`` (HTTP status code) + * ``detail`` (human-readable explanation specific to this occurrence) + * ``instance`` (URI identifying this specific occurrence, when available) + +* For validation errors, include a predictable extension member, e.g. ``errors`` (a dict mapping + field/path to a list of messages). The shape must be consistent across apps. +* Define a small catalog of common ``type`` URIs for shared errors (authz failure, not found, validation, + rate-limited, etc.), and allow app-specific types when needed. +* Ensure the schema is documented and enforced in DRF (exception handler + drf-spectacular schema). + +Relevance in edx-platform +------------------------- + +Current error shapes in the codebase are inconsistent: + +* **DeveloperErrorViewMixin** (``openedx/core/lib/api/view_utils.py``) returns + ``{"developer_message": "...", "error_code": "..."}`` and for validation + ``{"developer_message": "...", "field_errors": {field: {"developer_message": "..."}}}``. +* **Instructor API** (``lms/djangoapps/instructor/views/api.py``) uses + ``JsonResponse({"error": msg}, 400)``. +* **Registration** (``openedx/core/djangoapps/user_authn/views/register.py``) returns + HTTP 200 with ``success: true/false`` and ``error_code`` for some failures. +* **ORA Staff Grader** (``lms/djangoapps/ora_staff_grader/errors.py``) uses a custom + ``ErrorSerializer`` with an ``error`` field. + +Code example (target shape) +--------------------------- + +**Example structured error response (4xx):** + +.. code-block:: json + + { + "type": "https://open-edx.org/errors/validation", + "title": "Validation Error", + "status": 400, + "detail": "The request body failed validation.", + "instance": "/api/courses/v1/", + "errors": { + "course_id": ["This field is required."], + "display_name": ["Ensure this field has no more than 255 characters."] + } + } + +**Example DRF exception handler emitting the standard shape:** + +.. code-block:: python + + # Central exception handler (e.g. in openedx/core/lib/api/exceptions.py) + def standardized_error_exception_handler(exc, context): + from rest_framework.views import exception_handler + response = exception_handler(exc, context) + if response is None: + return response + request = context.get("request") + body = { + "type": f"https://open-edx.org/errors/{_error_type(exc)}", + "title": _error_title(exc), + "status": response.status_code, + "detail": _flatten_detail(response.data), + } + if request: + body["instance"] = request.build_absolute_uri() + if isinstance(exc, ValidationError) and hasattr(exc, "detail"): + body["errors"] = _normalize_validation_errors(exc.detail) + response.data = body + response["Content-Type"] = "application/json" + return response + +Consequences +------------ + +Positive +~~~~~~~~ + +* Clients can implement a single error-handling path across services. +* AI agents and external integrations can programmatically detect and classify error states. +* Removes “hidden failures” caused by HTTP 200 + ``success: false`` patterns. + +Negative / Trade-offs +~~~~~~~~~~~~~~~~~~~~~ + +* Requires refactoring of existing endpoints and tests that currently depend on ad-hoc error shapes. +* Some clients may need a migration period if they parse legacy error formats. + +Alternatives Considered +----------------------- + +* **Keep per-app formats**: rejected due to interoperability and client complexity. +* **Use DRF defaults only**: rejected because DRF defaults still vary across validation/auth exceptions + unless centrally handled and documented. + +Rollout Plan +------------ + +1. Introduce a shared DRF exception handler (platform-level) that emits the standardized error shape. +2. Add contract tests for a representative set of endpoints (including those known to exhibit issues). +3. Migrate apps module-by-module; keep a short deprecation window for legacy shapes where feasible. +4. Update API documentation to specify the standard error schema. + +References +---------- + +* Open edX REST API Standards: “Inconsistent Error Response Structure” and alignment with structured, + interoperable error payloads across services.