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.
To start, clone the Blnk repository from GitHub:
git clone https://github.com/blnkfinance/blnk && cd blnkInstall the Blnk TypeScript SDK in your project:
npm install @blnkfinance/blnk-typescript@1.6.0 --savev1.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).
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": "jerryenebeli@gmail.com",
"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.
With Docker Compose, launch the Blnk server:
docker compose upOnce running, your server will be accessible at http://localhost:5001.
System.health checks whether Blnk Core is running (GET /health):
const health = await blnk.System.health();
// health.status === 200
// health.data?.status === 'UP'The Blnk CLI offers quick access to manage ledgers, balances, and transactions. To verify the installation and view available commands, use:
blnk --helpIn 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.
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.
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).
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);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 });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: 'jane@example.com',
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);| 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: "jane@example.com" }
// Detokenize a single field and read the original value.
const email = await Identity.detokenizeField(identity.data!.identity_id, 'EmailAddress');
// email.data?.value — e.g. "jane@example.com"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.
| 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.
Balances represent the store of value within a ledger, like a wallet or account. Each balance belongs to a ledger.
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
});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.balanceLedgerBalances.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.currencyLedgerBalances.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.messageLedgerBalances.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.messageLedgerBalances.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.timestampLedgerBalances.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| 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.
Transactions track financial activities within your application. Blnk ensures that each transaction is both immutable and idempotent.
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);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_applydry_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_idWhen 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.
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,
});Transactions.get retrieves a transaction by its transaction_id (GET /transactions/{transaction_id}):
const response = await Transactions.get('txn_04551509-d7d3-4eab-a1fd-2eb12809b5a4');Transactions.getByReference retrieves a transaction by its reference (GET /transactions/reference/{reference}):
const response = await Transactions.getByReference('ref_04551509-d7d3-4eab-a1fd-2eb12809b5a4');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.thresholdTransactions.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_transactionsCore 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,
});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_applyTransactions.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'],
});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 });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;
}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.
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
async createBulk<T extends Record<string, unknown>>(data: BulkTransactions<T>)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
}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 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);// 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);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
}The bulk API returns batch metadata (not nested transaction objects). See Bulk transaction response above for the typed shape.
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
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);
}Converting from single transactions to bulk transactions is straightforward:
const tx1 = await Transactions.create(transactionData1);
const tx2 = await Transactions.create(transactionData2);const bulkResponse = await Transactions.createBulk({
transactions: [transactionData1, transactionData2]
});This migration provides better performance and the option for atomic processing.
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
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.
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.
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 trueSee the Search via DB reference for supported operators and fields.
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.
| 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 |
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_atSee the Update matching rule reference.
const { Reconciliation } = blnk;
const deleted = await Reconciliation.deleteMatchingRule('rule_abc123');
// deleted.data?.message — "Matching rule deleted successfully"See the Delete matching rule reference.
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_transactionsSee the View reconciliation details reference.
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 webhooksSee the Start reconciliation reference.
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 IDSee the Instant reconciliation reference.
| Method | Endpoint | Use case |
|---|---|---|
Metadata.update(id, data) |
POST /{id}/metadata |
Add or update metadata on a ledger, transaction, balance, or identity |
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 CoreSee the Update metadata reference.
| 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.
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 IDSee the Register hooks reference.
const allHooks = await Hooks.list();
const preTxnHooks = await Hooks.list({ type: 'PRE_TRANSACTION' });See the List hooks by type reference.
const hookDetails = await Hooks.get(hook.data!.id);
// hookDetails.data?.name, hookDetails.data?.active, etc.See the View hooks reference.
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.
const deleted = await Hooks.delete(hook.data!.id);
// deleted.data?.message — "hook deleted successfully"See the Delete hooks reference.
| 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.
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 onceSee the Create API key reference.
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.
await ApiKeys.delete('api_key_abc123', { owner: 'merchant_a' });
// 204 No Content on success; response.data is nullSee the Revoke API key reference.
For more examples and advanced use cases, please refer to the Examples Code.
If you encounter any issues, please report them on GitHub.
