Skip to main content

Foxbit Python SDK

The official Python client for the Foxbit REST API v3: trading, market data, wallets and account data.

Features

  • Every REST API v3 endpoint as a typed method, with pydantic v2 models for requests and responses.
  • Request signing built in: Ed25519 (recommended) or HMAC-SHA256, added to every authenticated call.
  • Safe defaults: connect/read timeouts, a receive window on signed requests, no redirects, and no automatic retry of order-creating requests.
  • Typed exceptions for HTTP errors, rate limits (with retry_after), network failures and unreadable responses.
  • Forward compatible: an enum value added to the API later is still read by older SDK versions.
  • Fully typed package (py.typed), Python 3.10 to 3.14.

Installation

pip install foxbit-group-rest-api

Everything is included, Ed25519 signing too (through cryptography). Requires Python 3.10 or higher.

Quick start

Public endpoints need no credentials:

from foxbit_group.rest_api import ApiClient
from foxbit_group.rest_api.api import MarketDataApi

with ApiClient() as client:
    market_data = MarketDataApi(client)

    markets = market_data.list_markets()
    for market in markets.data or []:
        print(market.symbol, market.price_min, market.quantity_min)

Authentication

Authenticated endpoints need an API key plus one of:

Scheme Configuration Signature
Ed25519 (recommended) api_key + private_key Ed25519 over the request, signed with your private key
HMAC-SHA256 api_key + api_secret HMAC-SHA256 over the request, keyed with your API secret

Setting both api_secret and private_key, a secret or key without api_key, or an empty api_secret raises ApiValueError before any request. An api_key alone signs nothing: public endpoints work, and authenticated ones answer 401. Each API key belongs to one scheme, so nothing else changes between them. A few endpoints do not accept Ed25519 keys yet and answer error 2010; use an HMAC key there.

With Ed25519 the secret never leaves your machine: Foxbit only stores your public key, so a leak on the server side cannot be used to sign requests.

  1. Generate a key pair:

    openssl genpkey -algorithm ed25519 -out foxbit-ed25519-private.pem
    openssl pkey -in foxbit-ed25519-private.pem -pubout -out foxbit-ed25519-public.pem
    
  2. Open app.foxbit.com.br/profile/api-key, create an API key under Generated by you and paste the contents of foxbit-ed25519-public.pem.

  3. Keep foxbit-ed25519-private.pem private (for example chmod 600, a secrets manager or an environment variable) and pass it to Configuration:

from pathlib import Path

from foxbit_group.rest_api import ApiClient, Configuration
from foxbit_group.rest_api.api import AccountApi

config = Configuration(
    api_key="YOUR_API_KEY",
    # A PEM (str or bytes) or a cryptography Ed25519PrivateKey
    private_key=Path("foxbit-ed25519-private.pem").read_bytes(),
)

with ApiClient(config) as client:
    accounts = AccountApi(client).list_accounts()
    print(accounts)

The key is checked when Configuration is created: a key that is not Ed25519, or a missing cryptography package, raises ApiValueError right away.

HMAC-SHA256

from foxbit_group.rest_api import ApiClient, Configuration
from foxbit_group.rest_api.api import AccountApi

config = Configuration(
    api_key="YOUR_API_KEY",
    api_secret="YOUR_API_SECRET",
)

with ApiClient(config) as client:
    accounts = AccountApi(client).list_accounts()
    print(accounts)

What the SDK sends

On every authenticated request the SDK adds:

Header Value
X-FB-ACCESS-KEY Your API key
X-FB-ACCESS-TIMESTAMP The current time in milliseconds
X-FB-ACCESS-SIGNATURE The signature, hex encoded (64 characters for HMAC, 128 for Ed25519)
X-FB-RECEIVE-WINDOW The receive window in milliseconds (see below)

Both schemes sign the same bytes: timestamp + METHOD + path + query + body. Your API secret and private key are only used locally and are never sent. repr(config) masks them, and debug logs redact the key and signature headers.

Receive window and clock

The server rejects a signed request whose X-FB-ACCESS-TIMESTAMP is more than receive_window milliseconds away from its own clock, before or after, so a captured request cannot be replayed later. Outside the window the API answers 412 with code 4014, an ApiException with status 412. The default is 10000 (10 s); any value from 1000 to 60000 is accepted. None or 0 leaves the header out, 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.

Signatures depend on your clock. If requests fail with an expired timestamp, sync the machine with NTP and compare it with the server time from GET /rest/v3/system/time:

import time

from foxbit_group.rest_api import ApiClient, Configuration
from foxbit_group.rest_api.api import SystemApi

config = Configuration(
    api_key="YOUR_API_KEY",
    api_secret="YOUR_API_SECRET",
    receive_window=5000,  # ms, 1000 to 60000
)

with ApiClient(config) as client:
    server_time = SystemApi(client).get_current_time()
    if server_time.timestamp is not None:
        offset_ms = server_time.timestamp - time.time() * 1000
        print(f"local clock offset: {offset_ms:.0f} ms")

Timeouts and retries

  • Timeouts. Every request has a 10 s connect and 30 s read timeout by default. Change it with Configuration(timeout=...), which takes a urllib3.Timeout, a total in seconds or a (connect, read) tuple (None waits forever). A per-call _request_timeout wins over it.
  • Retries. Only connection errors (the request never reached the server) are retried, up to 3 times, and only for GET, HEAD, OPTIONS and DELETE. POST, PUT and PATCH are never retried, so an order is never sent twice. Responses such as 429 or 5xx are not retried; they raise an exception you can act on. Pass Configuration(retries=...) (an int or a urllib3.Retry) to change it for GET, HEAD, OPTIONS and DELETE; POST, PUT and PATCH stay without retries.
  • Redirects are never followed, so credentials never reach another host; a 3xx raises ApiException with its status.

A retry resends the request as it was signed, so it must still fall inside the receive window. For anything else, call the method again: each call is signed afresh.

import urllib3

from foxbit_group.rest_api import Configuration

config = Configuration(
    api_key="YOUR_API_KEY",
    api_secret="YOUR_API_SECRET",
    timeout=urllib3.Timeout(connect=5, read=15),
)
print(config.timeout)

Error handling

HTTP, network and response-deserialization failures of an API call are ApiException instances, with status, reason, body and headers. Every class below lives in foxbit_group.rest_api.exceptions and is also importable from foxbit_group.rest_api. Arguments are checked before any request: a wrong type, a missing required argument or a value out of range raises pydantic.ValidationError (a ValueError), and an empty, . or .. path parameter or an invalid Configuration raises ApiValueError:

Exception When
BadRequestException 400: the request is invalid
UnauthorizedException 401: missing or wrong credentials, a rejected signature, or a timestamp header the server refuses
ForbiddenException 403: the API key lacks the permission
NotFoundException 404
ConflictException 409
UnprocessableEntityException 422
TooManyRequestsException 429: rate limit hit; retry_after holds the seconds to wait (from X-FB-RATE-LIMIT-RETRY-AFTER, or a standard Retry-After), or None
ServiceException 5xx
NetworkException No HTTP response: connection refused, DNS, TLS, timeout. status is 0 and the urllib3 error is __cause__
ResponseDeserializationException A 2xx whose body does not match the documented type. body keeps the response text (bytes that are not valid in its charset are replaced) and the parse error is __cause__
ApiException Any other status, such as a 3xx

Configuration mistakes (both api_secret and private_key, an out-of-range receive_window, a .. path parameter) raise ApiValueError before anything is sent.

from foxbit_group.rest_api import ApiClient, Configuration
from foxbit_group.rest_api.api import TradingApi
from foxbit_group.rest_api.exceptions import (
    ApiException,
    NetworkException,
    TooManyRequestsException,
    UnauthorizedException,
)

config = Configuration(api_key="YOUR_API_KEY", api_secret="YOUR_API_SECRET")

with ApiClient(config) as client:
    try:
        orders = TradingApi(client).list_orders()
        print(orders)
    except UnauthorizedException as e:
        print("Invalid credentials or rejected signature:", e.body)
    except TooManyRequestsException as e:
        print(f"Rate limited, retry in {e.retry_after} s")
    except NetworkException as e:
        print("Network problem:", e.reason)
    except ApiException as e:
        print(f"API error {e.status}: {e.body}")

Examples

All amounts and prices are strings, to keep their exact decimal value.

Create a LIMIT order

CreateOrderRequest wraps one of the order types: OrderLimit, OrderMarket, OrderInstant, OrderStop or OrderStopLimit.

from foxbit_group.rest_api import ApiClient, Configuration
from foxbit_group.rest_api.api import TradingApi
from foxbit_group.rest_api.models import CreateOrderRequest, OrderLimit

config = Configuration(api_key="YOUR_API_KEY", api_secret="YOUR_API_SECRET")

with ApiClient(config) as client:
    created = TradingApi(client).create_order(
        CreateOrderRequest(
            OrderLimit(
                side="BUY",
                type="LIMIT",
                market_symbol="btcbrl",
                quantity="0.001",
                price="150000.00",
                time_in_force="GTC",
                post_only=True,
                client_order_id="1001",
            )
        )
    )
    print("order id:", created.id)

Cancel an order by ID

from foxbit_group.rest_api import ApiClient, Configuration
from foxbit_group.rest_api.api import TradingApi
from foxbit_group.rest_api.models import CancelOrdersRequest, OrdersCancelId

config = Configuration(api_key="YOUR_API_KEY", api_secret="YOUR_API_SECRET")

with ApiClient(config) as client:
    canceled = TradingApi(client).cancel_orders(
        CancelOrdersRequest(OrdersCancelId(type="ID", id="1234567890"))
    )
    for order in canceled.data or []:
        print("cancel requested:", order.id)

Cancellation is asynchronous: use get_order_by_id to confirm the final state. CancelOrdersRequest also takes OrdersCancelClientOrderId, OrdersCancelMarket, OrdersCancelMarketSide and OrdersCancelAll.

List orders with a date filter and pagination

from foxbit_group.rest_api import ApiClient, Configuration
from foxbit_group.rest_api.api import TradingApi

config = Configuration(api_key="YOUR_API_KEY", api_secret="YOUR_API_SECRET")
page_size = 100

with ApiClient(config) as client:
    trading = TradingApi(client)
    for page in range(1, 11):  # at most 10 pages here
        orders = trading.list_orders(
            start_time="2026-01-01T00:00:00.000Z",  # ISO-8601 UTC, at most 90 days apart
            end_time="2026-01-31T23:59:59.999Z",
            market_symbol="btcbrl",
            state="FILLED",
            page=page,
            page_size=page_size,
        )
        batch = orders.data or []
        for order in batch:
            print(order.id, order.side, order.price, order.quantity_executed)
        if len(batch) < page_size:
            break

Balances

from foxbit_group.rest_api import ApiClient, Configuration
from foxbit_group.rest_api.api import AccountApi

config = Configuration(api_key="YOUR_API_KEY", api_secret="YOUR_API_SECRET")

with ApiClient(config) as client:
    accounts = AccountApi(client).list_accounts()
    for account in accounts.data or []:
        print(account.currency_symbol, "available:", account.balance_available, "locked:", account.balance_locked)

Market data

from foxbit_group.rest_api import ApiClient
from foxbit_group.rest_api.api import MarketDataApi

with ApiClient() as client:
    market_data = MarketDataApi(client)

    book = market_data.get_orderbook("btcbrl", depth=5)
    if book.bids and book.asks:
        print("best bid:", book.bids[0], "best ask:", book.asks[0])

    for ticker in market_data.get_ticker("btcbrl").data:
        print(ticker.market_symbol, "last:", ticker.last_trade.price)

Retry after a rate limit

import time

from foxbit_group.rest_api import ApiClient, Configuration
from foxbit_group.rest_api.api import AccountApi
from foxbit_group.rest_api.exceptions import TooManyRequestsException

config = Configuration(api_key="YOUR_API_KEY", api_secret="YOUR_API_SECRET")

with ApiClient(config) as client:
    accounts = None
    for attempt in range(3):
        try:
            accounts = AccountApi(client).list_accounts()
            break
        except TooManyRequestsException as e:
            # Each new call is signed again, with a fresh timestamp.
            time.sleep(e.retry_after if e.retry_after is not None else 2 ** attempt)
    print(accounts)

Custom timeout for one call

from foxbit_group.rest_api import ApiClient
from foxbit_group.rest_api.api import MarketDataApi

with ApiClient() as client:
    # (connect, read) in seconds; wins over Configuration.timeout
    currencies = MarketDataApi(client).list_currencies(_request_timeout=(2.0, 5.0))
    print(currencies)

API reference

The full reference, with every endpoint, field and rate limit, is at docs.foxbit.com.br/rest/v3. See the changelog for API changes.

The SDK groups the endpoints in these classes (foxbit_group.rest_api.api); every method has its docstring, and the endpoints are described in the API reference above:

  • AccountApi

  • BanksApi

  • DepositApi

  • MarketDataApi

  • MemberInfoApi

  • PrimeDeskApi

  • SystemApi

  • TradingApi

  • TransactionalLimitsApi

  • TravelRuleApi

  • WithdrawalApi

Upgrading from an earlier version

  • Python 3.10 or newer is required.
  • Network failures (connection refused, timeouts, broken connections) raise NetworkException, a subclass of ApiException, instead of urllib3 exceptions such as MaxRetryError. The urllib3 error is kept as its __cause__.
  • A 2xx response the SDK cannot read raises ResponseDeserializationException instead of a pydantic or JSON error, and a 429 raises TooManyRequestsException.
  • Redirects are no longer followed: a 3xx raises ApiException with its status.
  • Every request has a default timeout (connect 10 s, read 30 s), and signed requests send X-FB-RECEIVE-WINDOW: 10000. receive_window=None leaves the header out, which turns the replay check off for HMAC keys.
  • POST, PUT and PATCH are never retried, even with a custom retries.
  • debug=True logs through the package logger only; it no longer turns on http.client debugging for the whole process, and Configuration.logger no longer has an urllib3_logger entry.
  • New minimum dependencies: urllib3 2.6.3, pydantic 2.4 (below 3) and cryptography 42, which is installed with the package.

Disclaimer

This SDK is provided "as is", without warranty of any kind. Trading crypto assets involves risk, including the loss of the amounts invested; nothing here is investment advice. Test your integration with small amounts first, give each API key only the permissions it needs, and keep your API secret and private key out of source control.

License

MIT; the license text ships with the package (LICENSE). Maintained by Foxbit.

Metadata

Release files for foxbit-group-rest-api 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for foxbit-group-rest-api 0.2.0
File Size Uploaded
foxbit_group_rest_api-0.2.0.tar.gz 114.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for foxbit-group-rest-api 0.2.0
File Interpreter ABI Platform
foxbit_group_rest_api-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 467.2 kB

Release files / foxbit_group_rest_api-0.2.0.tar.gz

Download URL foxbit_group_rest_api-0.2.0.tar.gz
Size 114.7 kB
Tags Source
SHA-256 checksum
How to use checksums
f89e77131b6af7b90187110ef0e374957e701e0f2ccbaba25b39b5683e77a0db
BLAKE2b-256 checksum
How to use checksums
45910d9e99748858be3fe3d57c96f83506ad52a4adf22b0495be5e50f3f426e7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.

Transparency log

Release files / foxbit_group_rest_api-0.2.0-py3-none-any.whl

Download URL foxbit_group_rest_api-0.2.0-py3-none-any.whl
Size 352.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
888d2f0f6b438674c72a9dbbc3c36ba6939e24847d72b7e391cf9a9f917e18d9
BLAKE2b-256 checksum
How to use checksums
282d8cadaf112db5a3d33c13c34641ca87d3d18afe9c03bc72ac6e01833cf030
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.2

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page