Foxbit Python SDK
The official Python client for the Foxbit REST API v3: trading, market data, wallets and account data.
- Features
- Installation
- Quick start
- Authentication
- Receive window and clock
- Timeouts and retries
- Error handling
- Examples
- API reference
- Upgrading from an earlier version
- Disclaimer
- License
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.
Ed25519 (recommended)
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.
-
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
-
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. -
Keep
foxbit-ed25519-private.pemprivate (for examplechmod 600, a secrets manager or an environment variable) and pass it toConfiguration:
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 aurllib3.Timeout, a total in seconds or a(connect, read)tuple (Nonewaits forever). A per-call_request_timeoutwins over it. - Retries. Only connection errors (the request never reached the server) are retried, up to 3 times, and only for
GET,HEAD,OPTIONSandDELETE.POST,PUTandPATCHare 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. PassConfiguration(retries=...)(an int or aurllib3.Retry) to change it forGET,HEAD,OPTIONSandDELETE;POST,PUTandPATCHstay without retries. - Redirects are never followed, so credentials never reach another host; a 3xx raises
ApiExceptionwith 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 ofApiException, instead of urllib3 exceptions such asMaxRetryError. The urllib3 error is kept as its__cause__. - A 2xx response the SDK cannot read raises
ResponseDeserializationExceptioninstead of a pydantic or JSON error, and a 429 raisesTooManyRequestsException. - Redirects are no longer followed: a 3xx raises
ApiExceptionwith 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=Noneleaves the header out, which turns the replay check off for HMAC keys. POST,PUTandPATCHare never retried, even with a customretries.debug=Truelogs through the package logger only; it no longer turns onhttp.clientdebugging for the whole process, andConfiguration.loggerno longer has anurllib3_loggerentry.- New minimum dependencies:
urllib32.6.3,pydantic2.4 (below 3) andcryptography42, 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)
| File | Size | Uploaded | |
|---|---|---|---|
| foxbit_group_rest_api-0.2.0.tar.gz | 114.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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