Skip to main content
View as Markdown

Batch Operations

Batch endpoints write many records in a single request, so you can import, synchronize, or modify bulk data efficiently — and atomically. Each batch runs in a single transaction: if any record fails validation, the whole batch rolls back and no rows are written. Pass returnRecords: true to receive the affected records in the response (otherwise only a summary is returned).

Method & Path Operation Max per batch
POST /api/tables/:tableId/records/batch Create 1000
PATCH /api/tables/:tableId/records/batch Update 100
DELETE /api/tables/:tableId/records/batch Soft delete 100
POST /api/tables/:tableId/records/batch/restore Restore 100
POST /api/tables/:tableId/records/upsert Upsert 100

All batch bodies require the canonical envelope form ({ "fields": { ... } }) — the flat-body shorthand accepted by single-record create is not available in batch requests. Every batch must contain at least one record.

Batch create

Send a records array, each item an envelope. Up to 1000 records per call.

app.json
POST /api/tables/contacts/records/batch
{
  "records": [
    { "fields": { "email": "alice@example.com", "name": "Alice" } },
    { "fields": { "email": "bob@example.com", "name": "Bob" } }
  ],
  "returnRecords": true
}
Status Meaning
201 Created All records created
400 Bad Request Empty array, over the 1000 limit, or any record fails validation (whole batch rolls back)
401 Unauthorized No active session
404 Not Found Table not found or not visible
409 Conflict A record collides with a unique constraint (whole batch rolls back)

Batch update

Each item names the record id (string or number) plus the fields to patch. Up to 100 records per call.

app.json
PATCH /api/tables/contacts/records/batch
{
  "records": [
    { "id": "1", "fields": { "status": "active" } },
    { "id": 2, "fields": { "status": "archived" } }
  ],
  "returnRecords": true
}

Updates are partial per record — only the named fields are written. If any id is missing or any value is invalid, the transaction rolls back.

Batch delete

Send an ids array. Soft delete by default; set permanent: true to hard-delete already soft-deleted records (admin-only, enforced in the application layer). Up to 100 IDs per call.

app.json
DELETE /api/tables/contacts/records/batch
{
  "ids": ["1", "2", 3],
  "permanent": false
}

Batch restore

Recover many soft-deleted records at once. Records that are not currently deleted are skipped; a missing id rolls the whole batch back.

app.json
POST /api/tables/contacts/records/batch/restore
{
  "ids": ["1", "2", "3"]
}

The restore clears each record's deletedAt/deletedBy and is logged to the record's change history.

Batch upsert

Create-or-update many records matched on one or more unique fields, in a single transaction. Name the merge key(s) with fieldsToMergeOn (alias: matchFields). Up to 100 records per call.

app.json
POST /api/tables/contacts/records/upsert
{
  "records": [
    { "fields": { "email": "alice@example.com", "name": "Alice" } },
    { "fields": { "email": "carol@example.com", "name": "Carol" } }
  ],
  "fieldsToMergeOn": ["email"],
  "returnRecords": true
}

Each record is matched on the merge fields: an existing match is patched, otherwise a new row is created. Batch upsert is the canonical path for idempotent synchronization from an external source of truth.

Limits & semantics summary

Property Behavior
Atomicity Each batch is a single transaction — all-or-nothing
returnRecords false by default; true returns the affected records
Minimum size At least one record/ID required (400 otherwise)
Unique conflict 409 on create, update and upsert — the same code a single-record write uses
Authorship createdBy/updatedBy/deletedBy stamped per record, same as single writes
Permissions RBAC and field-level permissions enforced per record

A uniqueness collision answers 409 Conflict on every batch path, matching the single-record write. Other constraint failures — a check, a foreign key, a NOT NULL — stay 400, because those reject the value that was sent, whereas a unique collision is a clash with a row that already exists. Either way the transaction rolls back whole.

Last updated August 11, 2026

This documentation was written with AI, so errors or outdated content are possible. Sovrium is in beta. Contributions and corrections are welcome.

Built with Sovrium