API conventions¶
This document describes AutoCRUD’s HTTP API conventions — especially the parts that are not obvious from plain REST CRUD:
- returns= (select response sections)
- partial= (field-level projection across data / meta / revision_info)
- include_deleted= (soft-delete visibility)
- revisions & versioning semantics
- blob (Binary) handling
- query / search conventions (qb recommended)
This document reflects the current implementation in route templates.
Canonical read API: GET /{model}/{resource_id}¶
Response shape (section-based)¶
AutoCRUD returns a unified response envelope:
Each section can be included or omitted via returns.
returns query parameter¶
returns is a comma-separated list of sections to include.
-
Allowed values:
-
data revision_info-
meta -
Default:
-
returns=data,revision_info,meta
Examples:
- Full response (default)
GET /users/123
- Data only
GET /users/123?returns=data
- Meta only
GET /users/123?returns=meta
- Data + meta (no revision info)
GET /users/123?returns=data,meta
Note: Any section not listed in
returnswill be returned asUNSET(omitted in the serialized output, depending on encoder behavior).
Field projection: partial / partial[]¶
AutoCRUD supports field-level projection through partial (or partial[] for axios / repeated-query compatibility).
The partial path format¶
-
Paths are slash-prefixed:
-
/field /nested/field- If the user passes
field(no leading slash), AutoCRUD normalizes it to/field.
partialis treated as a structural selector (similar to JSON Pointer style paths), and is passed intofilter_struct_partial()/get_partial().
Prefix routing: project across data, meta, revision_info¶
Each partial field can be routed to a specific section by using a prefix:
data/<path>→ applies todatameta/<path>→ applies tometainfo/<path>→ applies torevision_info(**note the prefix isinfo/, section name isrevision_info)
Examples:
- Only some fields of
data
GET /users/123?returns=data&partial=/name&partial=/email
- Only some fields of
meta
GET /users/123?returns=meta&partial=meta/resource_id&partial=meta/updated_time
- Only some fields of
revision_info
GET /users/123?returns=revision_info&partial=info/revision_id&partial=info/status
- Mixed projection across multiple sections
GET /users/123?returns=data,meta&partial=/name&partial=meta/updated_time
Default routing of unprefixed partial¶
Unprefixed partial paths are routed by the route’s default_category.
For canonical GET /{model}/{resource_id}:
default_category = "data"-
Therefore:
-
partial=/name→ applies to data - If you want meta/info, you must prefix with
meta/orinfo/.
For legacy endpoints (deprecated aliases) the default category differs:
/metaendpoints setdefault_category="meta"/revision-infoendpoints setdefault_category="info"
Soft deletion: include_deleted¶
Most read endpoints accept:
include_deleted=false(default)include_deleted=true
Behavior (read)¶
include_deleted controls whether metadata can be retrieved for soft-deleted resources.
At the ResourceManager layer:
-
get_meta(resource_id, include_deleted=False): -
raises
ResourceIsDeletedErrorif resource is deleted -
get_meta(resource_id, include_deleted=True): -
returns metadata even if deleted
Route templates typically call get_meta(...) first, so include_deleted is a visibility switch that gates follow-up reads (data, revision info, etc.).
Note: Current read routes collapse most errors into HTTP 404 (see “HTTP error mapping”).
Revisions and mutability¶
AutoCRUD has two update modes with different revision semantics:
update (default): append a new revision (immutable history)¶
-
Used by:
-
PUT /{model}/{resource_id}withmode=update(default) PATCH /{model}/{resource_id}withmode=update(default)-
Semantics:
-
Creates a new revision
- Sets
parent_revision_idto the previous current revision - Revision history is append-only under this mode
modify (draft update): overwrite the current revision (not immutable)¶
-
Used by:
-
PUT /{model}/{resource_id}?mode=modify PATCH /{model}/{resource_id}?mode=modify-
Semantics:
-
Overwrites the current revision instead of creating a new one
- This means the revision history is not immutable under
modify change_statusis only allowed undermode=modify
Practical guidance:
- Use
updatefor normal production writes / audit history.- Use
modifyfor draft workflows where “current revision is editable”.
Blob conventions (Binary)¶
AutoCRUD supports a first-class blob type:
class Binary(Struct):
file_id: str | UNSET
size: int | UNSET
content_type: str | UNSET
data: bytes | UNSET
Storage behavior¶
When writing a resource that contains Binary(data=...):
- AutoCRUD extracts the bytes
- Stores them into the configured
IBlobStore -
Populates:
-
file_id(hash of content) size- optionally
content_type - Clears
datain the stored resource (so resource payload stays small)
Read behavior¶
Blob bytes are retrieved via the dedicated endpoint:
GET /{model}/{resource_id}/blobs/{file_id}
- This endpoint performs a permission gate by calling
resource_manager.get(resource_id)first. - Then it calls
resource_manager.get_blob(file_id)to fetch bytes. -
The response
Content-Typeuses: -
content_typefromBinaryif available - otherwise
application/octet-stream
Upload sessions¶
In addition to the one-shot POST /blobs/upload, AutoCRUD provides a
two-step upload-session flow for larger files or when pre-signed URLs
are needed:
| Step | Method | Endpoint |
|---|---|---|
| 1. Create session | POST |
/blobs/upload-sessions |
| 2. Check status | GET |
/blobs/upload-sessions/{upload_id} |
| 3. Upload bytes | PUT |
/blobs/upload-sessions/{upload_id}/content |
| 4a. Finalize | POST |
/blobs/upload-sessions/{upload_id}/finalize |
| 4b. Abort | POST |
/blobs/upload-sessions/{upload_id}/abort |
Lifecycle: pending → uploaded → finalized (or aborted)
The response from Step 1 includes an upload_method field:
"proxy"— Upload bytes viaPUT .../content(Step 3)."single_put"— Upload bytes directly to theupload_urlreturned in the session (e.g. an S3 presigned URL). Step 3 is skipped.
After finalization, the response is a Binary descriptor (same as the
one-shot upload).
Fallback behavior¶
If the underlying IBlobStore does not natively support upload sessions
(e.g. MemoryBlobStore, DiskBlobStore), the route layer provides a
real stateful fallback facade:
PUT .../contentstores bytes in temporary memory — does not write to the blob store.POST .../finalizereads the buffered bytes and callsblob_store.put(...)— only then writes to the blob store.POST .../abortdiscards temporary bytes.
This makes the upload-session API available with any blob store backend.
Query / search conventions (recommended: qb)¶
For list/search endpoints, AutoCRUD supports multiple query styles, but recommends qb.
qb (recommended)¶
qb is a Query Builder expression parsed by a safe AST parser (not eval).
Example:
qb=QB["age"].gt(18) & QB["status"].eq("active")
Rules:
-
If
qbis provided, it must not be combined with: -
data_conditions conditionssortslimit/offsetin URL can override defaults.
Structured JSON conditions¶
If not using qb, you can pass JSON strings:
data_conditions: filters over resource data fieldsconditions: general filters (meta or data depending on operator / field_path usage)sorts: mix of meta-sort and data-sort objects
These are parsed via json.loads(...) and converted into:
DataSearchConditionResourceMetaSearchSortResourceDataSearchSort
HTTP error mapping (route templates)¶
This section documents how current route templates map internal exceptions to HTTP responses.
Some routes intentionally normalize multiple internal errors into the same HTTP code.
Read routes (get.py)¶
Canonical GET resource¶
GET /{model}/{resource_id} (and deprecated aliases like /data, /meta, /full, /revision-info)
-
404 Not Found
-
Most internal exceptions are caught and returned as:
HTTPException(404, detail=str(e))
This includes (but is not limited to):
ResourceIDNotFoundErrorResourceIsDeletedError(unlessinclude_deleted=true)RevisionIDNotFoundErrorRevisionNotFoundError
Note
- Read routes currently do not consistently distinguish
404vs403vs409. Many failures collapse into404.
Revision list¶
GET /{model}/{resource_id}/revision-list
-
400 Bad Request
-
invalid
sort(must becreated_timeor-created_time) - invalid
limit(< 1) -
invalid
offset(< 0) -
404 Not Found
-
from_revision_idprovided but not found (detail="revision_id not found") - any other unhandled exception normalized to 404
Blob content¶
GET /{model}/{resource_id}/blobs/{file_id}
-
403 Forbidden
-
permission gate fails (permission denied OR resource not found are currently collapsed)
-
404 Not Found
-
FileNotFoundError(blob missing) -
400 Bad Request
-
NotImplementedError(blob store not configured) -
500 Internal Server Error
-
blob record exists but
datais missing (detail="Blob data missing")
Create routes (create.py)¶
POST /{model}
-
422 Unprocessable Entity
-
msgspec.ValidationError(type-level validation during decoding) -
autocrud.types.ValidationError(domain/business validation) -
409 Conflict
-
UniqueConstraintError→ mapped via helper (raise_unique_conflict) -
400 Bad Request
-
any other exception normalized to 400
Update routes (update.py)¶
PUT /{model}/{resource_id}
-
400 Bad Request
-
invalid argument combination:
change_statusonly allowed withmode=modify -
any other exception normalized to 400 (including “not found” today)
-
422 Unprocessable Entity
-
msgspec.ValidationError -
autocrud.types.ValidationError -
409 Conflict
-
UniqueConstraintError→ mapped via helper (raise_unique_conflict)
Note
- Update route currently does not return 404; “resource not found” is folded into 400.
Patch routes (patch.py)¶
PATCH /{model}/{resource_id}
-
400 Bad Request
-
most runtime errors normalized to 400:
- resource not found / revision not found
- permission denied
- jsonpatch apply failures (invalid path, test op failed, etc.)
- other unhandled exceptions
-
422 Unprocessable Entity
-
msgspec.ValidationError autocrud.types.ValidationError
Note
change_statusis only valid withmode=modify; otherwise returns 400.
Recommended client-side conventions¶
-
Treat
404on read endpoints as a generic “cannot fetch”. If you need to distinguish permission vs not-found, you must apply a stricter mapping (e.g. explicit exception handlers) or adjust templates. -
Prefer
qbfor search/list queries: -
easier to version and safer than ad-hoc JSON conditions
-
Use
updatemode for immutable audit history; usemodifyfor draft workflows.
Optional future improvements¶
If you want more REST-accurate mapping, consider:
PermissionDeniedError→ 403ResourceNotFoundErrorfamily → 404ResourceConflictErrorfamily (UniqueConstraintError,DuplicateResourceError, etc.) → 409- avoid broad
except Exception→ 404/400 (it can hide real server bugs)