
Blnk TypeScript SDK Documentation
1. Installation
Prerequisites
Ensure that you have the following installed on your machine:
- Docker and Docker Compose for running Blnk’s server locally.
- Node.js (v14 or later) and npm for installing the Blnk TypeScript SDK.
Step 1: Clone the Blnk Repository
To start, clone the Blnk repository from GitHub:
git clone https://github.com/blnkfinance/blnk && cd blnk
Step 2: Install Blnk TypeScript SDK
Install the Blnk TypeScript SDK in your project:
npm install @blnkfinance/[email protected] --save
v1.6.0 targets Blnk Core 0.15.4. Cloud proxy instanceId is available in 1.6.0. See RELEASE.md for the error catalogue, Search.multiSearch, and list methods (Ledgers.list, LedgerBalances.list, Transactions.list, BalanceMonitor.listByBalanceId).
Step 3: Setting Up Configuration
In your cloned directory, create a configuration file named blnk.json with the following content:
{
"project_name": "Blnk",
"data_source": {
"dns": "postgres://postgres:password@postgres:5432/blnk?sslmode=disable"
},
"redis": {
"dns": "redis:6379"
},
"server": {
"domain": "blnk.io",
"ssl": false,
"ssl_email": "[email protected]",
"port": "5001"
},
"notification": {
"slack": {
"webhook_url": "https://hooks.slack.com"
}
}
}
This configuration sets up connections to PostgreSQL and Redis, specifies your server details, and allows Slack notifications if needed.
2. Launching Blnk
With Docker Compose, launch the Blnk server:
docker compose up
Once running, your server will be accessible at http://localhost:5001.
Health check
System.health checks whether Blnk Core is running (GET /health):
const health = await blnk.System.health();
// health.status === 200
// health.data?.status === 'UP'
3. Using the Blnk CLI
The Blnk CLI offers quick access to manage ledgers, balances, and transactions. To verify the installation and view available commands, use:
blnk --help
4. Creating Your First Ledger
What is a Ledger?
In Blnk, ledgers are used to categorize balances for organized tracking. When you first install Blnk, an internal ledger called the General Ledger is created by default.
Step-by-Step: Creating a Ledger
Using the SDK, create a ledger for user accounts:
import { BlnkInit } from '@blnkfinance/blnk-typescript';
const blnk = await BlnkInit('<secret_key_if_set>', { baseUrl: 'http://localhost:5001' });
const { Ledgers } = blnk;
const newLedger = await Ledgers.create({
name: "Customer Savings Account",
meta_data: {
project_owner: "YOUR_APP_NAME"
}
});
console.log("Ledger Created:", newLedger);
This creates a new ledger for storing customer balances.
Using Blnk Cloud proxy
To call Core through Cloud Proxy, pass your Cloud API key (or OAuth access token) and the Core instance_id. The SDK keeps the same Core request bodies and paths, prefixes them with /proxy, and sends instance_id as a query parameter.
import { BlnkInit, DEFAULT_CLOUD_API_BASE_URL } from '@blnkfinance/blnk-typescript';
const blnk = BlnkInit('<cloud_api_key>', {
baseUrl: DEFAULT_CLOUD_API_BASE_URL, // https://api.cloud.blnkfinance.com
instanceId: 'instance_...',
});
const newLedger = await blnk.Ledgers.create({
name: 'Customer Savings Account',
});
// POST https://api.cloud.blnkfinance.com/proxy/ledgers?instance_id=instance_...
instanceId is available in 1.6.0 and is required for proxy routing. Omit it when you talk to Core directly (baseUrl is your Core origin).
Updating a ledger name
Rename an existing ledger without changing its ID or affecting balances and transactions:
const updatedLedger = await Ledgers.update(
'ldg_073f7ffe-9dfd-42ce-aa50-d1dca1788adc',
{ name: 'Updated Customer Savings Account' },
);
console.log('Ledger Updated:', updatedLedger);
List ledgers, balances, and transactions
Ledgers.list, LedgerBalances.list, and Transactions.list wrap Core's GET /ledgers, GET /balances, and GET /transactions (present since ~0.14.x; this SDK is aligned with Core 0.15.4). Omit options to use Core's default page (limit=10 / offset=0 for ledgers and balances, limit=20 for transactions). Invalid limit or offset is rejected client-side with HTTP 400.
const firstPage = await Ledgers.list();
const nextPage = await Ledgers.list({ limit: 10, offset: 10 });
const balances = await LedgerBalances.list({ limit: 50 });
const recentTransactions = await Transactions.list({ limit: 100 });
5. Creating Identities
Register customers or organizations before linking them to balances. Only identity_type is required; all other fields are optional and match the Create Identity API reference. The SDK validates identity_type and field formats (for example identity_id, dob, gender) when you provide them.
Minimal create:
const { Identity } = blnk;
const minimal = await Identity.create({
identity_type: 'individual',
});
Full example with optional caller-supplied identity_id (idt_ + UUID) and ISO 8601 dob:
const { Identity } = blnk;
const newIdentity = await Identity.create({
identity_id: 'idt_11111111-1111-4111-8111-111111111111',
identity_type: 'individual',
first_name: 'Jane',
last_name: 'Doe',
gender: 'female',
dob: '1990-01-15T00:00:00Z',
email_address: '[email protected]',
phone_number: '+1234567890',
nationality: 'US',
category: 'customer',
street: '123 Main St',
country: 'USA',
state: 'NY',
post_code: '10001',
city: 'New York',
});
console.log('Identity Created:', newIdentity);
Tokenize identity fields
| Method | Endpoint | Use case |
|---|---|---|
Identity.getTokenizedFields(id) | GET /identities/{identity_id}/tokenized-fields | List fields currently tokenized on an identity |
Identity.tokenizeField(id, field) | POST /identities/{identity_id}/tokenize/{field} | Tokenize one PII field on an identity |
Identity.tokenize(id, data) | POST /identities/{identity_id}/tokenize | Tokenize multiple PII fields on an identity |
Identity.detokenize(id, data) | POST /identities/{identity_id}/detokenize | Detokenize fields and return original values |
Identity.detokenizeField(id, field) | GET /identities/{identity_id}/detokenize/{field} | Detokenize one field and return its original value |
const { Identity } = blnk;
const tokenizedFields = await Identity.getTokenizedFields(identity.data!.identity_id);
// tokenizedFields.data?.tokenized_fields — e.g. ["FirstName", "EmailAddress"]
// Tokenize a single field (PascalCase struct name in the path).
const oneField = await Identity.tokenizeField(identity.data!.identity_id, 'EmailAddress');
// oneField.data?.message — "Field tokenized successfully"
// Use PascalCase struct field names — not the snake_case JSON keys on IdentityData.
const tokenized = await Identity.tokenize(identity.data!.identity_id, {
fields: ['FirstName', 'LastName', 'EmailAddress', 'PhoneNumber'],
});
// tokenized.data?.message — "Fields tokenized successfully"
// Detokenize specific fields, or pass { fields: [] } to detokenize all tokenized fields.
const restored = await Identity.detokenize(identity.data!.identity_id, {
fields: ['FirstName', 'EmailAddress'],
});
// restored.data?.fields — e.g. { FirstName: "Jane", EmailAddress: "[email protected]" }
// Detokenize a single field and read the original value.
const email = await Identity.detokenizeField(identity.data!.identity_id, 'EmailAddress');
// email.data?.value — e.g. "[email protected]"
Field names must be Core struct names (
FirstName,EmailAddress, …). Passingfirst_nameoremail_addressfromIdentityDatawill be rejected.
See the Get tokenized fields reference, Tokenize field reference, Tokenize identity reference, Detokenize field reference, and Detokenize identity reference.
Delete identity
| Method | Endpoint | Use case |
|---|---|---|
Identity.delete(id) | DELETE /identities/{identity_id} | Remove an identity record (Core 0.15.0+) |
const { Identity } = blnk;
const deleted = await Identity.delete(identity.data!.identity_id);
// deleted.data?.message — "Identity deleted successfully"
See the Delete identity reference.
6. Creating Balances
Balances represent the store of value within a ledger, like a wallet or account. Each balance belongs to a ledger.
Step-by-Step: Creating a Balance
To create a balance, specify the ledger_id and other details:
const { LedgerBalances } = blnk;
const newBalance = await LedgerBalances.create({
ledger_id: "ldg_073f7ffe-9dfd-42ce-aa50-d1dca1788adc",
currency: "USD",
meta_data: {
first_name: "Alice",
last_name: "Hart",
account_number: "1234567890"
}
});
console.log("Balance Created:", newBalance);
Create a General Ledger balance on Core 0.15.3+ with ledger_id: "general_ledger_id" and an @ indicator:
const glBalance = await LedgerBalances.create({
ledger_id: "general_ledger_id",
currency: "USD",
indicator: "@WorldUSD",
});
With fund lineage tracking enabled (requires identity_id):
const lineageBalance = await LedgerBalances.create({
ledger_id: "ldg_073f7ffe-9dfd-42ce-aa50-d1dca1788adc",
identity_id: "idt_3b63c8da-af29-4cc3-ad38-df17d87456e6",
currency: "USD",
track_fund_lineage: true,
allocation_strategy: "FIFO", // FIFO | LIFO | PROPORTIONAL
});
Get balance
LedgerBalances.get retrieves a balance by ID (GET /balances/{balance_id}). Pass { from_source: true } to reconstruct the balance from transactions instead of snapshots:
const response = await LedgerBalances.get(
'bln_5ce86029-3c2e-4e2a-aae2-7fb931ca4c4f',
{ from_source: true },
);
// response.data.balance_id
// response.data.balance
Get balance by indicator
LedgerBalances.getByIndicator retrieves a balance by its indicator and currency (GET /balances/indicator/{indicator}/currency/{currency}):
const response = await LedgerBalances.getByIndicator('@World', 'USD');
// response.data.balance_id
// response.data.indicator
// response.data.currency
Update balance identity
LedgerBalances.updateIdentity links a balance to an identity (PUT /balances/{id}/identity):
const response = await LedgerBalances.updateIdentity(
'bln_5ce86029-3c2e-4e2a-aae2-7fb931ca4c4f',
{ identity_id: 'idt_3b63c8da-af29-4cc3-ad38-df17d87456e6' },
);
// response.data.message
Create balance snapshots
LedgerBalances.createSnapshot triggers daily balance snapshots in batches (POST /balances-snapshots). Omit batch_size or pass zero to use the server default (1000):
const response = await LedgerBalances.createSnapshot({ batch_size: 500 });
// response.data.message
Get historical balance
LedgerBalances.getAt retrieves a balance at a specific timestamp (GET /balances/{balance_id}/at):
const response = await LedgerBalances.getAt(
'bln_5ce86029-3c2e-4e2a-aae2-7fb931ca4c4f',
{ timestamp: '2025-02-24T08:55:26Z', from_source: true },
);
// response.data.balance.balance_id
// response.data.balance.balance
// response.data.timestamp
Get balance lineage
LedgerBalances.getLineage retrieves the provider breakdown for a balance with fund lineage enabled (GET /balances/{balance_id}/lineage):
const response = await LedgerBalances.getLineage(
'bln_5ce86029-3c2e-4e2a-aae2-7fb931ca4c4f',
);
// response.data.balance_id
// response.data.total_with_lineage
// response.data.providers
Delete balance monitor
| Method | Endpoint | Use case |
|---|---|---|
BalanceMonitor.list() | GET /balance-monitors | List all balance monitors |
BalanceMonitor.listByBalanceId(id) | GET /balance-monitors/balances/{balance_id} | List monitors for one balance |
BalanceMonitor.delete(id) | DELETE /balance-monitors/{monitor_id} | Remove a balance monitor (Core 0.15.0+) |
const { BalanceMonitor } = blnk;
const forBalance = await BalanceMonitor.listByBalanceId(
'bln_5ce86029-3c2e-4e2a-aae2-7fb931ca4c4f',
);
// forBalance.data — MonitorDataResp[]
const deleted = await BalanceMonitor.delete(monitor.data!.monitor_id);
// deleted.data?.message — "BalanceMonitor deleted successfully"
See the Delete balance monitor reference.
6. Recording Transactions
Transactions track financial activities within your application. Blnk ensures that each transaction is both immutable and idempotent.
Step-by-Step: Recording a Transaction
To record a transaction, you’ll need the source and destination balance IDs:
const { Transactions } = blnk;
const newTransaction = await Transactions.create({
amount: 750,
reference: "ref_001adcfgf",
currency: "USD",
precision: 100,
source: "bln_28edb3e5-c168-4127-a1c4-16274e7a28d3",
destination: "bln_ebcd230f-6265-4d4a-a4ca-45974c47f746",
description: "Sent from app",
meta_data: {
sender_name: "John Doe",
sender_account: "00000000000"
}
});
console.log("Transaction Recorded:", newTransaction);
Dry-run a transaction
On Core 0.15.3+, set dry_run: true on create, bulk create, refund, inflight updateStatus, bulkCommitInflight, or bulkVoidInflight to preview balances without writing anything. The response is HTTP 200 with would_apply and balances — not a posted transaction.
const preview = await Transactions.create({
amount: 120,
precision: 100,
reference: 'ref_card_settle_4821',
description: 'Card settlement preview',
currency: 'USD',
source: '@WorldUSD',
destination: '@MyBalance',
dry_run: true,
});
// preview.status === 200
// preview.data?.dry_run === true
// preview.data?.would_apply
dry_run is discriminated so a preview is never typed as a posted transaction. Requests typed with a plain request type keep the posted response, so existing code is unaffected:
const body: CreateTransactions<Meta> = {/* ... */};
const posted = await Transactions.create(body);
posted.data?.transaction_id; // still typed
// Preview: pass the flag inline, or type the body as DryRun<...>
const previewBody: DryRun<CreateTransactions<Meta>> = {...body, dry_run: true};
const preview = await Transactions.create(previewBody);
preview.data?.would_apply; // TransactionPreview — no transaction_id
When the flag is only known at runtime the response widens to the posted-or-preview union, so narrow before reading posted-only fields:
const result = await Transactions.create({...body, dry_run: shouldPreview});
if (result.data && 'transaction_id' in result.data) {
console.log('posted', result.data.transaction_id);
} else if (result.data) {
console.log('preview', result.data.would_apply);
}
See Dry-run transactions.
Atomic split transactions
Set atomic: true when creating a split transaction (destinations or sources) so all legs succeed or fail together:
const response = await Transactions.create({
amount: 1000,
precision: 100,
reference: 'atomic_split_ref_001',
description: 'Atomic fee split',
currency: 'USD',
source: '@FundingPool',
destinations: [
{ identifier: 'bln_fee', distribution: '240.23' },
{ identifier: 'bln_recipient', distribution: 'left' },
],
atomic: true,
skip_queue: true,
});
Get transaction by ID
Transactions.get retrieves a transaction by its transaction_id (GET /transactions/{transaction_id}):
const response = await Transactions.get('txn_04551509-d7d3-4eab-a1fd-2eb12809b5a4');
Get transaction by reference
Transactions.getByReference retrieves a transaction by its reference (GET /transactions/reference/{reference}):
const response = await Transactions.getByReference('ref_04551509-d7d3-4eab-a1fd-2eb12809b5a4');
Recover queued transactions
Transactions.recoverQueue manually triggers recovery of stuck queued transactions (POST /transactions/recover). Optionally pass a threshold duration query (e.g. 5m, 1h):
const response = await Transactions.recoverQueue({ threshold: '5m' });
// response.data.recovered, response.data.threshold
Get transaction lineage
Transactions.getLineage retrieves fund allocation and shadow transactions for a transaction (GET /transactions/{transaction_id}/lineage):
const response = await Transactions.getLineage('txn_8d2ce2f0-0d75-4a91-9d43-2ad2c2e6b9ad');
// response.data.transaction_id
// response.data.fund_allocation
// response.data.shadow_transactions
Update inflight transaction status
Core 0.15.0 queues inflight commit and void by default. The response includes queued: true (status is typically QUEUED until the worker processes the action). Send skip_queue: true for immediate APPLIED or VOID responses (previous synchronous behavior).
Transactions.updateStatus accepts precise_amount for partial commits on inflight transactions (in addition to amount). Omit both fields to commit the full remaining inflight amount:
// Queued commit (default in Core 0.15.0)
const queued = await Transactions.updateStatus(transactionId, { status: 'commit' });
// queued.data?.queued === true
// Synchronous commit
await Transactions.updateStatus(transactionId, {
status: 'commit',
skip_queue: true,
});
// Partial commit in minor units
await Transactions.updateStatus(transactionId, {
status: 'commit',
precise_amount: 50000,
skip_queue: true,
});
Bulk commit inflight transactions
Transactions.bulkCommitInflight commits multiple independently-created inflight transactions in one request (POST /transactions/inflight/bulk/commit). By default, each item is queued (status: 'queued' in results). Pass skip_queue: true for synchronous processing. Omit amount and precise_amount on an item to commit the full remaining inflight amount:
const response = await Transactions.bulkCommitInflight({
skip_queue: true,
transactions: [
{ transaction_id: 'txn_11111111-1111-4111-8111-111111111111' },
{ transaction_id: 'txn_22222222-2222-4222-8222-222222222222', amount: 40 },
{
transaction_id: 'txn_33333333-3333-4333-8333-333333333333',
precise_amount: 125034,
},
],
});
// response.data.succeeded, response.data.failed, response.data.results
const commitPreview = await Transactions.bulkCommitInflight({
dry_run: true,
transactions: [
{ transaction_id: 'txn_11111111-1111-4111-8111-111111111111' },
],
});
// commitPreview.data?.dry_run === true
// commitPreview.data?.would_apply
Bulk void inflight transactions
Transactions.bulkVoidInflight voids multiple independently-created inflight transactions in one request (POST /transactions/inflight/bulk/void). By default, each item is queued. Pass skip_queue: true for synchronous void:
const response = await Transactions.bulkVoidInflight({
skip_queue: true,
transaction_ids: [
'txn_11111111-1111-4111-8111-111111111111',
'txn_22222222-2222-4222-8222-222222222222',
],
});
// response.data.succeeded, response.data.failed, response.data.results
const voidPreview = await Transactions.bulkVoidInflight({
dry_run: true,
transaction_ids: ['txn_11111111-1111-4111-8111-111111111111'],
});
Refund a transaction
Transactions.refund accepts an optional body with skip_queue to process the refund synchronously. On Core 0.15.3+ you can also set description, meta_data, and dry_run. Omit the body to queue the refund (default):
// Queued refund (default)
await Transactions.refund(transactionId);
// Synchronous refund
await Transactions.refund(transactionId, { skip_queue: true });
// Refund with description and metadata
await Transactions.refund(transactionId, {
description: 'Card reversal',
meta_data: { type: 'refund' },
});
// Preview without writing
await Transactions.refund(transactionId, { dry_run: true });
Create transaction response
Transactions.create resolves to a CreateTransactionResponse that matches the Core API reference, including hash, parent_transaction, allow_overdraft, and inflight date fields (scheduled_for, inflight_expiry_date, inflight_commit_date):
interface CreateTransactionResponse<T extends Record<string, unknown>> {
transaction_id: string;
amount: number;
precision: number;
precise_amount: number | string;
reference: string;
description: string;
rate?: number;
currency: string;
status: StatusType;
hash: string;
parent_transaction: string; // empty string when none
allow_overdraft: boolean;
inflight: boolean;
created_at: Date | string;
scheduled_for: Date | string;
inflight_expiry_date: Date | string;
inflight_commit_date: Date | string;
effective_date?: Date | string;
source?: string;
destination?: string;
meta_data?: T;
}
7. Bulk Transactions
The Blnk JavaScript SDK supports bulk transactions, allowing you to submit multiple transaction records in a single request for improved performance and atomic transaction processing.
Overview
The bulk transactions API provides the following benefits:
- Performance: Submit multiple transactions in one API call
- Atomicity: Ensure all transactions succeed or fail together (when
atomic: true) - Async Processing: Process large batches asynchronously
- Inflight Support: Create multiple inflight transactions that can be committed or voided later
Method Signature
async createBulk<T extends Record<string, unknown>>(data: BulkTransactions<T>)
Parameters
BulkTransactions Interface
interface BulkTransactions<T extends Record<string, unknown>> {
atomic?: boolean; // Optional: All transactions succeed or fail together
inflight?: boolean; // Optional: Create transactions as inflight
run_async?: boolean; // Optional: Process transactions asynchronously
skip_queue?: boolean; // Optional: Process without queuing
transactions: CreateTransactions<T>[]; // Required: Array of transaction objects
}
Usage Examples
Basic Bulk Transactions
const { Transactions } = blnk;
// Basic bulk transactions without additional options
const basicBulkData = {
transactions: [
{
amount: 1000,
precision: 100,
reference: 'bulk_txn_001',
description: 'Payment 1',
currency: 'USD',
source: '@source_account_1',
destination: '@destination_account_1',
},
{
amount: 2000,
precision: 100,
reference: 'bulk_txn_002',
description: 'Payment 2',
currency: 'USD',
source: '@source_account_2',
destination: '@destination_account_2',
},
],
};
const response = await Transactions.createBulk(basicBulkData);
console.log('Bulk transaction response:', response);
Atomic Bulk Transactions
// Atomic transactions - all succeed or all fail
const atomicBulkData = {
atomic: true,
transactions: [
{
amount: 5000,
precision: 100,
reference: 'atomic_txn_001',
description: 'Atomic payment 1',
currency: 'USD',
source: '@source_account_1',
destination: '@destination_account_1',
},
{
amount: 3000,
precision: 100,
reference: 'atomic_txn_002',
description: 'Atomic payment 2',
currency: 'USD',
source: '@source_account_2',
destination: '@destination_account_2',
},
],
};
const atomicResponse = await Transactions.createBulk(atomicBulkData);
Async Bulk Transactions with All Options
// Process transactions asynchronously with atomic and inflight options
const asyncBulkData = {
atomic: true,
inflight: true,
run_async: true,
transactions: [
{
amount: 12000,
precision: 100,
reference: 'async_txn_001',
description: 'Async atomic inflight payment 1',
currency: 'USD',
source: '@source_account_1',
destination: '@destination_account_1',
allow_overdraft: true,
inflight_expiry_date: new Date(Date.now() + 48 * 60 * 60 * 1000), // 48 hours
meta_data: {
department: 'sales',
project: 'Q4_campaign',
},
},
{
amount: 8500,
precision: 100,
reference: 'async_txn_002',
description: 'Async atomic inflight payment 2',
currency: 'USD',
source: '@source_account_2',
destination: '@destination_account_2',
allow_overdraft: true,
inflight_expiry_date: new Date(Date.now() + 48 * 60 * 60 * 1000), // 48 hours
meta_data: {
department: 'marketing',
project: 'Q4_campaign',
},
},
],
};
const asyncResponse = await Transactions.createBulk(asyncBulkData);
Bulk transaction response
Transactions.createBulk resolves to a BulkTransactionResponse that matches the Core API reference (batch_id, status, transaction_count, and optional message for async batches):
interface BulkTransactionResponse {
batch_id: string;
status: 'applied' | 'inflight' | 'queued' | string;
transaction_count?: number; // present on synchronous success
message?: string; // present when run_async is true
}
Response Format
The bulk API returns batch metadata (not nested transaction objects). See Bulk transaction response above for the typed shape.
Validation Rules
The bulk transactions API validates the following:
- Required Fields:
transactionsarray must be provided and cannot be empty - Max Size:
transactionsarray cannot exceed 10,000 items - Transaction Validation: Each transaction in the array must pass standard transaction validation
- Unique References: All transaction references must be unique within the bulk request
- Boolean Flags:
atomic,inflight,run_async, andskip_queuemust be booleans if provided - Standard Transaction Rules: All existing transaction validation rules apply to each transaction
Error Handling
Compare response.error?.code against the constants in BlnkErrorCode, which mirror the full Core 0.15.4 catalogue (TXN_ALREADY_REFUNDED, BAL_NOT_FOUND, TXN_INSUFFICIENT_FUNDS, TXN_DUPLICATE_REFERENCE, LGR_NOT_FOUND, and so on):
import {BlnkErrorCode} from '@blnkfinance/blnk-typescript';
const refund = await Transactions.refund(transactionId);
if (refund.error?.code === BlnkErrorCode.TXN_ALREADY_REFUNDED) {
// 409: already refunded, or this id is itself a refund — nothing to do
}
try {
const response = await Transactions.createBulk(bulkData);
if (response.status === 201) {
console.log('Bulk transactions created successfully:', response.data);
} else {
console.error('Bulk transaction error:', response.message);
}
} catch (error) {
console.error('Network or system error:', error);
}
Migration from Single Transactions
Converting from single transactions to bulk transactions is straightforward:
Before (Single Transactions)
const tx1 = await Transactions.create(transactionData1);
const tx2 = await Transactions.create(transactionData2);
After (Bulk Transactions)
const bulkResponse = await Transactions.createBulk({
transactions: [transactionData1, transactionData2]
});
This migration provides better performance and the option for atomic processing.
8. Viewing Ledgers, Balances, and Transactions
The Blnk CLI allows you to list all ledgers, balances, and transactions quickly:
- List Ledgers:
blnk ledgers list - List Balances:
blnk balances list - List Transactions:
blnk transactions list
Search
Blnk search APIs on the Search service:
| Method | Endpoint | Use case |
|---|---|---|
Search.search(params, collection) | POST /search/{collection} | Full-text search via Typesense |
Search.multiSearch(params) | POST /multi-search | Several Typesense searches in one request (Core 0.10.0+) |
Search.filter(params, collection) | POST /{collection}/filter | Structured DB filters (Core 0.13.2+) |
Search.startReindex(options?) | POST /search/reindex | Rebuild Typesense index from DB |
Search.getReindexStatus() | GET /search/reindex | Poll reindex progress |
Collections: ledgers, balances, transactions, identities.
Typesense search
const { Search } = blnk;
const results = await Search.search(
{ q: 'payment', per_page: 10 },
'transactions',
);
Search several collections in one request (POST /multi-search; results come back
in the same order as the searches):
const { Search } = blnk;
const multi = await Search.multiSearch({
searches: [
{ collection: 'transactions', q: 'ref_001', query_by: 'reference' },
{ collection: 'balances', q: '*', filter_by: 'currency:USD' },
{ collection: 'ledgers', q: 'savings', query_by: 'name' },
],
});
const transactionHits = multi.data?.results[0]?.hits;
Requires Blnk Core v0.10.0+ (router.POST("/multi-search", a.MultiSearch) in
api/api.go). This
SDK is aligned with Core 0.15.4.
DB filter
const { Search } = blnk;
const filtered = await Search.filter(
{
filters: [{ field: 'status', operator: 'eq', value: 'APPLIED' }],
logical_operator: 'and',
sort_by: 'created_at',
sort_order: 'desc',
include_count: true,
limit: 20,
offset: 0,
},
'transactions',
);
// filtered.data?.data — matching transaction records
// filtered.data?.total_count — present when include_count is true
See the Search via DB reference for supported operators and fields.
Typesense reindex
const { Search } = blnk;
const reindex = await Search.startReindex({ batch_size: 1000 });
// reindex.data?.message — "Reindex operation started"
// reindex.data?.progress.status — "pending" | "in_progress" | "completed" | "failed"
See the Start reindex reference.
Poll progress after starting a reindex:
const status = await Search.getReindexStatus();
// status.data?.status — "in_progress" | "completed" | "failed"
// status.data?.phase — e.g. "indexing_transactions" or "done"
See the Get reindex status reference.
Reconciliation
| Method | Endpoint | Use case |
|---|---|---|
Reconciliation.upload(file, source) | POST /reconciliation/upload | Upload external data file |
Reconciliation.createMatchingRule(data) | POST /reconciliation/matching-rules | Define match criteria |
Reconciliation.updateMatchingRule(id, data) | PUT /reconciliation/matching-rules/{rule_id} | Update an existing matching rule |
Reconciliation.deleteMatchingRule(id) | DELETE /reconciliation/matching-rules/{rule_id} | Remove a matching rule |
Reconciliation.run(data) | POST /reconciliation/start | Start batch reconciliation from upload |
Reconciliation.runInstant(data) | POST /reconciliation/start-instant | Reconcile inline external transactions |
Reconciliation.get(id) | GET /reconciliation/{reconciliation_id} | View reconciliation status and counts |
Update a matching rule
const { Reconciliation } = blnk;
const updated = await Reconciliation.updateMatchingRule('rule_abc123', {
name: 'Updated matcher',
description: 'Amount with 2% drift matcher',
criteria: [
{ field: 'amount', operator: 'equals', allowable_drift: 0.02 },
{ field: 'currency', operator: 'equals' },
],
});
// updated.data?.rule_id, updated.data?.updated_at
See the Update matching rule reference.
Delete a matching rule
const { Reconciliation } = blnk;
const deleted = await Reconciliation.deleteMatchingRule('rule_abc123');
// deleted.data?.message — "Matching rule deleted successfully"
See the Delete matching rule reference.
Get reconciliation status
const { Reconciliation } = blnk;
const status = await Reconciliation.get('recon_3803ea0d-28b4-4c73-a36b-5a9eb7a3edfd');
// status.data?.status — e.g. started, in_progress, completed, failed
// status.data?.matched_transactions, unmatched_transactions
See the View reconciliation details reference.
Start batch reconciliation
Reconciliation.run starts reconciliation from a prior upload (POST /reconciliation/start). Core 0.15.0 returns only a reconciliation ID — poll Reconciliation.get(id) or listen for reconciliation.completed / reconciliation.failed webhooks for results.
const { Reconciliation } = blnk;
const started = await Reconciliation.run({
upload_id: upload.data!.upload_id,
strategy: 'one_to_one',
dry_run: true,
grouping_criteria: 'amount',
matching_rule_ids: [rule.data!.rule_id],
});
// started.data?.reconciliation_id — use with Reconciliation.get() or webhooks
See the Start reconciliation reference.
Instant reconciliation
const { Reconciliation } = blnk;
const instant = await Reconciliation.runInstant({
external_transactions: [
{
id: 'txn_1',
amount: 5.49,
reference: 'INV-2023-002',
currency: 'GBP',
description: 'Card payment',
date: '2024-11-15T14:25:30Z',
source: 'bank-api',
},
],
strategy: 'one_to_one',
dry_run: true,
matching_rule_ids: ['rule_abc123'],
});
// instant.data?.reconciliation_id — reconciliation run ID
See the Instant reconciliation reference.
Metadata
| Method | Endpoint | Use case |
|---|---|---|
Metadata.update(id, data) | POST /{id}/metadata | Add or update metadata on a ledger, transaction, balance, or identity |
Update metadata
const { Metadata } = blnk;
const updated = await Metadata.update('ldg_073f7ffe-9dfd-42ce-aa50-d1dca1788adc', {
meta_data: {
project_owner: 'Acme LLC',
update_status: 'Approved',
},
});
// updated.data?.meta_data — merged metadata from Core
See the Update metadata reference.
Hooks
| Method | Endpoint | Use case |
|---|---|---|
Hooks.create(data) | POST /hooks | Register a pre- or post-transaction webhook |
Hooks.list(options?) | GET /hooks | List hooks, optionally by type query |
Hooks.get(id) | GET /hooks/{id} | View hook details |
Hooks.update(id, data) | PUT /hooks/{id} | Update an existing webhook |
Hooks.delete(id) | DELETE /hooks/{id} | Delete a webhook |
Hook management requires the master key (
server.secret_key) inX-Blnk-Key. Regular API keys return403.
Register a hook
const { Hooks } = blnk;
const hook = await Hooks.create({
name: 'Pre-transaction validation',
url: 'https://api.example.com/validate',
type: 'PRE_TRANSACTION',
active: true,
timeout: 30,
retry_count: 3,
});
// hook.data?.id — registered hook ID
See the Register hooks reference.
List hooks
const allHooks = await Hooks.list();
const preTxnHooks = await Hooks.list({ type: 'PRE_TRANSACTION' });
See the List hooks by type reference.
View a hook
const hookDetails = await Hooks.get(hook.data!.id);
// hookDetails.data?.name, hookDetails.data?.active, etc.
See the View hooks reference.
Update a hook
const updated = await Hooks.update(hook.data!.id, {
name: 'Pre-transaction validation (updated)',
url: 'https://api.example.com/validate-v2',
type: 'PRE_TRANSACTION',
active: false,
timeout: 45,
retry_count: 5,
});
See the Update hooks reference.
Delete a hook
const deleted = await Hooks.delete(hook.data!.id);
// deleted.data?.message — "hook deleted successfully"
See the Delete hooks reference.
API Keys
| Method | Endpoint | Use case |
|---|---|---|
ApiKeys.create(data) | POST /api-keys | Create a scoped API key |
ApiKeys.list(options?) | GET /api-keys | List API keys for an owner |
ApiKeys.delete(id, options?) | DELETE /api-keys/{id} | Revoke an API key |
API key management requires the master key or scoped permissions (
api-keys:writeto create,api-keys:readto list,api-keys:deleteto revoke). The rawkeyvalue is only returned once at creation.
Create an API key
const { ApiKeys } = blnk;
const apiKey = await ApiKeys.create({
name: 'Service Account',
owner: 'merchant_a',
scopes: ['ledgers:read', 'balances:write'],
expires_at: '2026-03-11T00:00:00Z',
});
// apiKey.data?.key — store securely; shown only once
See the Create API key reference.
List API keys
const keys = await ApiKeys.list({ owner: 'merchant_a' });
// keys.data?.[0]?.api_key_id, keys.data?.[0]?.scopes, etc.
See the List API keys reference.
Revoke an API key
await ApiKeys.delete('api_key_abc123', { owner: 'merchant_a' });
// 204 No Content on success; response.data is null
See the Revoke API key reference.
Additional Resources
For more examples and advanced use cases, please refer to the Examples Code.
Issue Reporting
If you encounter any issues, please report them on GitHub.