HTTP error mapping (route templates)¶
This page documents how AutoCRUD route templates map internal exceptions to HTTP responses.
All route templates use a shared helper (to_http_exception) that
provides consistent error mapping across every endpoint.
Exception mapping table¶
| Exception | HTTP | Meaning |
|---|---|---|
msgspec.ValidationError |
422 | Type-level validation |
autocrud.types.ValidationError |
422 | Custom validation |
PermissionDeniedError |
403 | Access denied |
ResourceNotFoundError (family) |
404 | Resource / revision missing |
UniqueConstraintError |
409 | Unique field conflict (structured detail) |
ResourceConflictError (family) |
409 | Conflict |
Any other Exception |
400 | Bad request (fallback) |
The ResourceNotFoundError family includes:
ResourceIDNotFoundErrorResourceIsDeletedErrorRevisionNotFoundErrorRevisionIDNotFoundError
The ResourceConflictError family includes:
UniqueConstraintError(structured JSON detail)DuplicateResourceErrorSchemaConflictErrorCannotModifyResourceError
Error matrix (quick overview)¶
| Route | 400 | 403 | 404 | 409 | 422 |
|---|---|---|---|---|---|
| GET | fallback | permission | not found | conflict | — |
| POST | fallback | permission | not found | unique / conflict | validation |
| PUT | fallback | permission | not found | unique / conflict | validation |
| PATCH | fallback | permission | not found | unique / conflict | validation |
| DELETE | fallback | permission | not found | conflict | — |
| SWITCH | fallback | permission | not found | conflict | — |
| RESTORE | fallback | permission | not found | conflict | — |
Read routes (get.py)¶
Canonical GET resource¶
GET /{model}/{resource_id}
(and deprecated aliases: /data, /meta, /full, /revision-info)
Errors pass through to_http_exception:
- 404 —
ResourceIDNotFoundError,ResourceIsDeletedError,RevisionIDNotFoundError - 403 —
PermissionDeniedError - 400 — any other internal exception
Revision list¶
GET /{model}/{resource_id}/revision-list
Specific inline errors:
- 400 — invalid
sort,limit < 1,offset < 0 - 404 —
from_revision_idnot found
All other exceptions go through to_http_exception.
Blob content¶
GET /{model}/{resource_id}/blobs/{file_id}
The route performs a permission gate via resource_manager.get(resource_id).
Errors from that call go through to_http_exception:
- 403 —
PermissionDeniedError - 404 —
ResourceIDNotFoundError
Blob-specific errors:
- 404 — blob does not exist (
FileNotFoundError) - 400 — blob store not configured (
NotImplementedError) - 500 — blob data missing
Upload-session routes¶
POST /blobs/upload-sessions, GET /blobs/upload-sessions/{upload_id},
PUT /blobs/upload-sessions/{upload_id}/content,
POST /blobs/upload-sessions/{upload_id}/finalize,
POST /blobs/upload-sessions/{upload_id}/abort
- 501 — blob store not configured
- 404 —
upload_idnot found - 409 Conflict — invalid state transition (e.g. finalize without content, finalize twice, abort after finalization, upload content to a non-pending session)
- 400 — other errors from the blob store or route layer
- 204 — successful abort (no body)
Create routes (create.py)¶
POST /{model}
422 Unprocessable Entity¶
Validation errors (caught explicitly before the fallback):
msgspec.ValidationErrorautocrud.types.ValidationError
409 Conflict¶
Unique constraint violations:
UniqueConstraintError
Response body (detail) format:
{
"message": "Unique constraint violated: field 'email' value 'foo@bar.com' already exists",
"field": "email",
"conflicting_resource_id": "user_123"
}
Fallback¶
All other exceptions go through to_http_exception (403, 404, 409, or 400).
Update routes (update.py)¶
PUT /{model}/{resource_id}
400 Bad Request (inline)¶
change_statusis only allowed whenmode="modify"
422 Unprocessable Entity¶
Validation errors (caught explicitly):
msgspec.ValidationErrorautocrud.types.ValidationError
409 Conflict¶
UniqueConstraintError(structured detail, same as Create)CannotModifyResourceError(e.g. modifying a stable resource withoutmode="modify")
404 Not Found¶
ResourceIDNotFoundError— resource does not exist
Fallback¶
All other exceptions go through to_http_exception.
Patch routes (patch.py)¶
PATCH /{model}/{resource_id}
Supports RFC6902 patch operations: add, remove, replace, move, copy, test.
422 Unprocessable Entity¶
Validation errors (caught explicitly):
msgspec.ValidationErrorautocrud.types.ValidationError
409 Conflict¶
UniqueConstraintError(structured detail)
404 Not Found¶
ResourceIDNotFoundError
Fallback¶
All other exceptions go through to_http_exception.
Delete routes (delete.py)¶
DELETE /{model}/{resource_id}
All exceptions go through to_http_exception:
- 404 — resource not found
- 403 — permission denied
- 409 — conflict
- 400 — fallback
The same mapping applies to:
DELETE /{model}/{resource_id}/permanentlyDELETE /{model}(batch delete)POST /{model}/{resource_id}/restorePOST /{model}/restore(batch restore)
Switch routes (switch.py)¶
POST /{model}/{resource_id}/switch/{revision_id}
All exceptions go through to_http_exception:
- 404 — resource or revision not found
- 403 — permission denied
- 400 — fallback
Implications for API clients¶
Consistent behavior across all routes¶
All routes now use the same error mapping:
- 403 → access denied
- 404 → resource or revision does not exist
- 409 → conflict (unique constraint, cannot modify, duplicate, schema conflict)
- 422 → data validation failure
- 400 → generic / unexpected error
UniqueConstraintError detail¶
409 responses from UniqueConstraintError include structured JSON:
{
"message": "Unique constraint violated: ...",
"field": "email",
"conflicting_resource_id": "user_123"
}
Other 409 responses have a plain string detail.
Implementation¶
The shared helper lives in autocrud/crud/route_templates/exception_handlers.py:
Route templates use it as: