Records Overview
Every table you declare automatically exposes a complete REST API for its records. There is no resolver to write and no endpoint to register — the moment a table exists in your config, its records collection is reachable under /api/tables/:tableId/records. This page documents the shared API surface: the URL layout, the response envelope, authorship metadata, and the cross-cutting rules (authentication, permissions, formatting) that every records endpoint obeys.
Records are the rows of a table. Tables define the columns (fields); records hold the values. The Records API is the canonical read/write path for that data — pages render it, forms write to it, automations react to it, and external integrations consume it.
Endpoint surface
:tableId accepts either the numeric table id or the table name/slug. All endpoints accept and return JSON.
| Method & Path | Description |
|---|---|
POST /api/tables/:tableId/records |
Create a single record |
GET /api/tables/:tableId/records |
List records (pagination, sort, filter, field-select, group, aggregate) |
GET /api/tables/:tableId/records/:recordId |
Read a single record by ID |
PATCH /api/tables/:tableId/records/:recordId |
Update a single record (partial) |
DELETE /api/tables/:tableId/records/:recordId |
Soft delete a record (?permanent=true to hard delete) |
POST /api/tables/:tableId/records/upsert |
Create-or-update by match fields |
POST /api/tables/:tableId/records/batch |
Batch create |
PATCH /api/tables/:tableId/records/batch |
Batch update |
DELETE /api/tables/:tableId/records/batch |
Batch (soft) delete |
POST /api/tables/:tableId/records/:recordId/restore |
Restore a soft-deleted record |
POST /api/tables/:tableId/records/batch/restore |
Batch restore |
GET /api/tables/:tableId/records/:recordId/history |
Record change history |
GET|POST|PATCH|DELETE /api/tables/:tableId/records/:recordId/comments |
Record comments |
GET /api/tables/:tableId/trash |
Browse soft-deleted records (trash view) |
GET /api/tables/:tableSlug/subscribe |
Real-time WebSocket subscription |
Detailed behavior is split across focused pages: CRUD & upsert, filtering, sorting & pagination, batch operations, history & comments, soft delete & trash, real-time subscriptions, and import/export.
The fields envelope
Record write bodies use the canonical Airtable-style envelope: a fields object mapping field names to values.
POST /api/tables/contacts/records
{
"fields": {
"email": "john@example.com",
"first_name": "John",
"last_name": "Doe"
}
}
A successful create returns 201 Created with the stored record, including its generated id and authorship metadata:
{
"id": "42",
"fields": {
"email": "john@example.com",
"first_name": "John",
"last_name": "Doe"
},
"createdBy": "user-uuid-123",
"createdAt": "2025-01-15T10:30:00Z",
"updatedAt": "2025-01-15T10:30:00Z"
}
Flat bodies are accepted too. A body without a fields key (e.g. { "email": "john@example.com" }) is automatically wrapped into the canonical { "fields": { ... } } shape. The envelope form is preferred and is the only shape accepted by batch and upsert endpoints.
List response envelope
GET .../records returns a paginated envelope, never a bare array:
{
"records": [{ "id": "1", "fields": {} }],
"pagination": { "total": 128, "limit": 20, "offset": 0 }
}
records is the page of results; pagination carries the total matching-row count plus the limit/offset that produced this page. See Filtering, Sorting & Pagination for the full query-parameter grammar.
Authorship metadata
The API automatically stamps and exposes authorship on every write. These fields are server-controlled and read-only — values supplied in a request body are ignored, so the audit trail cannot be forged.
| Field | Set when | Cleared/updated |
|---|---|---|
createdBy / createdAt |
Record is created | Never changes |
updatedBy / updatedAt |
Record is created or updated | Re-stamped on each write |
deletedBy / deletedAt |
Record is soft-deleted | Cleared on restore |
createdBy, updatedBy, and deletedBy resolve to the authenticated user's ID. They are surfaced through dedicated user-audit field types (created-by, updated-by, deleted-by) and the updated-at system field auto-bumps the timestamp on every write — enabling the optimistic-locking contract.
Cross-cutting rules
Every records endpoint enforces the same guarantees:
| Concern | Behavior |
|---|---|
| Authentication | Endpoints require a session. Unauthenticated requests return 401. |
| Table existence | An unknown :tableId returns 404 — never a 403, to avoid enumeration. |
| RBAC | Create/read/update/delete are gated per table permission and the role's RBAC grants. |
| Field-level permissions | Responses omit fields the caller cannot read; writes to fields the caller cannot write are rejected. |
| Validation | Field values are validated against the field type; missing required fields and constraint violations return 400. |
| Anti-enumeration | Unauthorized access to an existing record returns 404, not 403, so an attacker cannot distinguish "forbidden" from "absent". |
404 over 403 is intentional. To prevent record enumeration, the API returns 404 Not Found for records the caller may not access — identical to the response for records that do not exist. See the authentication overview.
Display vs raw formatting
By default, field values are returned raw (the stored value). Pass ?format=display to receive human-readable, locale- and timezone-aware values (formatted currency, dates, durations, attachment display names). The raw form is preferred for programmatic clients; the display form is preferred for direct rendering. See Record formatting for the controls.
Related pages
- CRUD & Upsert — create, read, update, delete, upsert, formatting
- Filtering, Sorting & Pagination — query parameters
- Batch Operations — bulk create/update/delete
- Record History & Comments — change log and collaboration
- Soft Delete & Restore — trash and recovery
- Real-Time Subscriptions — WebSocket/SSE/poll
- Import & Export — CSV/JSON and clipboard
- API Reference — the generated endpoint catalog