Skip to content

Quickstart — Python ​

Connect your first bank account in sandbox mode with budgetbakers-partner-sdk. You need a partner account in the partner portal — registration provisions sandbox access instantly, no card required.

1. Install ​

sh
pip install budgetbakers-partner-sdk

Python ≥ 3.10; one dependency (httpx); import name budgetbakers_partner_sdk. Until the PyPI package is published, install the wheel from the downloads page instead: pip install ./budgetbakers_partner_sdk-0.2.1-py3-none-any.whl.

2. Create the client ​

Your API key selects the mode: bb_test_… keys run sandbox (fictional *_xf banks), bb_live_… keys run live. Keys are server-side only.

python
from budgetbakers_partner_sdk import BudgetBakers

bb = BudgetBakers(api_key="{{BB_API_KEY}}")

config = bb.partner.get_config()
print(config["mode"])  # "sandbox"

3. Create an end-user client ​

A client is one of your end users — bank connections, accounts and transactions all belong to a client, and the X-Client-Id scoping keeps each user's data isolated. Pass your own user id as externalId and the call becomes an upsert — safe to call on every login, no duplicates, no need to store a BudgetBakers-id mapping on your side:

python
client = bb.clients.create(externalId="user-42", email="user42@example.com", countryCode="CZ")
scope = bb.client(client["id"])  # all user-scoped calls go through this

4. Start the hosted connect flow ​

Create a connect session and send the user to hostedUrl (a web redirect, or from your app in the system browser; see Mobile apps).

The returnUrl works like an OAuth redirect URI: the portal (Apps → return URLs) holds your allowlist of patterns — one per platform (web URL, iOS universal link, Android app link, custom scheme) — and each session passes the concrete URL for where this user should land. It must match a registered pattern, otherwise the create fails with validation_error.

python
session = scope.connect_sessions.create("https://app.example.com/bb-callback")
redirect_user_to(session["hostedUrl"])

The redirect back to your returnUrl is a UX signal — the authoritative result is the session state:

python
done = scope.connect_sessions.wait_for_terminal(session["sessionId"])
if done["state"] == "Completed":
    print("connected:", done["connectionId"])

5. Receive webhooks ​

Configure your webhook URL in the portal (per mode); every delivery is signed. See the webhook verification guide for the full contract.

python
from budgetbakers_partner_sdk import parse_event, verify

@app.post("/webhooks/bb")
def bb_webhook(request):
    result = verify(
        ["{{BB_WEBHOOK_SECRET}}"],              # ALL active secrets (two during rotation)
        request.headers.get("X-BB-Signature", ""),
        request.get_data(),                     # the RAW body bytes
    )
    if result != "valid":
        return "", 401

    event = parse_event(request.get_data())
    if event["kind"] == "event" and event["type"] == "ConnectionCreateSuccess":
        ...  # fetch data, update your records
    return "", 200  # always 2xx for verified deliveries — unknown types included

6. Read accounts and transactions ​

python
# Every account of the connection, all pages walked. An account the user left
# unchecked in the account picker is listed too, as subscriptionStatus "Disabled".
accounts = scope.connections.list_accounts(connection_id)

for tx in scope.accounts.transactions(accounts[0]["id"]):
    # tx["amount"] is a decimal.Decimal — money is never a float.
    # Only "id", "seq", "createdSeq" and "recordDate" are guaranteed; treat
    # every other field as nullable.
    ...

Polling for changes? scope.accounts.transactions(account_id, since_seq=...) returns every row inserted or modified since the highest seq you have seen.

Error handling ​

Branch on error.code only — never parse messages (full code reference):

python
from budgetbakers_partner_sdk import PartnerApiError

try:
    scope.connections.refresh(connection_id)
except PartnerApiError as err:
    if err.code == "refresh_cooldown":
        print("retry after", err.next_refresh_possible_at)
    else:
        raise

The SDK already retries 429/5xx with exponential backoff (honoring Retry-After) and auto-generates Idempotency-Key on creates.

Going live ​

Complete the go-live checklist in the portal (card verification, business check, terms) — production unlocks automatically and you can mint a bb_live_… key. The code above works unchanged; only the key differs.