Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 148 additions & 0 deletions docs/decisions/0029-standardize-error-responses.rst
Original file line number Diff line number Diff line change
@@ -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.
Loading