Skip to main content

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.

2. Follow the matching Quick Start section below.

3. Apply these rules:

General (all types):

  • Always use PrivacyMode.Strict. Never switch to PrivacyMode.Loose unless there is no QR scanner and ZK is impossible.
  • Never call BrantaClient directly — always go through BrantaService.
  • 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: use BrantaServerBaseUrl.Production only in production environments. Use BrantaServerBaseUrl.Staging everywhere else — including local development, CI, and staging/test environments.

Send side (wallets):

  • Prefer get_payments_by_qr_code over get_payments — it handles multi-value ZK QR payloads correctly.
  • Only fall back to get_payments for copy/paste flows where there is no QR code.
  • If result.payments is empty or an exception is thrown, render nothing.
  • When result.payments is 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 to result.verify_url — do not display the raw URL.
  • For the platform logo: on dark backgrounds use payment.platform_logo_url. On light backgrounds prefer payment.platform_logo_light_url when available, falling back to payment.platform_logo_url.
  • Optionally display payment.parent_platform.logo_url / payment.parent_platform.logo_light_url as a small secondary badge (e.g. corner icon). This is not required.

Receive side (platforms):

  • Always call .set_zk() on the PaymentBuilder before calling add_payment. Plain-text destinations are rejected in Strict mode.
  • Store the secret returned by add_payment alongside 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_secret in BrantaClientOptions but omit default_api_key at service construction.
  • Pass per-call BrantaClientOptions with each child's API key to scope requests.

Receive side (parent platforms — shared key), in addition to the platform rules:

  • Include default_api_key in BrantaClientOptions. Do not include hmac_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_payments with 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_code with the raw QR text. This handles both on-chain (when the QR includes branta_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.

branta/v2/service.py

Release

  • Update version in pyproject.toml
  • pip install build twine (one-time)
  • python -m build
  • twine 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

branta-3.2.1.tar.gz (27.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

branta-3.2.1-py3-none-any.whl (18.8 kB view details)

Uploaded Python 3

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

Hashes for branta-3.2.1.tar.gz
Algorithm Hash digest
SHA256 20fded5034701d480dfec1fa50a8c8a39296879af1beee3b11316a08a30deaeb
MD5 71d2d10a84a9aca275fdd0372b820ed4
BLAKE2b-256 2df2ebd4fabd2eb054ff3c54b7e1fe5b3b864548cc12eda7d130dcdef8be7362

See more details on using hashes here.

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

Hashes for branta-3.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8cf7f2a6578a186ef1f2159717ab039e6dd3e99bc5c6bcedefa75571807463c6
MD5 40833a26463e7358f4a01f0caa6333f0
BLAKE2b-256 a1361e8cfbebb8d2d91fd6d8b76c5145e0cc9a2de84ca3d36c24694b3a3cf9bf

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

3.2.1 This release

2 files

3.2.0

2 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