records
GET /records/{collection}
Query a collection with the closed filter language. Never SQL.
06 §6 and Q19. Each `where` is `field:op:value`, at most eight, all of which must match. - field: a top-level document key matching `^[A-Za-z_][A-Za-z0-9_]*$`, or one of `id`, `created_at`, `updated_at`. No nested paths. Any field may be filtered; `storage.collections[].index` only makes it faster. - op: `eq ne lt lte gt gte in contains exists`. - value: everything after the second `:`, so a value may itself contain `:` (a timestamp, a URL); a field and an operator never do. Parsed as JSON when it is valid JSON (`42`, `true`, `null`, `"42"`), otherwise taken as a literal string. `in` takes a JSON array. `contains` is a case-sensitive substring test when the stored value is a string and a membership test when it is an array. `exists` takes `true` or `false`. `lt`, `lte`, `gt` and `gte` match only stored values of the filter value's JSON type, so `seed:gt:10` never matches a seed stored as the string "42" (M4, signed off by the human at the second review, #5). - `created_at` and `updated_at` take RFC 3339 values and compare as instants (they are stored as milliseconds), so `created_at:gte:2026-09-15T00:00:00Z` works; any other value is `bad_filter`, and so are `contains` and `exists` on them. `id` compares as a string, which for ULIDs is creation order. Every value is bound as a parameter. `order` is `field:asc` or `field:desc` on one field, default `created_at:desc`, with `id` breaking ties in the same direction. Deleted records are never returned. Bad syntax, an unknown operator or more than eight clauses is 400 `bad_filter`.
Needs the records capability.
| Go | Query(ctx context.Context, collection string, params *RecordsQueryParams) (*RecordPage, error) |
|---|---|
| Python | def query(self, collection: str, *, where: Optional[Sequence[str]] = None, order: Optional[Any] = None, limit: Optional[Any] = None, cursor: Optional[Any] = None) -> Any |
| JavaScript | query(collection, params = {}) |
| Parameter | In | Type | About |
|---|---|---|---|
| collection | path · required | string | |
| where | query | string[] | |
| order | query | string | |
| limit | query | integer | |
| cursor | query | string | The `next_cursor` of the previous page, unchanged. |
| Status | Body | Means |
|---|---|---|
| 200 | RecordPage | A page of records. |
| 400 | Error | The request is malformed. Codes: `bad_request`, `bad_filter`, `bad_cursor`, `invalid_document`. |
| 401 | Error | No studio token, or a revoked or unknown one. Code `unauthenticated`. |
| 403 | Error | The token lacks a capability (`capability_required`, with `details.capability`), the job is not a task the studio may change (`not_a_task`), or the request came from another origin (`bad_origin`). |
POST /records/{collection}
Insert a document; returns its id and etag.
06 §6. The body is the document, a JSON object. A studio may write to any collection name matching the manifest schema's pattern, declared or not. Each live record counts toward the `records` quota (Q20).
Needs the records capability.
| Go | Insert(ctx context.Context, collection string, body map[string]any) (*Record, error) |
|---|---|
| Python | def insert(self, collection: str, body: Dict[str, Any]) -> Any |
| JavaScript | insert(collection, body) |
| Parameter | In | Type | About |
|---|---|---|---|
| collection | path · required | string |
Request body: JSONObject (application/json)
| Status | Body | Means |
|---|---|---|
| 201 | Record | The stored record. |
| 400 | Error | The request is malformed. Codes: `bad_request`, `bad_filter`, `bad_cursor`, `invalid_document`. |
| 401 | Error | No studio token, or a revoked or unknown one. Code `unauthenticated`. |
| 403 | Error | The token lacks a capability (`capability_required`, with `details.capability`), the job is not a task the studio may change (`not_a_task`), or the request came from another origin (`bad_origin`). |
| 413 | Error | The body is over its limit. Code `too_large`, with `details.limit` in bytes. |
| 429 | Error | A per-studio quota is full. Code `quota_exceeded`, with `details` as QuotaDetails. |
GET /records/{collection}/{id}
One record. Deleted, in another collection, or another studio's is 404.
Needs the records capability.
| Go | Get(ctx context.Context, collection string, id string) (*Record, error) |
|---|---|
| Python | def get(self, collection: str, id: str) -> Any |
| JavaScript | get(collection, id) |
| Parameter | In | Type | About |
|---|---|---|---|
| collection | path · required | string | |
| id | path · required | ULID |
| Status | Body | Means |
|---|---|---|
| 200 | Record | The record. |
| 401 | Error | No studio token, or a revoked or unknown one. Code `unauthenticated`. |
| 403 | Error | The token lacks a capability (`capability_required`, with `details.capability`), the job is not a task the studio may change (`not_a_task`), or the request came from another origin (`bad_origin`). |
| 404 | Error | Absent, deleted, or not the caller's to see. Code `not_found`. |
PUT /records/{collection}/{id}
Replace a document. If-Match on the etag; 409 on conflict.
Needs the records capability.
| Go | Replace(ctx context.Context, collection string, id string, body map[string]any, params *RecordsReplaceParams) (*Record, error) |
|---|---|
| Python | def replace(self, collection: str, id: str, body: Dict[str, Any], *, if_match: Optional[Any] = None) -> Any |
| JavaScript | replace(collection, id, body, params = {}) |
| Parameter | In | Type | About |
|---|---|---|---|
| collection | path · required | string | |
| id | path · required | ULID | |
| If-Match | header | string | The etag last read. When given and stale, the write is refused with 409 `etag_mismatch`. |
Request body: JSONObject (application/json)
| Status | Body | Means |
|---|---|---|
| 200 | Record | The replaced record. |
| 400 | Error | The request is malformed. Codes: `bad_request`, `bad_filter`, `bad_cursor`, `invalid_document`. |
| 401 | Error | No studio token, or a revoked or unknown one. Code `unauthenticated`. |
| 403 | Error | The token lacks a capability (`capability_required`, with `details.capability`), the job is not a task the studio may change (`not_a_task`), or the request came from another origin (`bad_origin`). |
| 404 | Error | Absent, deleted, or not the caller's to see. Code `not_found`. |
| 409 | Error | Codes: `etag_mismatch`, `name_taken`, `job_finished`, `already_running`, `not_running`, `port_conflict`, `heavy_conflict` (with `details.heavy`), `not_installed`, `conflict`, `in_use`, `missing`, `confirm_required` and `preview_changed` (with `details.preview`), `not_reclaimable`, and from M7: `approval_required` (with `details.approval`), `id_taken`, `not_fetched`, `already_exists`. |
| 413 | Error | The body is over its limit. Code `too_large`, with `details.limit` in bytes. |
PATCH /records/{collection}/{id}
Apply a JSON merge patch (RFC 7396). If-Match on the etag; 409 on conflict.
Needs the records capability.
| Go | Patch(ctx context.Context, collection string, id string, body map[string]any, params *RecordsPatchParams) (*Record, error) |
|---|---|
| Python | def patch(self, collection: str, id: str, body: Dict[str, Any], *, if_match: Optional[Any] = None) -> Any |
| JavaScript | patch(collection, id, body, params = {}) |
| Parameter | In | Type | About |
|---|---|---|---|
| collection | path · required | string | |
| id | path · required | ULID | |
| If-Match | header | string | The etag last read. When given and stale, the write is refused with 409 `etag_mismatch`. |
Request body: JSONObject (application/merge-patch+json)
| Status | Body | Means |
|---|---|---|
| 200 | Record | The patched record. |
| 400 | Error | The request is malformed. Codes: `bad_request`, `bad_filter`, `bad_cursor`, `invalid_document`. |
| 401 | Error | No studio token, or a revoked or unknown one. Code `unauthenticated`. |
| 403 | Error | The token lacks a capability (`capability_required`, with `details.capability`), the job is not a task the studio may change (`not_a_task`), or the request came from another origin (`bad_origin`). |
| 404 | Error | Absent, deleted, or not the caller's to see. Code `not_found`. |
| 409 | Error | Codes: `etag_mismatch`, `name_taken`, `job_finished`, `already_running`, `not_running`, `port_conflict`, `heavy_conflict` (with `details.heavy`), `not_installed`, `conflict`, `in_use`, `missing`, `confirm_required` and `preview_changed` (with `details.preview`), `not_reclaimable`, and from M7: `approval_required` (with `details.approval`), `id_taken`, `not_fetched`, `already_exists`. |
| 413 | Error | The body is over its limit. Code `too_large`, with `details.limit` in bytes. |
DELETE /records/{collection}/{id}
Soft-delete, so an undo is possible and a sync never sees a gap.
06 §6. The record stops counting toward the `records` quota.
Needs the records capability.
| Go | Delete(ctx context.Context, collection string, id string, params *RecordsDeleteParams) error |
|---|---|
| Python | def delete(self, collection: str, id: str, *, if_match: Optional[Any] = None) -> Any |
| JavaScript | delete(collection, id, params = {}) |
| Parameter | In | Type | About |
|---|---|---|---|
| collection | path · required | string | |
| id | path · required | ULID | |
| If-Match | header | string | The etag last read. When given and stale, the write is refused with 409 `etag_mismatch`. |
| Status | Body | Means |
|---|---|---|
| 204 | Deleted. | |
| 401 | Error | No studio token, or a revoked or unknown one. Code `unauthenticated`. |
| 403 | Error | The token lacks a capability (`capability_required`, with `details.capability`), the job is not a task the studio may change (`not_a_task`), or the request came from another origin (`bad_origin`). |
| 404 | Error | Absent, deleted, or not the caller's to see. Code `not_found`. |
| 409 | Error | Codes: `etag_mismatch`, `name_taken`, `job_finished`, `already_running`, `not_running`, `port_conflict`, `heavy_conflict` (with `details.heavy`), `not_installed`, `conflict`, `in_use`, `missing`, `confirm_required` and `preview_changed` (with `details.preview`), `not_reclaimable`, and from M7: `approval_required` (with `details.approval`), `id_taken`, `not_fetched`, `already_exists`. |