Skip to main content
View as Markdown

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"
}

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".

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.