Node.js client for the Bitfinex v1 API — public and authenticated REST plus the v1 WebSocket endpoint — and the v2 public REST + v2 authenticated REST + v2 WebSocket endpoints.
npm install bitfinex-node-api
import { PublicClient } from "bitfinex-node-api";
const client = new PublicClient();
const ticker = await client.getTicker({ symbol: "BTCUSD" });
const stats = await client.getStats({ symbol: "BTCUSD" });
const book = await client.getFundingBook({
currency: "USD",
limit_bids: 10,
limit_asks: 5,
});
const book = await client.getOrderBook({
symbol: "BTCUSD",
limit_bids: 20,
limit_asks: 10,
group: 1,
});
const trades = await client.getTrades({
symbol: "BTCUSD",
timestamp: 1444266681,
limit_trades: 10,
});
const lends = await client.getLends({
currency: "USD",
timestamp: 1444266681,
limit_lends: 10,
});
const symbols = await client.getSymbols();
const details = await client.getSymbolDetails();
Client for the
Bitfinex v2 public REST
endpoints (https://api-pub.bitfinex.com/v2/).
Positional arrays are decoded into typed objects with named fields.
Trading and funding pairs share the same endpoints; the decoded
payload is discriminated by a type field. Trading
symbols start with t (e.g. tBTCUSD);
funding currencies start with f (e.g.
fUSD).
import { PublicClientV2 } from "bitfinex-node-api";
const client = new PublicClientV2();
const { status } = await client.getPlatformStatus(); // 0 = maintenance, 1 = operative
const ticker = await client.getTicker({ symbol: "tBTCUSD" });
// { type: "trading_ticker", symbol, bid, bid_size, ask, ask_size,
// daily_change, daily_change_relative, last_price, volume, high, low }
const funding = await client.getTicker({ symbol: "fUSD" });
// { type: "funding_ticker", symbol, frr, bid, bid_period, bid_size,
// ask, ask_period, ask_size, daily_change, daily_change_relative,
// last_price, volume, high, low, frr_amount_available }
const tickers = await client.getTickers({ symbols: ["tBTCUSD", "fUSD"] });
const all = await client.getTickers({ symbols: "ALL" });
const history = await client.getTickersHistory({
symbols: ["tBTCUSD"],
limit: 100,
});
const trades = await client.getTrades({
symbol: "tBTCUSD",
limit: 100,
sort: -1,
});
// [{ type: "trading_trade", id, mts, amount, price }, ...]
// or for funding: [{ type: "funding_trade", id, mts, amount, rate, period }, ...]
const book = await client.getBook({
symbol: "tBTCUSD",
precision: "P0",
len: 25,
});
// Aggregated trading: { type: "book", price, count, amount }
// Raw trading (R0): { type: "raw_book", order_id, price, amount }
// Aggregated funding: { type: "funding_book", rate, period, count, amount }
// Raw funding (R0): { type: "raw_funding_book", offer_id, period, rate, amount }
const last = await client.getStats({
key: "pos.size",
size: "1m",
symbol: "tBTCUSD",
side: "long",
section: "last",
});
const hist = await client.getStats({
key: "funding.size",
size: "1m",
symbol: "fUSD",
section: "hist",
limit: 100,
});
const last = await client.getCandles({
timeframe: "1D",
symbol: "tBTCUSD",
section: "last",
});
const hist = await client.getCandles({
timeframe: "1m",
symbol: "fUSD",
section: "hist",
aggr: 30,
period_start: "2",
period_end: "30",
limit: 100,
});
const configs = await client.getConfigs({
configs: ["pub:list:currency", "pub:list:pair:exchange"],
});
const status = await client.getDerivativesStatus({ keys: ["tBTCF0:USTF0"] });
const history = await client.getDerivativesStatusHistory({
key: "tBTCF0:USTF0",
limit: 100,
});
const liquidations = await client.getLiquidations({ limit: 50 });
const board = await client.getLeaderboards({
key: "plu",
timeframe: "3h",
symbol: "tBTCUSD",
limit: 25,
});
const stats = await client.getFundingStats({ symbol: "fUSD", limit: 100 });
const vasps = await client.getVASPs(); // [{ id, name }, ...]
const avg = await client.getMarketAveragePrice({
symbol: "tBTCUSD",
amount: "1.0",
});
// { rate_avg, amount }
const fx = await client.getForeignExchangeRate({ ccy1: "BTC", ccy2: "USD" });
// { current_rate }
When a v2 endpoint replies with
["error", CODE, MESSAGE] (the documented
maintenance error
["error", 20060, "maintenance"] is
one example), PublicClientV2 detects the envelope in
both get and post and rejects with a
BitfinexError so decoders never run on the malformed
body.
import { BitfinexError, PublicClientV2 } from "bitfinex-node-api";
const client = new PublicClientV2();
try {
await client.getBook({ symbol: "tBTCUSD" });
} catch (error) {
if (error instanceof BitfinexError) {
// error.code — number (e.g. 20060 for maintenance)
// error.message — `Bitfinex error <code>: <text>`
}
}
import { AuthenticatedClient } from "bitfinex-node-api";
const client = new AuthenticatedClient({
key: "BitfinexAPIKey",
secret: "BitfinexAPISecret",
});
const info = await client.getAccountInfo();
const fees = await client.getAccountFees();
const summary = await client.getSummary();
const address = await client.getDepositAddress({
method: "bitcoin",
wallet_name: "trading",
renew: 1,
});
const permissions = await client.getKeyPermissions();
const margin = await client.getMarginInformation();
const balances = await client.getWalletBalances();
const result = await client.transfer({
amount: "1.00954735",
currency: "BTC",
walletfrom: "trading",
walletto: "exchange",
});
const result = await client.withdraw({
amount: "1.0",
withdraw_type: "bitcoin",
address: "1DKwqRhDmVyHJDL4FUYpDmQMYA3Rsxtvur",
walletselected: "exchange",
});
const order = await client.newOrder({
amount: "1",
price: "3",
type: "limit",
symbol: "ETCUSD",
side: "buy",
is_postonly: true,
});
const result = await client.newOrders({
orders: [
{
amount: "1",
price: "3",
type: "limit",
symbol: "ETCUSD",
side: "buy",
is_postonly: true,
},
{
amount: "2",
price: "2",
type: "limit",
symbol: "ETCUSD",
side: "buy",
},
],
});
const order = await client.cancelOrder({ order_id: 446915287 });
const { result } = await client.cancelOrders({ order_ids: [1, 2] });
const { result } = await client.cancelAllOrders();
const order = await client.replaceOrder({
order_id: 1,
amount: "3",
price: "101",
type: "limit",
symbol: "ETCUSD",
side: "sell",
is_postonly: true,
});
const order = await client.getOrder({ order_id: 448411153 });
const orders = await client.getOrders();
const orders = await client.getOrderHistory({ limit: 50 });
const positions = await client.getPositions();
const position = await client.claimPosition({
position_id: 943715,
amount: "1.0",
});
const history = await client.getBalanceHistory({
currency: "USD",
since: "1444277602.0",
});
const history = await client.getDepositsWithdrawals({
currency: "BTC",
since: "1444277602.0",
limit: 10,
});
const trades = await client.getPastTrades({
symbol: "BTCEUR",
limit_trades: 25,
reverse: 1,
});
const offer = await client.newOffer({
currency: "USD",
amount: "50.0",
rate: "20.0",
period: 2,
direction: "lend",
});
const offer = await client.cancelOffer({ offer_id: 13800585 });
const offer = await client.offerStatus({ offer_id: 13800585 });
const credits = await client.activeCredits();
const offers = await client.getOffers();
const offers = await client.offersHistory({ limit: 25 });
const trades = await client.getFundingTrades({
symbol: "USD",
limit_trades: 1,
until: "1444141858.0",
});
const funds = await client.getTakenFunds();
const funds = await client.getUnusedFunds();
const funds = await client.getTotalFunds();
const funding = await client.closeFunding({ swap_id: 11576737 });
const response = await client.closePosition({ position_id: 943715 });
Client for the
Bitfinex v2 authenticated REST
endpoints. The default base URL is
https://api.bitfinex.com/v2/ — distinct from the public
https://api-pub.bitfinex.com/v2/ host that
PublicClientV2 uses; override via the
url option if needed. The class extends
PublicClientV2, so all v2 public methods are available
on the same instance, but they will hit whichever host the auth
client was configured with (so by default the public methods on an
AuthenticatedClientV2 instance go to
api.bitfinex.com, which serves them with stricter rate
limits than api-pub.bitfinex.com). For high-volume
public data, prefer a dedicated
PublicClientV2 instance. Each request is signed with
HMAC-SHA384 over /api/v2/{path}{nonce}{body} and sent
with the bfx-apikey, bfx-nonce,
bfx-signature headers. Responses use the same
BitfinexError envelope handling as
PublicClientV2, and write endpoints return a typed
INotificationV2<T> envelope.
import { AuthenticatedClientV2 } from "bitfinex-node-api";
const client = new AuthenticatedClientV2({
key: "BitfinexAPIKey",
secret: "BitfinexAPISecret",
});
The default nonce generator (createMonotonicNonce()) is
anchored to Date.now() * 1000 (microsecond resolution)
and bumps +1 whenever a second call lands inside the
same millisecond, so concurrent Promise.all and fast
sequential usage stay strictly increasing as Bitfinex requires. You
can still provide a custom generator via the
nonce constructor option or the
client.nonce = ... setter.
const wallets = await client.getWallets();
const all = await client.getActiveOrders();
const btc = await client.getActiveOrders({ symbol: "tBTCUSD", id: [1] });
const result = await client.submitOrder({
type: "EXCHANGE LIMIT",
symbol: "tBTCUSD",
amount: "0.01",
price: "50000",
});
await client.updateOrder({ id: 1, price: "51000" });
await client.cancelOrder({ id: 1 });
await client.cancelOrdersMultiple({ all: 1 });
await client.orderMulti({
ops: [
[
"on",
{ type: "LIMIT", symbol: "tBTCUSD", amount: "0.01", price: "50000" },
],
["oc", { id: 1 }],
],
});
await client.getOrdersHistory({ symbol: "tBTCUSD", limit: 25 });
await client.getOrderTrades({ symbol: "tETHUSD", id: 33963608932 });
await client.getTradesHistory({ limit: 50, sort: -1 });
await client.getOtcOrdersHistory({ symbol: "tBTCUSD" });
await client.getLedgers({ currency: "USD", limit: 100 });
await client.getMarginInfo({ key: "base" });
await client.getMarginInfo({ key: "tBTCUSD" });
await client.getPositions();
claimPosition
increasePosition
getIncreasePositionInfo
getPositionsHistory
getPositionsSnapshot
getPositionsAudit
updatePositionFundingType
derivPositionCollateralSet
derivPositionCollateralLimits
await client.claimPosition({ id: 142031891 });
await client.increasePosition({ symbol: "tBTCUSD", amount: "0.1" });
await client.getIncreasePositionInfo({ symbol: "tBTCUSD" });
await client.derivPositionCollateralSet({
symbol: "tBTCF0:USTF0",
collateral: 100,
});
await client.derivPositionCollateralLimits({ symbol: "tBTCF0:USTF0" });
getFundingOffers
submitFundingOffer
cancelFundingOffer
cancelAllFundingOffers
fundingClose
fundingAutoRenew
keepFunding
getFundingOffersHistory
getFundingLoans
getFundingLoansHistory
getFundingCredits
getFundingCreditsHistory
getFundingTradesHistory
getFundingInfo
await client.submitFundingOffer({
type: "LIMIT",
symbol: "fUSD",
amount: "50",
rate: "0.001",
period: 2,
});
await client.cancelAllFundingOffers({ currency: "USD" });
await client.getFundingLoans({ symbol: "fUSD" });
await client.getFundingInfo({ key: "fUST" });
getUserInfo
getSummary
getLoginsHistory
getKeyPermissions
generateToken
getAuditHistory
transfer
getDepositAddress
getDepositAddressAll
generateDepositInvoice
lnxInvoicePayments
withdraw
getMovements
getMovementInfo
getAlerts
setAlert
deleteAlert
getCalcOrderAvailable
getUserSettings
setUserSettings
deleteUserSettings
await client.transfer({
from: "exchange",
to: "margin",
currency: "UST",
amount: "100",
});
await client.getDepositAddress({ wallet: "exchange", method: "bitcoin" });
await client.withdraw({
wallet: "exchange",
method: "ethereum",
amount: "0.1",
address: "0xabc...",
});
await client.setAlert({ type: "price", symbol: "tETHUSD", price: "185" });
await client.deleteAlert({ symbol: "tBTCUSD", price: 600 });
await client.getUserSettings({ keys: ["bit"] });
await client.setUserSettings({ settings: [["bit", "finex"]] });
await client.thalexDeposit({
provider: "thalex",
amount: "1000",
ccy: "USE",
tfaToken: { method: "u2f" },
});
await client.thalexFreeTransferCount({ provider: "thalex" });
The
v1 WebSocket
endpoint (wss://api.bitfinex.com/ws/1). Raw array
frames are parsed into typed objects with named fields. Every
channel message carries channel_id and
type so consumers can discriminate without parsing
positional arrays.
import { WebSocketClient } from "bitfinex-node-api";
const ws = new WebSocketClient({
key: "BitfinexAPIKey", // optional, only required for `auth`
secret: "BitfinexAPISecret", // optional, only required for `auth`
});
ws.on("message", (message) => {
console.log(message);
});
await ws.connect();
connect / disconnectawait ws.connect();
await ws.disconnect();
ping —
docs
const pong = await ws.ping();
subscribeTicker
— push every ticker update for a pair. Funding currencies such as
USD/fUSD are emitted as
funding_ticker messages.
const subscription = await ws.subscribeTicker({ pair: "BTCUSD" });
// Incoming `message` payload:
// {
// channel_id: 56771,
// type: "ticker",
// bid: 76892,
// bid_size: 5.80585799,
// ask: 76926,
// ask_size: 7.03177505,
// daily_change: 810,
// daily_change_perc: 0.01064893,
// last_price: 76874,
// volume: 1438.81140233,
// high: 76984,
// low: 74027,
// }
const subscription = await ws.subscribeTicker({ pair: "USD" });
// Incoming `message` payload:
// {
// channel_id: 57169,
// type: "funding_ticker",
// currency: "USD",
// frr: 0.00039778,
// bid: 0.00029,
// bid_period: 2,
// bid_size: 21298086.02518276,
// ask: 0.00008723,
// ask_period: 2,
// ask_size: 123300.84010007,
// daily_change: -0.000015,
// daily_change_perc: -0.15,
// last_price: 0.00007587,
// volume: 1000,
// high: 0.0004,
// low: 0.00007,
// frr_amount_available: 42,
// }
subscribeTrades
— pushes one initial snapshot of recent trades, then
trade_executed (te) and
trade_updated (tu) live events. Funding
currencies such as USD/fUSD are emitted
as funding trade messages.
const subscription = await ws.subscribeTrades({ pair: "BTCUSD" });
// Snapshot:
// { channel_id, type: "trades_snapshot", trades: [{ id, timestamp, price, amount }, ...] }
// Live update:
// { channel_id, type: "trade_executed", seq, timestamp, price, amount }
// { channel_id, type: "trade_updated", seq, id, timestamp, price, amount }
// Funding snapshot:
// { channel_id, type: "funding_trades_snapshot", currency,
// trades: [{ id, timestamp, amount, rate, period }, ...] }
// Funding live update:
// { channel_id, type: "funding_trade_executed", currency, id, timestamp, amount, rate, period }
// { channel_id, type: "funding_trade_updated", currency, id, timestamp, amount, rate, period }
subscribeBook
— aggregated order book (prec ∈
P0–P3). Funding currencies such as
USD/fUSD are emitted as funding book
messages.
const subscription = await ws.subscribeBook({
pair: "BTCUSD",
prec: "P0",
freq: "F0",
len: 25,
});
// Snapshot:
// { channel_id, type: "book_snapshot", book: [{ price, count, amount }, ...] }
// Update:
// { channel_id, type: "book_update", price, count, amount }
// Funding snapshot:
// { channel_id, type: "funding_book_snapshot", currency,
// book: [{ rate, period, count, amount }, ...] }
// Funding update:
// { channel_id, type: "funding_book_update", currency, rate, period, count, amount }
subscribeRawBook
— raw order book at order-id granularity (prec = R0).
Funding currencies are emitted as raw funding book messages.
const subscription = await ws.subscribeRawBook({ pair: "BTCUSD", len: 100 });
// Snapshot:
// { channel_id, type: "raw_book_snapshot", book: [{ order_id, price, amount }, ...] }
// Update:
// { channel_id, type: "raw_book_update", order_id, price, amount }
// Funding snapshot:
// { channel_id, type: "raw_funding_book_snapshot", currency,
// book: [{ offer_id, period, rate, amount }, ...] }
// Funding update:
// { channel_id, type: "raw_funding_book_update", currency, offer_id, period, rate, amount }
// { channel_id, type: "heartbeat" }
unsubscribeawait ws.unsubscribe({ chanId: subscription.chanId });
auth
— authenticate to receive private channels. v1 accepts only the
documented five fields (event, apiKey,
authSig, authNonce,
authPayload); the v2 extensions
filter/dms/calc are not
supported.
const response = await ws.auth();
Once authenticated the server pushes private events on channel id
0. This client decodes
wallet snapshots and updates:
// { channel_id: 0, type: "wallet_snapshot",
// wallets: [{ wallet_type, currency, balance, unsettled_interest, balance_available }, ...] }
// { channel_id: 0, type: "wallet_update",
// wallet_type, currency, balance, unsettled_interest, balance_available }
Every other private message comes through as a generic envelope with
the v1 mnemonic carried in type and the raw payload
preserved:
// { channel_id: 0, type: "os" | "on" | "ou" | "oc" | ..., payload: [...] }
Refer to the official docs to interpret each payload:
type |
Reference |
|---|---|
os/on/ou/oc
|
Orders |
ps/pn/pu/pc
|
Positions |
te/tu |
Trades |
fos/fon/fou/foc
|
Funding offers |
fcs/fcn/fcu/fcc
|
Funding credits |
fls/fln/flu/flc
|
Funding loans |
fte/ftu |
Funding trades |
bu |
Balance info |
miu |
Margin info |
fiu |
Funding info |
n |
Notifications |
unauth
— drop the authenticated session without closing the socket.
Failure comes back as
{event: "error", code: 10201, ...} and is
surfaced as a rejection.
await ws.unauth();
send — send any raw payload to the server.await ws.send({ event: "ping" });
Every method that returns a promise accepts an
AbortSignal:
const controller = new AbortController();
setTimeout(() => {
controller.abort();
}, 1000);
const sub = await ws.subscribeTicker({ signal: controller.signal });
Client for the
Bitfinex v2 WebSocket
API. The default URL is
wss://api-pub.bitfinex.com/ws/2 (public); when
key and secret are supplied it defaults to
the authenticated host wss://api.bitfinex.com/ws/2.
Override either via the ws_url option. Raw positional
array frames are parsed into typed objects discriminated by a
type field (and channel_id). v2
subscriptions use symbol (e.g. tBTCUSD,
fUSD), unlike the v1 client's pair.
import { WebSocketClientV2 } from "bitfinex-node-api";
const ws = new WebSocketClientV2({
key: "BitfinexAPIKey", // optional, only required for `auth`
secret: "BitfinexAPISecret", // optional, only required for `auth`
});
ws.on("message", (message) => {
console.log(message);
});
await ws.connect();
connect / disconnectawait ws.connect();
await ws.disconnect();
ping —
docs. The client tags each ping with an auto-incrementing
cid and resolves with the matching pong.
const pong = await ws.ping(); // { event: "pong", ts, cid }
conf — change connection settings via
bitwise flags. The exported ConfFlags enumerates
TIMESTAMP, SEQ_ALL,
OB_CHECKSUM, BULK_UPDATES.
import { ConfFlags } from "bitfinex-node-api";
await ws.conf({ flags: ConfFlags.OB_CHECKSUM }); // 131072
// Checksum frames then arrive as { channel_id, type: "checksum", checksum }
subscribeTicker
— trading symbols emit ticker, funding currencies
emit funding_ticker.
const subscription = await ws.subscribeTicker({ symbol: "tBTCUSD" });
// { channel_id, type: "ticker", symbol, bid, bid_size, ask, ask_size,
// daily_change, daily_change_relative, last_price, volume, high, low }
subscribeTrades
— one trades_snapshot then
trade_executed/trade_updated
(te/tu). Funding currencies emit
funding_* variants. The v2 trade layout is
[ID, MTS, AMOUNT, PRICE].
const subscription = await ws.subscribeTrades({ symbol: "tBTCUSD" });
const book = await ws.subscribeBook({ symbol: "tBTCUSD", prec: "P0", len: 25 });
const raw = await ws.subscribeRawBook({ symbol: "tBTCUSD", len: 25 });
const candles = await ws.subscribeCandles({ key: "trade:1m:tBTCUSD" });
// { channel_id, type: "candles_snapshot" | "candle_update", key, ... }
subscribeStatus
— deriv:SYMBOL emits derivatives_status,
liq:global emits liquidation_feed.
const deriv = await ws.subscribeStatus({ key: "deriv:tBTCF0:USTF0" });
const liq = await ws.subscribeStatus({ key: "liq:global" });
unsubscribeawait ws.unsubscribe({ chanId: subscription.chanId });
auth
— authenticate to receive account events on channel id
0. dms: 4 enables the Dead-Man-Switch
(cancel all orders on disconnect); filter narrows the
delivered events.
const response = await ws.auth({ dms: 4, filter: ["trading", "wallet"] });
Once authenticated, account frames are decoded into typed messages:
// { channel_id: 0, type: "wallet_snapshot", wallets: [...] }
// { channel_id: 0, type: "wallet_update", wallet_type, currency, balance, ... }
// { channel_id: 0, type: "position_snapshot" | "position_new" | "position_update" | "position_close", ... }
// { channel_id: 0, type: "order_snapshot" | "order_new" | "order_update" | "order_cancel", ... }
// { channel_id: 0, type: "balance_update", aum, aum_net }
// { channel_id: 0, type: "funding_offer_snapshot" | "funding_offer_new" | ..., ... }
// { channel_id: 0, type: "funding_credit_snapshot" | ..., ... }
// { channel_id: 0, type: "funding_loan_snapshot" | ..., ... }
// { channel_id: 0, type: "notification", mts, notification_type, status, text, ... }
Account frames whose payload is not decoded (e.g.
te/tu account trades,
miu margin info, fiu funding info,
historical snapshots) come through as a generic envelope with the
v2 mnemonic:
// { channel_id: 0, type: "auth_envelope", mnemonic: "miu", payload: [...] }
unauth / sendawait ws.unauth();
await ws.send({ event: "ping", cid: 1234 });
Every method accepts an AbortSignal, and async
iterators are available for all public channels
(tickers, trades, books,
rawBooks, candles, status):
for await (const update of ws.tickers({ symbol: "tBTCUSD" })) {
console.log(update);
}
v1 signature (base64 payload + HMAC-SHA384, headers
X-BFX-*):
import { signature } from "bitfinex-node-api";
const headers = signature({
key: "BitfinexAPIKey",
secret: "BitfinexAPISecret",
payload: Buffer.from(JSON.stringify({ request, nonce })).toString("base64"),
});
v2 signature (HMAC-SHA384 over
/api/v2/{path}{nonce}{body}, headers
bfx-apikey/bfx-nonce/bfx-signature):
import { signatureV2 } from "bitfinex-node-api";
const body = JSON.stringify({});
const nonce = `${Date.now() * 1000}`;
const headers = signatureV2({
key: "BitfinexAPIKey",
secret: "BitfinexAPISecret",
path: "auth/r/wallets",
nonce,
body,
});
npm test