Errors
Endpoint-level errors from the Smart EPD v2 API use a consistent envelope, so you can handle most failures with one code path. When an endpoint rejects a request or hits a business-rule failure, the non-2xx response has this shape:
{
"error": {
"code": "validation_error",
"message": "Name is required.",
"details": [
{ "field": "name", "message": "This field is required." }
]
}
}
| Key | Type | Meaning |
|---|---|---|
error.code | string | Stable, machine-readable error code. Branch on this, not on message. |
error.message | string | Human-readable explanation. Safe to surface to end users. |
error.details | array | omitted | Optional field-level breakdown (most common on validation_error). Each item is an object such as { "field": "...", "message": "..." }. Omitted when there is nothing field-specific to report. |
error.code is the contract; error.message wording may change between releases. Always switch on error.code.
Common error codes
The codes below are the ones the API emits today. Treat the list as the common set rather than an exhaustive enumeration — handle unknown codes by falling back to the HTTP status.
| HTTP status | error.code | When it happens |
|---|---|---|
400 Bad Request | validation_error | An endpoint rejected the request on a business/validation rule (raised via make_error). Check details. |
400 Bad Request | invalid_file_type | An uploaded file's content type is not accepted for that endpoint. |
403 Forbidden | forbidden | You are authenticated but lack the scope/role for this resource or action. |
404 Not Found | not_found | The resource does not exist or you don't have access to it. (Access failures return 404, not 403, so resource IDs can't be probed.) |
409 Conflict | conflict | The operation conflicts with the resource's current state (e.g. an in-flight task already exists). |
413 Payload Too Large | file_too_large | An uploaded file exceeds the endpoint's size limit. |
429 Too Many Requests | rate_limited | A throttled endpoint (the email/login auth endpoints, invite lookups, and the public EPD library) received too many requests. |
500 Internal Server Error | internal_error | An unexpected server-side error. The message is generic by design; check server logs / Sentry. |
EPD submission codes
POST /epds/{epd_id}/submit/ returns the 400 codes below. Resubmitting a flagged EPD can return the last three. Their details items are objects specific to each code, not {field, message} pairs:
error.code | When it happens | details items |
|---|---|---|
shape_validation_error | The document is missing required fields or has values of the wrong type. | The blocking document violations. |
tool_expired | The project's EPD tool verification has expired. | {tool_name, expires_on} |
reference_inputs_missing | A reference EPD whose template enforces its constraints lacks a value for an external input field that is required or needed by a constraint. Cannot be acknowledged. | {field_id, label} |
constraint_failures | A reference EPD's values fail error-severity constraints of an enforced template. Repeat the call with ?acknowledge_constraint_failures=true to submit anyway. | The failing constraint rulings. |
model_lock_drift | A variant EPD's model (EPD tool or version, PCR spec version, or template definition) differs from its reference EPD's locked model, and the variant_model_lock_block feature flag is on. | {coordinate, label, locked, actual} |
See Submitting a reference EPD and Variant conformance.
A 404 is returned both when a resource genuinely doesn't exist and when it exists but you can't see it. This is intentional — it prevents callers from discovering the existence of resources they aren't authorized to access. Don't treat 404 as proof that an ID is unused.
Framework-level errors look different. Failures raised by the web framework before a request reaches endpoint logic — request body/query schema validation (HTTP 422) and authentication failures (HTTP 401) — currently return the framework's default body, not the error envelope above. Branch on the HTTP status for those cases. (Normalizing them onto the envelope is tracked as a separate follow-up.)
Handling errors
resp=$(curl -s -w '\n%{http_code}' \
-H "Authorization: Bearer $TOKEN" \
"https://sandbox.smartepd.xyz/api/v2/projects/$PROJECT_ID/epds/")
status=$(printf '%s' "$resp" | tail -n1)
body=$(printf '%s' "$resp" | sed '$d')
if [ "$status" -ge 400 ]; then
code=$(printf '%s' "$body" | jq -r '.error.code')
echo "Request failed ($status): $code"
printf '%s' "$body" | jq '.error.details // empty'
fi
A robust client should:
- Branch on
error.codefor known cases. - Fall back to the HTTP status class for unknown codes (
4xx→ caller error,5xx→ retry with backoff). - Surface
error.messageto users only where it's safe; log the full envelope for debugging.
Related
- Pagination — the cursor model for list endpoints.
- API Reference overview — download the OpenAPI spec or browse endpoints.