Backend API
Use POST /:transaction to run a group of write operations as one atomic unit—either every operation commits, or none of them do. Use a transaction when a partial result would leave your data in an inconsistent state: creating an order and decrementing stock, moving a balance between two accounts, or writing a record and its child records together.
Send an array of operations. They run in order, in a single database transaction.
await swell.post('/:transaction', [
{ method: 'post', url: '/products', data: { name: 'Chair', price: 40 } },
{ method: 'put', url: '/products/6812a1f4c0', data: { stock_level: 12 } },
{ method: 'delete', url: '/products/6812a1f4c1' },
]);Each operation takes the following fields:
- url (required): The endpoint the operation writes to.
- method (optional): post, put, or delete. Defaults to the method of the transaction request itself.
- data (optional): The body for that operation.
Reads are not allowed inside a transaction—a get operation is rejected before anything runs. Each operation is permission-checked on its own, the same as if you had sent it as a standalone request.
Put the collection in the transaction URL and the operations inherit it:
await swell.post('/:transaction/products', [
{ method: 'post', data: { name: 'Chair', price: 40 } },
{ method: 'put', url: '6812a1f4c0', data: { stock_level: 12 } },
]);On success, the response is an array in the same order as the operations you sent. Each entry is what that operation would have returned on its own:
[
{ "id": "6812a1f4c2", "name": "Chair", "price": 40 },
{ "id": "6812a1f4c0", "name": "Desk", "stock_level": 12 }
]- Operations per transaction: 10
- Methods allowed: post, put, delete
- Time per operation: 2 seconds
- Time to commit: 2 seconds
- Concurrent transactions per store: 10
- API quota cost: 1 per operation, plus 1 for the transaction
Operations run one after another, not in parallel. Keep transactions small: they hold locks on every record they touch, so a long transaction makes conflicts more likely for everyone writing to the same records.
A failed transaction rolls back completely and returns a typed error:
{
"error": {
"message": "Transaction exceeded time budget",
"code": "transaction_timeout",
"status": 408
}
}- transaction_conflict (409): Another write touched the same records first. Retry with backoff.
- transaction_throttled (429): Too many transactions in flight for your store. Retry with backoff.
- transaction_timeout (408): An operation or the commit ran past its time budget. Not retryable.
- transaction_op_failed (the operation's own status): One operation was rejected. Fix the operation and send it again.
- transaction_error (400 for an invalid request body, 503 otherwise): The request body was rejected, or the database was unreachable.
When an operation fails, the error carries op_index, the zero-based position of the operation that caused the rollback:
{
"error": {
"message": "Product not found",
"code": "transaction_op_failed",
"status": 404,
"op_index": 1
}
}Retry transaction_conflict and transaction_throttled with exponential backoff. The other errors will fail the same way again.
Nothing is dispatched while a transaction is running. Webhooks and app functions make HTTP calls that can't be rolled back, so they are held back until the transaction is known to have committed.
- No per-record events (such as product.created) are written for operations inside a transaction.
- One transaction.committed event fires after a successful commit, listing every operation in the transaction.
- If the transaction rolls back, nothing fires at all.
Subscribe to transaction.committed when you need to react to the group as a whole.
Every transaction writes a log entry with its outcome, operation count, and duration. View them with the Swell CLI:
swell logs --type transaction