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.
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.
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.
DELETE /api/tables/contacts/records/batch
{
"ids": ["1", "2", 3],
"permanent": false
}permanent goes in the body. Batch delete reads the flag from the JSON body, where the schema validates it; a ?permanent=true query string has no effect on this route, so a request that relies on it soft-deletes instead. Permanent batch delete is irreversible and is reserved for the admin role — see Soft Delete & Restore.
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.
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.
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.
Related pages
- CRUD & Upsert — single-record operations and the upsert contract
- Soft Delete & Restore — permanent delete and restore
- Record History & Comments — batch operations are logged
- Records Overview — envelope and authorship rules
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.