Error handling
Every error response uses a consistent envelope:
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.