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!

Metadata

Release files for branta 3.2.2

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

Source distribution (sdist)

Source distribution for branta 3.2.2
File Size Uploaded Provenance
branta-3.2.2.tar.gz 28.3 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for branta 3.2.2
File Interpreter ABI Platform
branta-3.2.2-py3-none-any.whl Python 3 none any Details

Total release size: 47.2 kB

Release files / branta-3.2.2.tar.gz

Download URL branta-3.2.2.tar.gz
Size 28.3 kB
Tags Source
SHA-256 checksum
How to use checksums
58f08be05871c9c811e9229bac1f83bb490df3cd9c0fa4584826cd87e1590b55
BLAKE2b-256 checksum
How to use checksums
eb21e9ded30c5ff76205dd297fd319f50f3828c4bb06e1a8ad12c0ff1dc56c06
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Release files / branta-3.2.2-py3-none-any.whl

Download URL branta-3.2.2-py3-none-any.whl
Size 19.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
31f2b8df7c95ca5b42e14bdaf9ff0d36ce1bc2d5b4b3fe3d7a49086d0f8cd066
BLAKE2b-256 checksum
How to use checksums
821122bba8e94aafb96280a1b06c85c1d452486b5a6ab41bdbee39bfdd5872fb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Security for branta 3.2.2

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

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