npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@foxbit-group/rest-api

v0.4.0

Published

Foxbit's API client

Readme

Foxbit REST API SDK for JavaScript and TypeScript

Official Foxbit client for the REST API v3: market data, trading, accounts, deposits and withdrawals from Node.js.

Features

  • Typed requests and responses for every REST v3 endpoint, generated from the official OpenAPI specification.
  • Request signing built in: HMAC-SHA256 with your API secret, or Ed25519 with your own private key.
  • Receive window on every signed request, a 30 second default timeout and no redirects, so credentials never follow a redirect to another host.
  • A private axios instance: your application's global axios interceptors, headers and credentials do not reach the SDK, and a baseURL set on the global axios never redirects its requests.
  • Helpers for rate limits (isTooManyRequests, getRetryAfterSeconds) and typed API errors.
  • CommonJS and ES module builds, with TypeScript declarations.

Installation

Requires Node.js 18 or later.

npm install @foxbit-group/rest-api

Quick start

Public endpoints need no credentials:

import { MarketDataApi } from '@foxbit-group/rest-api';

const marketData = new MarketDataApi();

const { data } = await marketData.listMarkets();
console.log(data.data);

Authentication

Private endpoints are signed. Pass a Configuration with your API key and exactly one of:

  • apiSecret: the secret shown when you created the key. Signs with HMAC-SHA256.
  • privateKey: an Ed25519 private key you generated yourself (PEM PKCS#8 string or crypto.KeyObject). Only the public key is ever registered with Foxbit.

Both use the same headers and the same signed payload; each API key belongs to one scheme. A few endpoints do not accept Ed25519 keys yet and answer error 2010; use an HMAC key there. Setting both, or a key that is not Ed25519, throws when the Configuration is created.

We recommend Ed25519: the private key never leaves your machine, so Foxbit never stores a secret that could sign on your behalf.

Ed25519 (recommended)

  1. Generate a key pair:

    openssl genpkey -algorithm ed25519 -out foxbit-ed25519.pem
    openssl pkey -in foxbit-ed25519.pem -pubout -out foxbit-ed25519.pub.pem
    cat foxbit-ed25519.pub.pem
  2. Open https://app.foxbit.com.br/profile/api-key, create an API key under Generated by you and paste the public key (foxbit-ed25519.pub.pem).

  3. Keep foxbit-ed25519.pem private and pass it to the SDK:

import { readFileSync } from 'fs';
import { AccountApi, Configuration } from '@foxbit-group/rest-api';

const config = new Configuration({
  apiKey: 'YOUR_API_KEY',
  privateKey: readFileSync('foxbit-ed25519.pem', 'utf8'),
});
const account = new AccountApi(config);

const { data } = await account.listAccounts();
console.log(data.data);

HMAC-SHA256

import { AccountApi, Configuration } from '@foxbit-group/rest-api';

const config = new Configuration({
  apiKey: 'YOUR_API_KEY',
  apiSecret: 'YOUR_API_SECRET',
});
const account = new AccountApi(config);

const { data } = await account.listAccounts();
console.log(data.data);

On every signed request the SDK sends:

  • X-FB-ACCESS-KEY: your API key.
  • X-FB-ACCESS-TIMESTAMP: the current time in milliseconds.
  • X-FB-ACCESS-SIGNATURE: the signature of timestamp, method, path, query and body, in hex.
  • X-FB-RECEIVE-WINDOW: how long the request stays valid, in milliseconds.

apiSecret and privateKey are not enumerable, so they do not show up in JSON.stringify(config) or console.log(config). For the same reason { ...config } drops them: build another Configuration instead of copying one. Load them from a secret store or environment variables, never from source code.

Receive window and clock

A signed request is only accepted while its timestamp is within the receive window of the server's clock, before or after; outside it the API answers 412 with code 4014. The SDK sends X-FB-RECEIVE-WINDOW: 10000 (10 seconds) by default. Set receiveWindow from 1000 to 60000 ms. null or 0 does not send the header, and then HMAC requests get no receive-window check at all (Ed25519 requests keep the server's fixed 5-minute window, answered with 401 and code 2006), so keep the header on.

The timestamp comes from your machine's clock, so keep it in sync (NTP). To check the drift, compare it with GET /rest/v3/system/time:

import { Configuration, SystemApi } from '@foxbit-group/rest-api';

const config = new Configuration({
  apiKey: 'YOUR_API_KEY',
  apiSecret: 'YOUR_API_SECRET',
  receiveWindow: 5000,
});

const { data } = await new SystemApi(config).getCurrentTime();
const drift = Date.now() - (data.timestamp ?? Date.now());
console.log(`Local clock is ${drift} ms off; receive window is ${config.receiveWindow} ms`);

Timeouts

Every request times out after 30 seconds by default. Change it for all calls with timeout in the Configuration, or for one call with the last options argument, which always wins:

import { Configuration, MarketDataApi } from '@foxbit-group/rest-api';

const marketData = new MarketDataApi(new Configuration({ timeout: 10000 }));

// This call gives up after 2 seconds; the others after 10.
const { data } = await marketData.getOrderbook({ marketSymbol: 'btcbrl', depth: 20 }, { timeout: 2000 });
console.log(data.bids.length, data.asks.length);

Error handling

Every method returns the axios response. A non-2xx status rejects with an AxiosError (re-exported by this package with isAxiosError), whose error.response.data.error holds the API error:

  • code: the numeric Foxbit error code.
  • message: a readable description.
  • details: a list with more context, such as which field failed validation.

When you hit a rate limit the API answers HTTP 429. isTooManyRequests(error) detects it and getRetryAfterSeconds(error) returns the seconds to wait from the X-FB-RATE-LIMIT-RETRY-AFTER header the API sends (or a standard Retry-After, the longer one if both come), or undefined without one. Invalid arguments, such as a missing required parameter or an empty path parameter, throw a RequiredError before any request is sent.

import {
  ErrorResponse,
  MarketDataApi,
  RequiredError,
  getRetryAfterSeconds,
  isAxiosError,
  isTooManyRequests,
} from '@foxbit-group/rest-api';

const marketData = new MarketDataApi();

try {
  const { data } = await marketData.getTicker({ marketSymbol: 'btcbrl' });
  console.log(data.data);
} catch (error) {
  if (isTooManyRequests(error)) {
    const seconds = getRetryAfterSeconds(error) ?? 1;
    console.warn(`Rate limited, retry in ${seconds} s`);
  } else if (isAxiosError<ErrorResponse>(error) && error.response?.data?.error) {
    const { code, message, details } = error.response.data.error;
    console.error(error.response.status, code, message, details);
  } else if (isAxiosError(error) && error.response) {
    // Not a Foxbit error body, e.g. a redirect or a proxy page.
    console.error(error.response.status, error.response.data);
  } else if (error instanceof RequiredError) {
    console.error(`Invalid argument ${error.field}: ${error.message}`);
  } else {
    throw error;
  }
}

Configuration

| Option | Default | Description | | --- | --- | --- | | apiKey | none | Your API key. Required for signed endpoints. | | apiSecret | none | API secret for HMAC-SHA256 signing. | | privateKey | none | Ed25519 private key (PEM PKCS#8 or crypto.KeyObject) for Ed25519 signing. | | receiveWindow | 10000 | Receive window in ms, from 1000 to 60000. null or 0 does not send it. | | timeout | 30000 | Timeout of every request in ms. A per-call options.timeout wins. | | basePath | https://api.foxbit.com.br | API host, without a trailing slash. Always wins over the axios instance's baseURL. | | baseOptions | none | Extra axios request options for every call. |

Each API class also takes an axios instance as its third argument, for example to add your own interceptors: new MarketDataApi(config, undefined, myAxios). Any axios 1.x instance works. Without one, the SDK uses its own private instance.

Examples

The examples below assume Node.js 18+ with ES modules (top-level await). Replace YOUR_API_KEY and YOUR_API_SECRET with your credentials, or use privateKey as shown in Authentication.

Create a LIMIT order

import { Configuration, TradingApi } from '@foxbit-group/rest-api';

const trading = new TradingApi(new Configuration({ apiKey: 'YOUR_API_KEY', apiSecret: 'YOUR_API_SECRET' }));

const { data } = await trading.createOrder({
  createOrderRequest: {
    market_symbol: 'btcbrl',
    side: 'BUY',
    type: 'LIMIT',
    quantity: '0.001',
    price: '300000',
    time_in_force: 'GTC',
    post_only: true,
    client_order_id: '1001',
  },
});
console.log(`Order ${data.id} created (SN ${data.sn})`);

Cancel an order by ID

Cancellation is asynchronous: check the final state with getOrderById.

import { Configuration, TradingApi } from '@foxbit-group/rest-api';

const trading = new TradingApi(new Configuration({ apiKey: 'YOUR_API_KEY', apiSecret: 'YOUR_API_SECRET' }));

const { data } = await trading.cancelOrders({
  cancelOrdersRequest: { type: 'ID', id: '1234567890' },
});
console.log(data.data);

const { data: order } = await trading.getOrderById({ id: '1234567890' });
console.log(order.state);

List orders with a date filter and pagination

startTime and endTime are ISO-8601 UTC and at most 90 days apart; pageSize goes up to 100.

import { Configuration, OrderWithoutRemark, TradingApi } from '@foxbit-group/rest-api';

const trading = new TradingApi(new Configuration({ apiKey: 'YOUR_API_KEY', apiSecret: 'YOUR_API_SECRET' }));

const pageSize = 100;
const orders: OrderWithoutRemark[] = [];
for (let page = 1; ; page++) {
  const { data } = await trading.listOrders({
    marketSymbol: 'btcbrl',
    state: 'FILLED',
    startTime: '2026-01-01T00:00:00.000Z',
    endTime: '2026-01-31T23:59:59.999Z',
    page,
    pageSize,
  });
  const batch = data.data ?? [];
  orders.push(...batch);
  if (batch.length < pageSize) break;
}
console.log(`${orders.length} filled orders in January`);

Get balances

import { AccountApi, Configuration } from '@foxbit-group/rest-api';

const account = new AccountApi(new Configuration({ apiKey: 'YOUR_API_KEY', apiSecret: 'YOUR_API_SECRET' }));

const { data } = await account.listAccounts();
for (const balance of data.data ?? []) {
  console.log(balance.currency_symbol, 'available', balance.balance_available, 'locked', balance.balance_locked);
}

Market data: ticker and order book

import { MarketDataApi } from '@foxbit-group/rest-api';

const marketData = new MarketDataApi();

const { data: ticker } = await marketData.getTicker({ marketSymbol: 'btcbrl' });
for (const market of ticker.data) {
  console.log(market.market_symbol, 'last', market.last_trade.price, 'bid', market.best.bid.price, 'ask', market.best.ask.price);
}

const { data: book } = await marketData.getOrderbook({ marketSymbol: 'btcbrl', depth: 10 });
const [bestBid] = book.bids;
const [bestAsk] = book.asks;
console.log('best bid', bestBid, 'best ask', bestAsk);

Retry after a rate limit

import { MarketDataApi, getRetryAfterSeconds, isTooManyRequests } from '@foxbit-group/rest-api';

const marketData = new MarketDataApi();
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

for (let attempt = 1; attempt <= 3; attempt++) {
  try {
    const { data } = await marketData.getTicker24h();
    console.log(`${data.data.length} markets`);
    break;
  } catch (error) {
    if (!isTooManyRequests(error) || attempt === 3) throw error;
    await sleep((getRetryAfterSeconds(error) ?? attempt) * 1000);
  }
}

Set a custom timeout

import { Configuration, TradingApi } from '@foxbit-group/rest-api';

const trading = new TradingApi(new Configuration({
  apiKey: 'YOUR_API_KEY',
  apiSecret: 'YOUR_API_SECRET',
  timeout: 5000,
}));

// Fail fast on this one call.
const { data } = await trading.getOrderById({ id: '1234567890' }, { timeout: 1500 });
console.log(data.state);

API reference

The full reference of every endpoint, parameter and error code is in the Foxbit API documentation. API changes are listed in the changelog.

The SDK groups the endpoints in these classes:

  • AccountApi
  • BanksApi
  • DepositApi
  • MarketDataApi
  • MemberInfoApi
  • PrimeDeskApi
  • SystemApi
  • TradingApi
  • TransactionalLimitsApi
  • TravelRuleApi
  • WithdrawalApi

Upgrading from an earlier version

  • The SDK uses its own axios instance, so interceptors, headers, auth, params and baseURL set on the global axios no longer apply to it. To keep them, pass your instance as the third argument of the API class. On signed requests the query and body must stay as signed: an instance or call that sets params or paramsSerializer is refused with RequiredError, and transformRequest is not applied. A request interceptor of your instance must not change the method, URL, query or body of a signed request either, or the API answers 401.
  • axios 1.20 or newer is required. If your app stays on an older axios, npm installs a separate copy for the SDK, and error instanceof AxiosError with your own AxiosError becomes false; use isAxiosError(error), exported by this package.
  • Redirects are never followed, even with maxRedirects in baseOptions or in the call options.
  • Every request has a default timeout of 30 s, and signed requests send X-FB-RECEIVE-WINDOW: 10000. receiveWindow: null leaves the header out, which turns the replay check off for HMAC keys.
  • apiSecret and privateKey are not enumerable: a { ...config } copy has no credentials. Build a new Configuration instead.
  • Configuration throws right away for a receiveWindow outside 1000 to 60000, for apiSecret together with privateKey, and for a secret or key without apiKey.
  • An explicit basePath (in Configuration or as the second argument) now wins over the baseURL of your own axios instance.
  • The Configuration type has receiveWindow and timeout members, so an object literal typed as Configuration or a class that implements it must declare them; build one with new Configuration(...) instead.
  • Subclasses of BaseAPI see basePath as string | undefined and axios as AxiosLike.
  • Node 18 or newer is required, and the package ships only the compiled dist/ (no .ts sources): import from @foxbit-group/rest-api, not from a source path.

Disclaimer

Trading crypto assets involves significant risk, including the loss of your funds. This SDK is provided "as is", without warranty of any kind; you are responsible for your orders, credentials and use of the API. Test your integration with small amounts first. Use at your own risk.

License

MIT © Foxbit Group; the license text ships with the package (LICENSE).