Skip to main content
The Softr Database API lets you integrate your Softr data with external systems. The API follows REST semantics, uses JSON to encode objects, and requires all requests to be made over HTTPS. Download the OpenAPI specification to import into tools like Postman or use for code generation.

Video Introduction

Data Model

The API is organized around four core resources:
  • Database — A top-level container that belongs to a workspace. Each database holds one or more tables.
  • Table — A structured collection of records, similar to a spreadsheet or database table. Each table has a set of fields and one or more views.
  • Field — A column definition on a table. Fields have a type (e.g. SINGLE_LINE_TEXT, NUMBER, SELECT) and optional configuration. Some fields are read-only (computed or system-managed).
  • Record — A single row of data in a table. Record values are stored in a fields object, keyed by field ID (or field name if you pass ?fieldNames=true).
All resources are identified by UUIDs.

Authentication

All requests require a Personal Access Token (PAT) passed in the Softr-Api-Key header:
curl -X GET 'https://tables-api.softr.io/api/v1/databases' \
  -H 'Softr-Api-Key: <your-token>'
Tokens are scoped to one or more workspaces — you can only access databases within your authorized workspaces. See Authorisation for how to generate a token.

Quick Start

1. List your databases:
curl -X GET 'https://tables-api.softr.io/api/v1/databases' \
  -H 'Softr-Api-Key: <your-token>'
2. Get records from a table:
curl -X GET 'https://tables-api.softr.io/api/v1/databases/{databaseId}/tables/{tableId}/records?limit=10&fieldNames=true' \
  -H 'Softr-Api-Key: <your-token>'
3. Create a record:
curl -X POST 'https://tables-api.softr.io/api/v1/databases/{databaseId}/tables/{tableId}/records' \
  -H 'Softr-Api-Key: <your-token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "fields": {
      "Name": "Jane Doe",
      "Email": "jane@example.com"
    }
  }'

Response Format

Success (single item)

{
  "data": { ... },
  "metadata": null
}

Success (paginated list)

{
  "data": [ ... ],
  "metadata": {
    "offset": 0,
    "limit": 20,
    "total": 100
  }
}

Error

{
  "message": "Human-readable error message",
  "errorCode": "MACHINE_READABLE_CODE",
  "details": {
    "fieldId": "Additional context",
    "traceId": "abc-123"
  }
}

Error Codes

CodeHTTP StatusDescription
BAD_REQUEST400Invalid request format or data
VALIDATION_ERROR400Field validation failed
UNKNOWN_FIELD400Unrecognized field in request
UNAUTHORIZED401Missing or invalid token
QUOTA_EXCEEDED402Plan quota limit reached
FORBIDDEN403Insufficient permissions
RESOURCE_NOT_FOUND404Resource does not exist
CONSTRAINT_VIOLATION409Data constraint violation (e.g. duplicate unique value)
PAYLOAD_TOO_LARGE413Request body exceeds 64 MB
TOO_MANY_REQUESTS429Rate limit exceeded
SERVICE_UNAVAILABLE503Service temporarily unavailable

Pagination

All list endpoints support offset-based pagination:
ParameterDefaultMaxDescription
offset0Number of records to skip
limit20200Number of records per page
The response metadata includes a total count. To iterate through all records:
GET .../records?offset=0&limit=100    → records 1–100
GET .../records?offset=100&limit=100  → records 101–200
GET .../records?offset=200&limit=100  → records 201–300
Stop when offset >= metadata.total.

Using Field Names

By default, the fields object in record responses uses field IDs as keys (UUIDs). Pass ?fieldNames=true on any record endpoint to use human-readable field names instead:
# With field IDs (default)
GET .../records
 { "fields": { "fld_abc123": "Jane Doe" } }

# With field names
GET .../records?fieldNames=true
 { "fields": { "Full Name": "Jane Doe" } }