bachs-io — Python SDK for the Bachs API
Production-grade Python SDK for the Bachs payments & billing platform — the payments stack for African internet businesses. Define products, run hosted checkouts, collect payments in customers' local currencies, manage subscriptions and trials, issue refunds, respond to disputes, convert balances, withdraw funds, configure webhooks, and run a Connect-style platform with connected accounts, transfers, and split payments.
Generated from the official Bachs OpenAPI spec (v1.0.0, 96 operations).
Installation
pip install bachs-io
Python 3.9+ required. Depends on httpx and pydantic>=2.
Quickstart
import bachs
client = bachs.BachsClient(api_key="sk_sandbox_...")
# Product-based hosted checkout
session = client.checkouts.create_checkout_session(
customer={"email": "jane@example.com", "name": "Jane Doe"},
product_cart=[{"product_id": "prod_abc", "quantity": 1}],
success_url="https://shop.example.com/success",
)
print(session.checkout_url)
# Retrieve a payment
payment = client.payments.get_payment_detail("pay_123")
client.close()
Authentication & environments
Every request is authenticated with Authorization: Bearer sk_....
sk_sandbox_...keys automatically targethttps://sandbox-api.bachs.iosk_live_...keys automatically targethttps://api.bachs.io- Override with
base_url=...(useful for mocking/staging)
You can also set the BACHS_API_KEY environment variable and omit api_key.
The API surface
Methods are grouped into typed resources on the client:
| Resource | Highlights |
|---|---|
client.payments |
list/retrieve payments, charge status, payment methods & rails, supported currencies |
client.checkouts |
create/retrieve checkout sessions and API checkouts |
client.customers |
create/update/retrieve customers, customer portal sessions |
client.products |
create/update/list products, pricing, archive/unarchive |
client.product_groups |
bundles of products for multi-plan checkout |
client.uploads |
upload/retrieve/delete product media |
client.subscriptions |
list/retrieve/update/cancel subscriptions |
client.refunds |
create/list/retrieve refunds |
client.disputes |
list/get disputes, evidence upload/update/submit |
client.accounts |
organization balances |
client.organizations |
your organization, checkout settings, connected accounts |
client.connected_accounts |
Connect-style account lifecycle, Tasks/checklist, account links, uploads |
client.transfers |
move funds between platform and connected accounts |
client.conversions |
quote and execute currency conversions |
client.payouts |
destinations, quotes, bank lookup, withdrawals |
client.webhooks |
endpoints, secrets, events, metrics, replay |
Every method is fully typed — parameters map 1:1 to the API request body/query,
and every response is validated into a typed model (see bachs.models, also
exported from the package root). Unknown fields in API responses are preserved
rather than dropped, so the SDK tolerates additive API changes.
Conventions
- Money is always a decimal string (e.g.
"29.00") at the currency's precision — the SDK never converts between units. - Timestamps are ISO 8601 UTC strings.
- IDs (
cust_,prod_,sub_,chk_,evt_, ...) are opaque strings.
Sending null to clear a field
Request models are serialized with None fields omitted. If you need to send an
explicit null (e.g. clearing billing_address on update_customer), pass an
empty representation for that field after reviewing the API docs for the
endpoint's accepted values.
Pagination
List endpoints return a typed response carrying items (or items + a
pagination block). Use the limit/offset (or cursor) parameters and read
pagination.next_cursor / pagination.has_more to walk pages:
page = client.payments.list_payments(limit=100)
while True:
for payment in page.items:
...
if not page.pagination.has_more:
break
page = client.payments.list_payments(limit=100, offset=page.pagination.offset + page.pagination.returned)
Error handling
Every failure is raised as a subclass of bachs.BachsError:
try:
client.refunds.create_refund(charge_id="pay_1", reference="ref_1")
except bachs.NotFoundError as exc:
print(exc.error_code, exc.detail, exc.doc_url)
except bachs.RateLimitError as exc:
print(exc.retry_after) # seconds, from Retry-After
except bachs.ApiServerError as exc:
pass # safe to retry
| Exception | HTTP |
|---|---|
BadRequestError |
400 |
AuthenticationError |
401 |
ForbiddenError |
403 |
NotFoundError |
404 |
ConflictError |
409 |
UnprocessableEntityError |
422 |
PreconditionRequiredError |
428 |
RateLimitError |
429 (has .retry_after) |
ApiServerError |
5xx |
APIConnectionError |
transport-level (DNS/timeouts) |
Errors carry status_code, error_code, detail, doc_url, and errors
(field-level validation details on 400).
Idempotency
All mutating requests accept an idempotency_key= argument that is sent as the
Idempotency-Key header. Reuse the same key across retries to avoid duplicate
creates.
Retries
429 and 5xx responses are retried automatically (up to max_retries=2, with
exponential backoff and Retry-After honoured for rate limits). Configure via
BachsClient(max_retries=..., retry_on_429=...).
Webhook verification
Webhooks are the source of truth for fulfilment. Verify every delivery with the endpoint's signing secret:
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
@app.post("/webhooks")
async def receive(request: Request):
raw = await request.body()
try:
event = bachs.construct_event(
raw,
request.headers.get("X-Bachs-Signature", ""),
"whsec_...",
timestamp_header=request.headers.get("X-Bachs-Timestamp"),
)
except bachs.SignatureVerificationError:
raise HTTPException(400, "Bad signature")
if event.event_type == "collection.succeeded":
... # fulfil the order
return {"status": "ok"}
verify_header returns a bool; construct_event parses and validates in one
step. generate_signature is provided for testing.
Async
AsyncBachsClient mirrors the sync client:
async with bachs.AsyncBachsClient(api_key="sk_sandbox_...") as client:
session = await client.checkouts.create_checkout_session(
customer={"email": "jane@example.com", "name": "Jane Doe"},
product_cart=[{"product_id": "prod_abc"}],
)
Connect & acting on behalf of accounts
Endpoints that support X-Connected-Account-ID (checkout settings, transfers)
expose a connected_account_id= parameter:
client.organizations.get_checkout_settings(connected_account_id="org_ca")
client.transfers.create_transfer(
destination="org_ca", amount="7000.00", currency="NGN",
connected_account_id="org_ca",
)
Uploads
Upload endpoints accept a file path, bytes, a file-like object, or an httpx
(filename, content, content_type) tuple:
upload = client.uploads.create_upload(file="./cover.jpg", scope="product-media")
client.products.create_product(name="Pro", price={"currency": "USD", "amount": "29.00"},
media=[upload.upload_id])
Development
python -m venv .venv && .venv/bin/pip install -e .
.venv/bin/python -m pytest
The typed models and resource clients are generated from the Bachs OpenAPI spec
(docs.bachs.io/docs/openapi/openapi.json) and checked in under
bachs/_models.py, bachs/_resources.py, and bachs/_async_resources.py.
Release files for bachs-io 0.0.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| bachs_io-0.0.3.tar.gz | 59.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bachs_io-0.0.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 114.3 kB
Release files / bachs_io-0.0.3.tar.gz
| Download URL | bachs_io-0.0.3.tar.gz |
|---|---|
| Size | 59.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
06ad6e15d487fb069724ffc88da3e78592d5689eeb2e0792fd506d7ae71663c9
|
|
BLAKE2b-256 checksum How to use checksums |
a50b8662f5bd2abf68f244a94b8aa8993068813b38473b1cce5e36dae608a7f2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.2
|
Release files / bachs_io-0.0.3-py3-none-any.whl
| Download URL | bachs_io-0.0.3-py3-none-any.whl |
|---|---|
| Size | 55.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
779ab814ff8484bd825e4c288a8b15595c54694b89b17e8a97e8b91ad114bed7
|
|
BLAKE2b-256 checksum How to use checksums |
7358b61b3d14b8e966547d3561d2455074483843f5a6d1fa07aa09861fdfaaa2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.2
|