Skip to content

Error handling

Every error response uses a consistent envelope:

{
  "error": {
    "code": "<machine-readable code>",
    "message": "<human-readable description>"
  }
}

Or, for field-level validation failures:

{
  "error": {
    "code": "field_validation",
    "field_errors": {
      "<field path>": "<human-readable message>"
    }
  }
}

See the full error code table for the canonical list. This page covers what to do about each one.

400 invalid_json

The request body wasn't valid JSON (single-student endpoint) or wasn't parseable as the expected codec (bulk endpoint with text/csv). Inspect the request body you sent — common causes are trailing commas, unescaped quotes, or sending form-encoded data instead of JSON.

Action: fix the request body; retry.

415 unsupported_media_type

The Content-Type request header didn't match what the endpoint accepts. /v1/calculate requires application/json. /v1/calculate/bulk requires text/csv.

Action: set the correct Content-Type header; retry.

406 (bulk only)

The Accept request header lists no response type the endpoint supports. Today, the bulk endpoint only returns text/csv.

Action: set Accept: text/csv or omit the header.

422 validation_error

The body parsed correctly but the structure doesn't match the request schema — e.g., a required field is missing, an enum value is unknown, or a numeric field is out of range. The details array carries Pydantic's structured error path so your client can highlight the offending field.

Action: inspect details; fix the field; retry.

422 field_validation

A semantic validation failure inside the calculation engine — typically a prior_disbursement_* value that exceeds the pre-drop maximum. The field_errors map keys are paths like terms_0_priorDisbursementSub (Datastar-flavored for backward compatibility with the calculator UI's signal names) and values are human-readable messages such as "Cannot exceed $4,500".

Action: display the field-level message next to the relevant input; let the user correct it.

500 internal_error

An unexpected error. The response message is sanitized — no stack traces or internal detail are leaked. The full traceback is logged on the server.

Action: retry once. If it persists, capture the request body you sent and email api@desata.io with it; the team can correlate against logs.

What's stable

The list of error codes and the structure of the envelope are part of the v1 stability contract. The human-readable message text may be improved over time, but the code value won't change. Match on code, not on message. See Stability for the full policy.