Importing Data
Bulk import accounts and transactions from external systems into Beetles.
Importing Data
Beetles supports bulk importing of accounts and transfers from external systems. This is useful for migrating from a legacy ledger, backfilling data, or batch-processing recent transactions.
How It Works
The import endpoints dispatch an asynchronous workflow that writes data to the ledger engine and database. This means:
- All items in a batch succeed or all fail — no partial imports.
- Each item is stamped with the provided timestamp as
created_atin the database, preserving your timeline for queries and ordering. - The ledger engine assigns its own internal timestamps for consistency — same behavior as single transfers.
- The API returns immediately with a
workflow_idso you can track progress.
Import is asynchronous — you receive a workflow_id and can poll for completion.
Import Order
When migrating from another ledger system, follow this order:
- Create ledgers first — each account belongs to a ledger.
- Import accounts — including at least one funding account (
allow_overdraft: true) that can act as a source of funds. - Import transfers — all referenced accounts must exist.
Importing Accounts
Endpoint
POST /v1/ledgers/{ledger_id}/accounts
Authorization: Bearer <token>
Beetles-Workspace-Id: <workspace-id>
Content-Type: application/jsonRequest Format
{
"accounts": [
{
"ledger_id": "019489a5-b3c4-7000-8000-000000000001",
"name": "Funding Source",
"code": 1,
"timestamp": "2024-01-01T00:00:01Z",
"allow_overdraft": true
},
{
"ledger_id": "019489a5-b3c4-7000-8000-000000000001",
"name": "Revenue Account",
"code": 5001,
"timestamp": "2024-01-01T00:00:02Z"
},
{
"ledger_id": "019489a5-b3c4-7000-8000-000000000001",
"name": "Expenses Account",
"code": 5002,
"timestamp": "2024-01-01T00:00:03Z"
}
],
"idempotency_key": "import-batch-jan-2024"
}Field Reference
| Field | Type | Required | Description |
|---|---|---|---|
ledger_id | UUID | * | Target ledger for the account. Mutually exclusive with ledger_label. |
ledger_label | string | * | Ledger label (case-insensitive). Mutually exclusive with ledger_id. |
name | string | Yes | Human-readable account name |
label | string | No | Optional label for later reference (case-insensitive, unique per ledger) |
code | integer | Yes | Account category code |
timestamp | string | Yes | RFC 3339 with timezone (e.g. 2024-01-01T00:00:01Z). Stored as created_at in the database for ordering. |
allow_overdraft | boolean | No | If true, debits may exceed credits (funding account). Default: false |
history | boolean | No | If true, enable balance history tracking in the ledger. Default: true |
idempotency_key | string | No | (Top-level field) Prevents duplicate execution on retries. Unique per workspace. |
* One of ledger_id or ledger_label is required.
Using Labels
You can reference a ledger by its label, and assign labels to accounts for later use in transfers:
{
"accounts": [
{
"ledger_label": "main-ledger",
"name": "Funding Source",
"label": "funding",
"code": 1,
"timestamp": "2024-01-01T00:00:01Z",
"allow_overdraft": true
},
{
"ledger_label": "main-ledger",
"name": "Revenue Account",
"label": "revenue",
"code": 5001,
"timestamp": "2024-01-01T00:00:02Z"
}
]
}Once accounts have labels, you can create transfers referencing them by debit_account_label / credit_account_label instead of UUIDs. See Transfers — Using Labels.
Response
{
"workflow_id": "import-accounts-019...",
"imported": 3,
"accounts": [
{
"id": "01948a1b-...",
"ledger_id": "019489a5-...",
"name": "Funding Source",
"code": 1,
"allow_overdraft": true,
"status": "active",
"created_at": "2024-01-01T00:00:01Z"
}
]
}Importing Transfers
Transfer imports are now handled through the unified POST /v1/transfers endpoint using the batch mode.
See the Transfers guide for full details, request format, and examples.
Validation Rules
These rules apply to account imports:
- Timestamps are required — every item must include a
timestampin RFC 3339 format with timezone offset. - Batch limit — maximum 1,000 items per request.
- Atomic — the entire batch is written in a single operation. If any item fails validation, none are committed.
Migration Guide
When migrating from another ledger system:
Create your ledger
via POST /v1/ledgers.
Import accounts
via POST /v1/ledgers/{id}/accounts with the accounts array. Include at least one funding account (allow_overdraft: true) if you need a source of funds that can go below zero.
Import transfers
via POST /v1/transfers with the transfers array. Sort by date and batch in chunks of ≤ 1,000.
Use metadata
store original IDs and source system references in the metadata field for traceability.
Track progress
use the workflow_id from each response to monitor import completion.
# Step 1: Import accounts
curl -X POST http://localhost:8080/v1/ledgers/$LEDGER_ID/accounts \
-H "Authorization: Bearer $TOKEN" \
-H "Beetles-Workspace-Id: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d @legacy_accounts.json
# Step 2: Import transfers (unified endpoint)
curl -X POST http://localhost:8080/v1/transfers \
-H "Authorization: Bearer $TOKEN" \
-H "Beetles-Workspace-Id: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d @legacy_transfers.json