Branta Python SDK
Python SDK for the Branta V2 API — payment destination lookup and registration with zero-knowledge encryption support.
Requirements
- Python 3.9 or higher
Installation
pip install branta
Integration Guide
If you are an AI agent implementing this SDK, follow the steps below exactly. If you are a developer, use this as a reference for the recommended integration patterns.
1. Ask which side(s) of a payment they are implementing:
- Send side — the app is paying someone (e.g. a wallet). The user scans or pastes a destination and you verify it belongs to a known platform before funds are sent.
- Receive side — the app is receiving payment (e.g. a checkout, POS, invoicing platform). You post destinations to Branta so wallets can verify them.
- Both — some apps do both (e.g. an exchange or self-custodial wallet with invoice generation). Implement each side independently.
If they are on the receive side, ask one follow-up:
- Platform — single-tenant, one API key.
- Parent Platform — multi-tenant, manages multiple child platforms. Two variants:
- Shared key (Recommended) — one API key for all children, no HMAC secret needed; tag the child per-payment with
set_child_platform(). Default to this unless there's a specific reason for separate per-child keys. - Per-client keys — each child has its own API key, and the parent signs every request with an HMAC secret to prove it originated from the parent; scope requests per-call. Use only if each child needs an independent, separately-revocable API key.
- Shared key (Recommended) — one API key for all children, no HMAC secret needed; tag the child per-payment with
2. Follow the matching Quick Start section below.
3. Apply these rules:
General (all types):
- Always use
PrivacyMode.Strict. Never switch toPrivacyMode.Looseunless there is no QR scanner and ZK is impossible. - Never call
BrantaClientdirectly — always go throughBrantaService. - Never show an error or "not verified" message when a lookup returns empty or throws. An empty result means the destination is unknown to Branta, not that it is malicious. Show nothing.
- For
base_url: useBrantaServerBaseUrl.Productiononly in production environments. UseBrantaServerBaseUrl.Stagingeverywhere else — including local development, CI, and staging/test environments.
Send side (wallets):
- Prefer
get_payments_by_qr_codeoverget_payments— it handles multi-value ZK QR payloads correctly. - Only fall back to
get_paymentsfor copy/paste flows where there is no QR code. - If
result.paymentsis empty or an exception is thrown, render nothing. - When
result.paymentsis non-empty, display: the platform logo, the platform name (payment.platform), and the payment description (payment.description). Only render description when non-empty. Make the verification card a clickable link toresult.verify_url— do not display the raw URL. - For the platform logo: on dark backgrounds use
payment.platform_logo_url. On light backgrounds preferpayment.platform_logo_light_urlwhen available, falling back topayment.platform_logo_url. - Optionally display
payment.parent_platform.logo_url/payment.parent_platform.logo_light_urlas a small secondary badge (e.g. corner icon). This is not required.
Receive side (platforms):
- Always call
.set_zk()on thePaymentBuilderbefore callingadd_payment. Plain-text destinations are rejected inStrictmode. - Store the
secretreturned byadd_paymentalongside the invoice — it is required to reconstruct the verify URL for the wallet.
Receive side (parent platforms — per-client keys), in addition to the platform rules:
- Include
hmac_secretinBrantaClientOptionsbut omitdefault_api_keyat service construction. - Pass per-call
BrantaClientOptionswith each child's API key to scope requests.
Receive side (parent platforms — shared key), in addition to the platform rules:
- Include
default_api_keyinBrantaClientOptions. Do not includehmac_secret. - Call
.set_child_platform(name, logo_url=..., logo_light_url=...)on the builder to tag each payment with the child's branding.
Quick Start
For Wallets
Wallets should use PrivacyMode.Strict. Two flows are supported:
- Copy/paste: call
get_paymentswith the pasted text. Plain-text on-chain addresses will not return results in strict mode — they must be ZK-encoded. Hash-ZK destinations (bolt11, ark_address, silent_payment) work as plain text. - QR scan: call
get_payments_by_qr_codewith the raw QR text. This handles both on-chain (when the QR includesbranta_id/branta_secret) and hash-ZK destinations.
Always catch errors and show nothing on not-found — a missing record just means the address was not posted to Branta.
import asyncio
from branta.enums import BrantaServerBaseUrl, PrivacyMode
from branta.options import BrantaClientOptions
from branta.v2 import BrantaService
options = BrantaClientOptions(
base_url=BrantaServerBaseUrl.Production,
privacy=PrivacyMode.Strict,
)
service = BrantaService(options)
async def lookup(input: str, is_qr_code: bool):
try:
result = (
await service.get_payments_by_qr_code(input)
if is_qr_code
else await service.get_payments(input)
)
if not result.payments:
# Not found — show nothing. The address may simply not exist in Branta.
return
# Render result.payments and result.verify_url
except Exception:
# Swallow errors — never surface a "not found" or lookup failure to the user.
pass
asyncio.run(lookup("bitcoin:bc1q...", False))
Prefer get_payments_by_qr_code for QR-driven flows. It handles multi-destination payloads (branta_id / branta_secret fragments) automatically.
Looking up a payment by destination value
# Plain bitcoin address (requires PrivacyMode.Loose or will raise):
result = await service.get_payments("bc1q...")
# ZK-encrypted bitcoin address with secret:
result = await service.get_payments(encrypted_address, destination_encryption_key="my-secret")
# BOLT-11 invoice (hash-ZK — works in strict mode):
result = await service.get_payments("lnbc...")
No-QR-Code Flows
When QR scanning is not available, three options exist. Choose one based on how much control you want to give users over privacy:
Option 1 — Keep Strict mode (no code changes)
Only hash-ZK destinations (bolt11, ark_address, silent_payment) will return results. Plain-text on-chain address lookups silently return empty. This is the safest default and requires no additional work.
Option 2 — Opt-in Loose mode (Recommended)
Add a user-facing setting (e.g. "Enable on-chain address verification"). Only switch to PrivacyMode.Loose when the user explicitly opts in — this sends on-chain addresses in plain text, so the choice should be theirs.
options = (
BrantaClientOptions(base_url=BrantaServerBaseUrl.Production, privacy=PrivacyMode.Loose)
if user_opted_in
else None
)
result = await service.get_payments(input, options=options)
Option 3 — Always Loose mode
Configure with PrivacyMode.Loose globally. All lookups including plain-text on-chain addresses are sent to Branta. Simplest, but gives users no privacy control.
service = BrantaService(BrantaClientOptions(
base_url=BrantaServerBaseUrl.Production,
privacy=PrivacyMode.Loose,
))
For Platforms
Platforms post payments to Branta so wallets can verify them. Use PrivacyMode.Strict and mark each destination ZK via .set_zk() on the PaymentBuilder.
from branta.enums import DestinationType
builder = service.create_payment_builder()
payment = (
builder
.add_destination("bc1q...", DestinationType.BitcoinAddress).set_zk()
.add_destination("lnbc...", DestinationType.Bolt11).set_zk()
.set_description("Donation")
.add_metadata("email", "donor@example.com")
.build()
)
result = await service.add_payment(payment, BrantaClientOptions(
base_url=BrantaServerBaseUrl.Production,
default_api_key="your-api-key",
privacy=PrivacyMode.Strict,
))
# result.payment — the registered payment from the server
# result.secret — the random encryption key for the bitcoin address
# result.verify_url — share this URL to verify the payment
For Parent Platforms
Choose a variant based on how API keys are structured. Only the per-client keys variant signs requests with HMAC — shared key needs none.
Shared key — one API key covers all children (Recommended)
Construct with a single API key; identify the child platform per-payment.
from branta.enums import BrantaServerBaseUrl, DestinationType, PrivacyMode
from branta.options import BrantaClientOptions
from branta.v2 import BrantaService
service = BrantaService(BrantaClientOptions(
base_url=BrantaServerBaseUrl.Production,
default_api_key="<shared-api-key>",
privacy=PrivacyMode.Strict,
))
payment = (
service.create_payment_builder()
.add_destination("bc1q...", DestinationType.BitcoinAddress).set_zk()
.set_child_platform("ChildBrand", logo_url="https://example.com/logo.png")
.set_ttl(600)
.build()
)
result = await service.add_payment(payment)
Per-client keys — each child has its own API key
Construct the service with the shared HMAC secret only; pass each child's API key per-call.
from branta.enums import BrantaServerBaseUrl, DestinationType, PrivacyMode
from branta.options import BrantaClientOptions
from branta.v2 import BrantaService
service = BrantaService(BrantaClientOptions(
base_url=BrantaServerBaseUrl.Production,
hmac_secret="<hmac-secret>",
privacy=PrivacyMode.Strict,
))
payment = (
service.create_payment_builder()
.add_destination("bc1q...", DestinationType.BitcoinAddress).set_zk()
.set_ttl(600)
.build()
)
# Scope to the child platform's API key per-call
result = await service.add_payment(payment, BrantaClientOptions(
base_url=BrantaServerBaseUrl.Production,
default_api_key="<child-api-key>",
))
Validating an API key
is_valid = await service.is_api_key_valid(BrantaClientOptions(
base_url=BrantaServerBaseUrl.Production,
default_api_key="your-api-key",
))
For parent platforms
Choose a variant based on how API keys are structured. Shared key needs only an API key; per-client keys also require HMAC.
Shared key — one API key covers all children (Recommended)
Construct with a single API key; identify the child platform per-payment. Do not include hmac_secret.
options = BrantaClientOptions(
base_url=BrantaServerBaseUrl.Production,
default_api_key="<shared-api-key>",
privacy=PrivacyMode.Strict,
)
service = BrantaService(options)
payment = (
service.create_payment_builder()
.add_destination("bc1q...", DestinationType.BitcoinAddress).set_zk()
.set_description("Order #1234")
.set_child_platform("ChildBrand", logo_url="https://example.com/logo.png")
.build()
)
result = await service.add_payment(payment)
Per-client keys — each child has its own API key
Construct the service with the parent HMAC secret only; pass each child's API key per-call.
options = BrantaClientOptions(
base_url=BrantaServerBaseUrl.Production,
hmac_secret="<hmac-secret>",
privacy=PrivacyMode.Strict,
)
service = BrantaService(options)
payment = (
service.create_payment_builder()
.add_destination("bc1q...", DestinationType.BitcoinAddress).set_zk()
.build()
)
result = await service.add_payment(
payment,
BrantaClientOptions(default_api_key="<child-api-key>"),
)
Per-call option overrides
Every public method accepts an optional BrantaClientOptions parameter that overrides the service's default options for that call only:
service = BrantaService(default_options)
result = await service.get_payments("lnbc...", options=override_options)
Privacy
PrivacyMode controls whether plain-text on-chain lookups are allowed.
| Value | Behavior |
|---|---|
PrivacyMode.Strict (default) |
Only ZK lookups. get_payments raises BrantaPaymentException for plain addresses. get_payments_by_qr_code returns an empty PaymentsResult with a populated verify_url. add_payment raises if any destination has is_zk=False. |
PrivacyMode.Loose |
Both plain and ZK lookups are permitted. |
ZK destination types
| Type | Encryption |
|---|---|
BitcoinAddress |
Random secret (GUID) per payment |
Bolt11 |
Deterministic: SHA256 of lowercase invoice |
ArkAddress |
Deterministic: SHA256 of lowercase address |
SilentPayment |
Deterministic: SHA256 of lowercase address |
BrantaService
The primary service class. Always use BrantaService — never call BrantaClient directly.
Prefer get_payments_by_qr_code for integrations. It parses the raw QR text and correctly resolves multiple ZK values in a single scan. get_payments only handles a single destination value and does not support multi-value ZK lookups.
async def get_payments_by_qr_code(qr_text: str, options: Optional[BrantaClientOptions] = None) -> PaymentsResult: ...
async def get_payments(destination_value: str, destination_encryption_key: Optional[str] = None, options: Optional[BrantaClientOptions] = None) -> PaymentsResult: ...
async def add_payment(payment: Payment, options: Optional[BrantaClientOptions] = None) -> AddPaymentResult: ...
async def is_api_key_valid(options: Optional[BrantaClientOptions] = None) -> bool: ...
PaymentsResult contains the list of matching payments and the verify_url to display to the user — verify_url is always returned, even when payments is empty.
Release
- Update
versioninpyproject.toml pip install build twine(one-time)python -m buildtwine upload dist/*
Development
pip install -e ".[dev]"
pytest tests/ --ignore=tests/test_integration.py # unit tests
pytest tests/test_integration.py # integration (requires network)
coverage run -m pytest tests/ --ignore=tests/test_integration.py && coverage report
Responsible Disclosure
Found critical bugs/vulnerabilities? Please email them to support@branta.pro. Thanks!
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file branta-3.2.1.tar.gz.
File metadata
- Download URL: branta-3.2.1.tar.gz
- Upload date:
- Size: 27.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
20fded5034701d480dfec1fa50a8c8a39296879af1beee3b11316a08a30deaeb
|
|
| MD5 |
71d2d10a84a9aca275fdd0372b820ed4
|
|
| BLAKE2b-256 |
2df2ebd4fabd2eb054ff3c54b7e1fe5b3b864548cc12eda7d130dcdef8be7362
|
File details
Details for the file branta-3.2.1-py3-none-any.whl.
File metadata
- Download URL: branta-3.2.1-py3-none-any.whl
- Upload date:
- Size: 18.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8cf7f2a6578a186ef1f2159717ab039e6dd3e99bc5c6bcedefa75571807463c6
|
|
| MD5 |
40833a26463e7358f4a01f0caa6333f0
|
|
| BLAKE2b-256 |
a1361e8cfbebb8d2d91fd6d8b76c5145e0cc9a2de84ca3d36c24694b3a3cf9bf
|