Documentation

Get started with
Datatomically

Everything you need to build, organize, and automate your business data.

Introduction

Datatomically is a no-code database platform that combines the simplicity of a spreadsheet with the power of a relational database. You organize data into bases, which contain tables, which contain records.

Each record has one or more fields — typed columns that define the structure of your data. You can view the same table through multiple views like Grid, Kanban, Calendar, and more.

New to Datatomically?

Start with a template. Head to the Templates page and pick one that fits your use case. You can always customize it after.

Quickstart

Get your first base running in under 5 minutes.

  • Create a workspace

    After signing up, you'll land in your workspace. A workspace holds all your bases. You can rename it from the workspace settings panel.

  • Create or import a base

    Click "+ New Base" to start from scratch, choose a template, or import a CSV from Airtable, Excel, or Google Sheets.

  • Add fields

    Click the "+ Field" button in the column header to add a new field. Choose from 20+ field types including text, number, select, date, and linked records.

  • Add records

    Click "+ Add record" to add rows. Click any cell to edit inline. Press Tab to move right, Enter to move down.

  • Invite your team

    Open workspace settings and click "Invite members." Set their role: Editor, Commenter, or Viewer.

REST API

Every base you can open in Datatomically is also available over a JSON REST API, so you can read, create, update and delete records from scripts, integrations and other systems.

Base URL: https://datatomically.com/api/v1
Airtable compatible

The API uses the same request and response shapes as Airtable's records API (records, fields, offset, filterByFormula, typecast…), so most existing Airtable integrations and client libraries only need the base URL and the token changed.

Authentication

Create a personal API token in the app under Account → API tokens. Tokens start with dt_ and are shown once — copy it straight away; only a hash is stored. Each token has read and/or write scopes. Pass it as a Bearer token on every request:

Authorization: Bearer dt_YOUR_API_TOKEN

A token acts as you: it can read every base in workspaces you belong to, and write to bases where you are an owner or editor. Requests without a valid token get 401 AUTHENTICATION_REQUIRED; requests you are not allowed to make get 403 NOT_AUTHORIZED.

Finding base, table and field ids

Tables and fields can be addressed by id or by name (table names are matched case-insensitively). Two discovery endpoints return everything you need:

GET /api/v1/bases → { "bases": [{ "id", "name", "permissionLevel" }] } GET /api/v1/bases/{baseId}/tables → { "tables": [{ "id", "name", "primaryFieldId", "fields": [{ "id", "name", "type", "options" }], "views": [{ "id", "name", "type" }] }] }

List records

GET https://datatomically.com/api/v1/{baseId}/{tableIdOrName} Response: { "records": [ { "id": "3f1c2a7e-5b7d-4c1e-9a0f-2d6b8e4c1a55", "createdTime": "2026-03-15T10:22:00.000Z", "fields": { "Name": "Project Alpha", "Status": "In Progress", "Points": 5, "Tags": ["design", "q2"] } } ], "offset": "100" }

Field values are keyed by field name. Empty values (blank text, unchecked checkboxes, empty lists) are omitted, as in Airtable. Only stored fields are returned — computed fields (formula, lookup, rollup, count, autonumber, created/last-modified time) are calculated inside the app and are not part of the API.

Query parameters:

  • pageSize — records per page, default and maximum 100.
  • offset — cursor from the previous response. Keep requesting with the returned offset until the response has none.
  • maxRecords — stop after this many records in total.
  • view — a view id or name; the view's filters and sorts are applied.
  • filterByFormula — a formula; records where it is truthy are returned (see below).
  • sort[0][field], sort[0][direction] — sort by field name or id, asc (default) or desc. Add sort[1]… for secondary sorts. Explicit sorts override the view's sorts.
  • fields[]=Name&fields[]=Status (or fields=Name,Status) — return only these fields.
  • returnFieldsByFieldId=true — key fields by field id instead of name.
# curl curl "https://datatomically.com/api/v1/{baseId}/Tasks?pageSize=50&view=Grid&sort%5B0%5D%5Bfield%5D=Due%20Date" \ -H "Authorization: Bearer dt_YOUR_API_TOKEN" // JavaScript (fetch) — walk every page const base = 'https://datatomically.com/api/v1/{baseId}/Tasks'; const headers = { Authorization: 'Bearer dt_YOUR_API_TOKEN' }; let offset, all = []; do { const url = base + (offset ? '?offset=' + offset : ''); const page = await fetch(url, { headers }).then(r => r.json()); all.push(...page.records); offset = page.offset; } while (offset);

A single record is available at GET /api/v1/{baseId}/{table}/{recordId} (404 NOT_FOUND if it does not exist).

Filtering with filterByFormula

Use the same formula language as formula fields. Reference fields with {Field Name}; a record is returned when the formula evaluates to something truthy. Remember to URL-encode the parameter.

# Open tasks worth more than 3 points curl -G "https://datatomically.com/api/v1/{baseId}/Tasks" \ -H "Authorization: Bearer dt_YOUR_API_TOKEN" \ --data-urlencode "filterByFormula=AND({Status} = 'Todo', {Points} > 3)" # Records whose name contains "alpha", ignoring case --data-urlencode "filterByFormula=FIND('alpha', LOWER({Name}))" # Records with no due date --data-urlencode "filterByFormula={Due Date} = BLANK()" // JavaScript const q = new URLSearchParams({ filterByFormula: "IS_AFTER({Due Date}, TODAY())", pageSize: 20 }); const { records } = await fetch(`${base}?${q}`, { headers }).then(r => r.json());

An invalid formula or an unknown field name returns 422 INVALID_FILTER_BY_FORMULA.

Create records

Send { "records": [{ "fields": … }] } (up to 10 records per request) or a single { "fields": … }. Fields can be given by name or id; anything you leave out gets its empty default. Values are coerced to the field type: numbers, booleans for checkboxes, strings for text and dates (ISO 8601), arrays for multiple select, linked records (record ids) and attachments ({ "url": … }).

POST https://datatomically.com/api/v1/{baseId}/Tasks Content-Type: application/json { "records": [ { "fields": { "Name": "New Project", "Status": "Backlog", "Due Date": "2026-05-01" } }, { "fields": { "Name": "Another one", "Points": 3 } } ], "typecast": true } Response: { "records": [ { "id": "…", "createdTime": "…", "fields": { … } }, … ] } # curl curl -X POST "https://datatomically.com/api/v1/{baseId}/Tasks" \ -H "Authorization: Bearer dt_YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"fields":{"Name":"New Project","Status":"Backlog"}}' // JavaScript await fetch(base, { method: 'POST', headers: { ...headers, 'Content-Type': 'application/json' }, body: JSON.stringify({ records: [{ fields: { Name: 'New Project', Status: 'Backlog' } }] }), });

Unknown select options are rejected with 422 INVALID_MULTIPLE_CHOICE_OPTIONS unless you pass "typecast": true, which creates the option in the field.

Update records

PATCH merges the fields you send into the record; PUT replaces every editable field (fields you omit are cleared). Both accept up to 10 records.

PATCH https://datatomically.com/api/v1/{baseId}/Tasks { "records": [ { "id": "3f1c2a7e-…", "fields": { "Status": "Done" } } ] } PATCH https://datatomically.com/api/v1/{baseId}/Tasks/{recordId} { "fields": { "Status": "Done" } } PUT …/Tasks or …/Tasks/{recordId} — same bodies, replaces all fields # curl curl -X PATCH "https://datatomically.com/api/v1/{baseId}/Tasks/{recordId}" \ -H "Authorization: Bearer dt_YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"fields":{"Status":"Done"}}'

Delete records

DELETE https://datatomically.com/api/v1/{baseId}/Tasks/{recordId} DELETE https://datatomically.com/api/v1/{baseId}/Tasks?records[]={id1}&records[]={id2} (up to 10; records=id1,id2 also works) Response: { "records": [ { "id": "…", "deleted": true } ] }

Limits

  • 100 records per page when listing; use offset to page through the rest.
  • 10 records per create, update or delete request.
  • Unfiltered, unsorted listing pages straight from the database. filterByFormula and view filters stop reading as soon as a page is full. Sorting reads the whole table, which is sized for the plan limit of 50,000 records per base.
  • Rate limit: 50 requests per 10 seconds per token (about 5 per second). Every response includes X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Unix seconds). Over the limit you get 429 with a Retry-After header.
  • Plan limits apply to the API too: Free workspaces hold up to 1,000 records per base, Pro up to 50,000.

Errors

Errors are always JSON with an Airtable-style error object:

{ "error": { "type": "UNKNOWN_FIELD_NAME", "message": "Unknown field name: \"Priorty\"" } }
  • 400 INVALID_REQUEST_BODY — body is not valid JSON.
  • 401 AUTHENTICATION_REQUIRED — missing, invalid or revoked token.
  • 403 NOT_AUTHORIZED — not a member of the workspace, not an editor, or the token lacks the scope.
  • 404 NOT_FOUND — unknown route, base, table or record.
  • 405 METHOD_NOT_ALLOWED — wrong HTTP method for the route.
  • 422 — validation: UNKNOWN_FIELD_NAME, INVALID_VALUE_FOR_COLUMN, INVALID_MULTIPLE_CHOICE_OPTIONS, INVALID_RECORD_ID, INVALID_FILTER_BY_FORMULA, VIEW_NAME_NOT_FOUND, INVALID_REQUEST_BODY (e.g. more than 10 records).
  • 422 PLAN_LIMIT_EXCEEDED — the workspace's plan doesn't allow more records in this base.
  • 429 TOO_MANY_REQUESTS — rate limit exceeded; wait for Retry-After seconds.
  • 500 SERVER_ERROR — something went wrong on our side; retry with backoff.
Webhooks & Zapier

To push data out when something changes, use an automation with the Send a webhook action (see Automations below) — it POSTs JSON to any URL, including Zapier and Make catch hooks. To pull data in, point your Airtable-compatible Zapier/Make step at this API's base URL with your token.

Automations

Automations react to changes in a base without any code. Each automation belongs to one base and has a single trigger (when) and one or more actions (then).

Creating an automation

  • Open the base and click the ⚡ Automations button in the toolbar (also available from the table menu as Automations…).
  • Click + New automation, give it a name and pick the table it watches.
  • Choose the trigger under When and add actions under Then. Action values can reference the triggering record's fields with placeholders like {Name} or {Due Date}.
  • Save. Automations are enabled by default; toggle them off from the list without deleting them.

Triggers

  • A record is created — fires when a new record is added to the table (in the grid, from a form, via import or the API while the app is open).
  • A record is updated — fires when a record changes. Optionally limit it to a single field so it only fires when that field changes.
  • A record matches a condition — pick a field, an operator (the same operators as view filters: is, contains, is empty, greater than, is before…) and a value. It fires when a record starts matching — a record that already matched does not fire again on unrelated edits.

Actions

  • Show a notification — displays an in-app toast with your message, e.g. Record {Name} was created.
  • Update the triggering record — sets a field on the record that fired the trigger to a new value (placeholders allowed).
  • Create a record — adds a record to any table in the base, with as many field values as you like copied or composed from the triggering record.
  • Send a webhook — POSTs JSON to a URL of your choice (Zapier, Make, n8n, your own server). The payload is { "event", "base", "table", "record": { "id", "fields": { "Field name": "display value", … } } }.
Automations run in your browser

Automations execute inside the Datatomically app while it is open — they are evaluated on the device that made the change. Records changed purely through the REST API, or while nobody has the base open, do not trigger automations. For server-side workflows, poll the REST API or trigger your own webhook from your integration.

Run history

Click Run history in the Automations panel to see the last 50 runs for the base — when each automation fired, whether it succeeded, and the error message if an action failed.