Beetles Docs

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_at in 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_id so 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:

  1. Create ledgers first — each account belongs to a ledger.
  2. Import accounts — including at least one funding account (allow_overdraft: true) that can act as a source of funds.
  3. 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/json

Request Format

Import three historical accounts
{
  "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

FieldTypeRequiredDescription
ledger_idUUID*Target ledger for the account. Mutually exclusive with ledger_label.
ledger_labelstring*Ledger label (case-insensitive). Mutually exclusive with ledger_id.
namestringYesHuman-readable account name
labelstringNoOptional label for later reference (case-insensitive, unique per ledger)
codeintegerYesAccount category code
timestampstringYesRFC 3339 with timezone (e.g. 2024-01-01T00:00:01Z). Stored as created_at in the database for ordering.
allow_overdraftbooleanNoIf true, debits may exceed credits (funding account). Default: false
historybooleanNoIf true, enable balance history tracking in the ledger. Default: true
idempotency_keystringNo(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:

Import accounts with labels
{
  "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

202 Accepted
{
  "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:

  1. Timestamps are required — every item must include a timestamp in RFC 3339 format with timezone offset.
  2. Batch limit — maximum 1,000 items per request.
  3. 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.

Example: Import with cURL
# 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

On this page