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.
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 bachs_io-0.0.3.tar.gz.
File metadata
- Download URL: bachs_io-0.0.3.tar.gz
- Upload date:
- Size: 59.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
06ad6e15d487fb069724ffc88da3e78592d5689eeb2e0792fd506d7ae71663c9
|
|
| MD5 |
4619d8752b0f331e5a51bec7b7271c78
|
|
| BLAKE2b-256 |
a50b8662f5bd2abf68f244a94b8aa8993068813b38473b1cce5e36dae608a7f2
|
File details
Details for the file bachs_io-0.0.3-py3-none-any.whl.
File metadata
- Download URL: bachs_io-0.0.3-py3-none-any.whl
- Upload date:
- Size: 55.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
779ab814ff8484bd825e4c288a8b15595c54694b89b17e8a97e8b91ad114bed7
|
|
| MD5 |
c94c69bc2d8804a3048a3b0ba45d93da
|
|
| BLAKE2b-256 |
7358b61b3d14b8e966547d3561d2455074483843f5a6d1fa07aa09861fdfaaa2
|