The _bulk endpoint allows for efficient processing of multiple requests in a single operation. It supports streaming, parallel or sequential processing, and atomic execution.
Request formats#
Non-streaming#
For standard bulk requests, send operations as a JSON array with Content-Type: application/json:
[
{"action": "CREATE_TRANSACTION", "data": {"postings": [{"source": "world", "amount": 100, "asset": "USD", "destination": "alice"}]}},
{"action": "CREATE_TRANSACTION", "data": {"postings": [{"source": "world", "amount": 200, "asset": "USD", "destination": "bob"}]}}
]By default, the ledger has a maximum bulk transaction size of 100 items. This limitation helps prevent timeouts when processing large bulk requests.
Streaming#
Bulk requests can be streamed without requiring the entire request to be loaded into memory.
Results are kept in memory until the full stream is complete, which may result in large responses for big datasets. Consider breaking very large operations into smaller batches.
For processing more than 100 items, we recommend using streaming mode instead of increasing the bulk size limit.
To enable streaming, include one of the following content type headers in your HTTP request:
- For a script stream, include content type
application/vnd.formance.ledger.api.v2.bulk+script-stream - For a JSON stream, include content type
application/vnd.formance.ledger.api.v2.bulk+json-stream
Script stream format
For a script stream, each Numscript transaction must be wrapped with //script and //end delimiters:
//script
send [USD 100] (
source = @world
destination = @alice
)
//end
//script
send [USD 100] (
source = @world
destination = @bob
)
//endJSON stream format
For a JSON stream, send an array of elements in the request body. Example:
[
{"action": "CREATE_TRANSACTION", "data": {"postings": [{"source": "world", "amount": 100, "asset": "USD", "destination": "bank"}]}},
{"action": "CREATE_TRANSACTION", "data": {"postings": [{"source": "world", "amount": 200, "asset": "USD", "destination": "bank"}]}}
]Available actions for JSON bulk operations (script streams support CREATE_TRANSACTION only):
| Action | Description |
|---|---|
CREATE_TRANSACTION | Creates a new transaction in the ledger. Accepts postings or a Numscript (ScriptV1) script, along with optional timestamp, reference, metadata, account metadata, runtime type, and a force flag. Returns the created transaction and its log ID. |
ADD_METADATA | Adds or updates metadata on a target resource — either an account (by address) or a transaction (by numeric ID). Specify the target using 'targetType' and 'targetId' fields. Returns the log ID of the operation. |
REVERT_TRANSACTION | Reverts a previously committed transaction by its numeric ID, creating a compensating transaction. Supports a 'force' flag to bypass checks and an 'atEffectiveDate' flag to use the original transaction's effective date. Returns the revert transaction and its log ID. |
DELETE_METADATA | Deletes a specific metadata key from a target resource — either an account (by address) or a transaction (by numeric ID). Specify the target using 'targetType', 'targetId', and the 'key' to remove. Returns the log ID of the operation. |
Processing options#
The bulk endpoint accepts the following query parameters:
| Name | Type | Default | Description |
|---|---|---|---|
continueOnFailure | boolean | Continue on failure | |
atomic | boolean | Make bulk atomic | |
parallel | boolean | Process bulk elements in parallel | |
schemaVersion | string | Default schema version to use for validation (can be overridden per element) |
You cannot set parallel=true and atomic=true at the same time.
The continueOnFailure parameter only applies when parallel=false. In parallel mode, elements are processed independently regardless of this setting.
Example with continue on failure:
curl -X POST $FORMANCE_API_URL/api/ledger/v2/my-ledger/_bulk?parallel=false&continueOnFailure=trueThis is useful when you want to process as many elements as possible and handle failures separately, rather than stopping the entire batch on the first error.
Idempotency#
Idempotency keys prevent duplicate transactions when replaying bulk requests after failures. Each bulk element can specify its own key.
For script streams, add ik=<key> to the script header:
//script ik=transaction-001
send [USD 100] (
source = @world
destination = @alice
)
//endFor JSON streams, add the ik field to each element:
{"action": "CREATE_TRANSACTION", "ik": "transaction-001", "data": {"postings": [{"source": "world", "amount": 100, "asset": "USD", "destination": "bank"}]}}Detecting duplicate requests
When a request uses an idempotency key that was already processed, the API returns the original response with an Idempotency-Hit: true header. This helps clients distinguish between new and replayed requests.
This header is also returned on individual API endpoints (not just bulk) when using the Idempotency-Key request header.
Configuration#
Bulk size limits#
The 100 item limit applies only to non-streaming requests. If your use case requires a larger limit, you can configure it:
ledger.api.bulk-max-size100default: noneintMaximum number of operations allowed in a single bulk API request to the ledger; requests exceeding this limit are rejected.
Increasing the bulk size does not necessarily improve write performance. Test different values to find the optimal setting for your use case.
Examples#
Script streaming#
To send a bulk request with script streaming:
curl -X POST $FORMANCE_API_URL/api/ledger/v2/my-ledger/_bulk?parallel=true \
-H "Content-Type: application/vnd.formance.ledger.api.v2.bulk+script-stream" \
--data-binary @transactions.nsJSON streaming#
To send a bulk request with JSON streaming:
curl -X POST $FORMANCE_API_URL/api/ledger/v2/my-ledger/_bulk?parallel=true \
-H "Content-Type: application/vnd.formance.ledger.api.v2.bulk+json-stream" \
--data-binary @transactions.jsonFor more details on the bulk API parameters and response format, see the API Reference.