Skip to main content

ln.bot

PyPI version PyPI downloads Python License: MIT

The official Python SDK for ln.bot — Bitcoin for AI Agents.

Give your AI agents, apps, and services access to Bitcoin over the Lightning Network. Create wallets, send and receive sats, and get real-time payment notifications.

from lnbot import LnBot

ln = LnBot(api_key="uk_...")
w = ln.wallet("wal_...")

invoice = w.invoices.create(amount=1000, memo="Coffee")

ln.bot also ships a TypeScript SDK, C# SDK, Go SDK, Rust SDK, CLI, and MCP server.


Install

pip install lnbot

Quick start

Register an account

from lnbot import LnBot

ln = LnBot()
account = ln.register()
print(account.primary_key)
print(account.recovery_passphrase)

Create a wallet

ln = LnBot(api_key=account.primary_key)
wallet = ln.wallets.create()
print(wallet.wallet_id)

Receive sats

w = ln.wallet(wallet.wallet_id)

invoice = w.invoices.create(amount=1000, memo="Payment for task #42")
print(invoice.bolt11)

Wait for payment (SSE)

for event in w.invoices.watch(invoice.number):
    if event.event == "settled":
        print("Paid!")
        break

An expired invoice is not always final: one paid just before it expired can turn settled a moment later. Before giving up, check again with w.invoices.get(invoice.number), or keep listening for invoice.settled on w.events.stream() or a webhook.

If the wait runs out first (timeout= seconds, 60 by default, 300 at most), the last event is timeout: event.data is the current state; read again or watch again.

Event stream and webhook payloads use snake_case on the wire (max_fee, created_at). The SDK converts event payloads to the same field names as its REST types; a webhook handler reads the snake_case JSON as is.

Send sats

payment = w.payments.create(target="alice@ln.bot", amount=500)

Leave max_fee unset. The server defaults it to 1% of the amount (minimum 20 sats), which covers the network fee (Spark's 0.25% plus Lightning routing) for most payments. Only the fee estimate plus a small buffer is held while the payment is in flight. On success the wallet is debited amount + actual_fee + service_fee.

If the fee estimate is above the limit, create raises BadRequestError (for example Network fee estimate (X sats) exceeds maxFee (Y sats).). Pass a higher max_fee only if you accept that fee.

A payment can stay pending for a while: it is retried automatically until it settles or fails. It fails if the invoice expires first, and after 24 hours at most. If watch() runs out of time first, it ends with a timeout event whose data is the current state: check again with w.payments.get(payment.number) or watch again. A failed payment carries the reason in failure_reason.

Price a payment before sending

quote = w.payments.resolve(target="alice@example.com", amount=500)
print(quote.network_fee_estimate, quote.service_fee, quote.total)

if quote.fee_warning:
    print(quote.fee_warning)  # sending without max_fee would be rejected

For a BOLT11 invoice, leave out amount (or pass the invoice amount). quote.internal is True when the recipient is a wallet on ln.bot, which makes the transfer free.

Pricing a Lightning address or LNURL fetches an invoice from the recipient, so it is slower. Call it once the amount is settled, not on every keystroke.

resolve raises BadRequestError when the target can't be paid, for example Recipient not found. for an unknown ln.bot address or Cannot transfer to yourself. for your own. When the amount is outside the recipient's range, the JSON in the error's body also carries min and max.

Send everything

Resolve a Lightning address or LNURL without an amount to get max_sendable, the most this wallet can send there now after fees:

quote = w.payments.resolve(target="alice@example.com")

if quote.max_sendable:
    w.payments.create(target="alice@example.com", amount=quote.max_sendable)

Don't pass max_fee here: max_sendable assumes the default. It is 0 when the balance can't cover the recipient's minimum.

Check balance

info = w.get()
print(f"{info.available} sats available")

Wallet-scoped API

All wallet operations go through a Wallet handle obtained via ln.wallet(wallet_id):

w = ln.wallet("wal_abc123")

# Wallet info
info = w.get()
w.update(name="production")

# Sub-resources
w.key           # Wallet key management (wk_ keys)
w.invoices      # Create, list, get, watch invoices
w.payments      # Send, list, get, watch, resolve payments
w.addresses     # Create, list, delete, transfer Lightning addresses
w.transactions  # List transaction history
w.webhooks      # Create, list, delete webhook endpoints
w.events        # Real-time SSE event stream
w.l402          # L402 paywall authentication

Account-level operations stay on the client:

ln.register()                       # Register new account
ln.me()                             # Get authenticated identity
ln.wallets.create()                 # Create wallet
ln.wallets.list()                   # List wallets
ln.keys.rotate(0)                   # Rotate account key
ln.invoices.create_for_wallet(...)  # Public invoice by wallet ID
ln.invoices.create_for_address(...) # Public invoice by address

Async support

Every method has an async equivalent via AsyncLnBot:

from lnbot import AsyncLnBot

async with AsyncLnBot(api_key="uk_...") as ln:
    w = ln.wallet("wal_...")
    info = await w.get()
    invoice = await w.invoices.create(amount=1000)

    async for event in w.invoices.watch(invoice.number):
        if event.event == "settled":
            print("Paid!")
            break

Error handling

from lnbot import LnBot, BadRequestError, UnauthorizedError, NotFoundError, ConflictError, LnBotError

try:
    w.payments.create(target="invalid", amount=100)
except BadRequestError:
    ...  # 400
except UnauthorizedError:
    ...  # 401
except NotFoundError:
    ...  # 404
except ConflictError:
    ...  # 409
except LnBotError as e:
    print(e.status, e.body)

Configuration

from lnbot import LnBot

ln = LnBot(
    api_key="uk_...",                 # or set LNBOT_API_KEY env var
    base_url="https://api.ln.bot",    # optional — this is the default
    timeout=30.0,                     # optional — request timeout in seconds
)

The API key can also be provided via the LNBOT_API_KEY environment variable. If both are provided, the constructor argument takes precedence.


L402 paywalls

w = ln.wallet("wal_...")

# Create a challenge (server side)
challenge = w.l402.create_challenge(amount=100, description="API access", expiry_seconds=3600)

# Pay the challenge (client side). max_fee works as for payments: leave it unset for the default.
result = w.l402.pay(www_authenticate=challenge.www_authenticate)
print(result.fee)  # network fee charged

# Verify a token (server side, stateless)
v = w.l402.verify(authorization=result.authorization)
print(v.valid)

Features

  • Zero extra dependencies — only httpx
  • Wallet-scoped API — ln.wallet(id) returns a typed scope with all sub-resources
  • Sync + async — LnBot and AsyncLnBot with identical APIs
  • Typed exceptions — BadRequestError, NotFoundError, ConflictError, UnauthorizedError, ForbiddenError
  • SSE support — watch() returns an iterator/async iterator for real-time events
  • Dataclass responses — all responses are frozen dataclasses

Requirements

  • Python 3.10+
  • Get your API key at ln.bot

Other SDKs

License

MIT

Metadata

Release files for lnbot 1.1.1

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

Source distribution (sdist)

Source distribution for lnbot 1.1.1
File Size Uploaded Provenance
lnbot-1.1.1.tar.gz 37.9 kB Without provenance Details

Built distribution (wheel)

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

Total release size: 53.5 kB

Release files / lnbot-1.1.1.tar.gz

Download URL lnbot-1.1.1.tar.gz
Size 37.9 kB
Tags Source
SHA-256 checksum
How to use checksums
290514f5b36af23aba875d4d5452327846a9df5be7a4f536e64dc58f2f978812
BLAKE2b-256 checksum
How to use checksums
003038dcffa008c0dbee5b2059c4ddacb47e97461566c3d87a2361767e5e5e63
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / lnbot-1.1.1-py3-none-any.whl

Download URL lnbot-1.1.1-py3-none-any.whl
Size 15.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
abae75eb15eee13ba624f3b6885d25e923031c1f7a40f8270728b1b703a2e601
BLAKE2b-256 checksum
How to use checksums
eb5e2c1e6b5aab33b23bad7bab39a4c91f575b4c5e356d86693cf0f060762166
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Security for lnbot 1.1.1

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.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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