Velobase Billing SDK for Python
Project description
velobase-billing
Official Velobase Billing SDK for Python.
- Synchronous and asynchronous clients
- Type-safe with Pydantic v2 models and
py.typedsupport - Automatic retries with exponential backoff
- Python 3.9+
Installation
pip install velobase-billing
Quick Start
import uuid
from velobase_billing import Velobase
vb = Velobase(api_key="vb_live_xxx")
# 1. Deposit credits to a customer (creates the customer if new)
deposit = vb.customers.deposit(customer_id="user_123", amount=1000)
# 2. Check balance
customer = vb.customers.get("user_123")
print(customer.balance.available) # 1000.0
# 3. Generate transaction_id once and persist before freezing
transaction_id = f"user_123_{uuid.uuid4().hex}"
# 4. Freeze credits before doing work
freeze = vb.billing.freeze(
customer_id="user_123",
amount=50,
transaction_id=transaction_id,
)
# 5a. Job succeeded — consume (supports partial)
consume = vb.billing.consume(
transaction_id=transaction_id,
actual_amount=32, # only charge 32, return 18
)
# 5b. Or if the job failed — unfreeze to return all
unfreeze = vb.billing.unfreeze(transaction_id=transaction_id)
How It Works
Velobase Billing uses a freeze-then-consume pattern to safely manage credits:
deposit -> freeze -> consume (normal flow)
-> unfreeze (failure/cancellation)
It also supports a direct deduct pattern for immediate deduction without freezing:
deposit -> deduct (immediate deduction)
- Deposit — Add integer credits to a customer's wallet. Creates the customer automatically on first deposit. Supports
wallet/sourcefor balance categories, andstarts_at/expires_atfor time-limited credits.credit_typeis kept only as a deprecated alias forwallet. - Freeze — Pre-authorize an amount before performing work. The frozen credits are deducted from
availablebut not yetused. Each freeze is identified by a uniquetransaction_idyou provide. Supportscredit_typesto freeze from specific credit categories. - Consume — After the work is done, settle the frozen amount. You can pass
actual_amountto charge less than what was frozen; the difference is automatically returned. - Unfreeze — If the work fails or is cancelled, release the full frozen amount back to the customer.
- Deduct — Directly deduct credits from a customer without freezing first. Useful for immediate charges. Supports
credit_typesto deduct from specific credit categories. - Ledger — Query a customer's transaction history with filtering and cursor-based pagination.
All write operations are idempotent — repeating the same transaction_id (freeze/consume/unfreeze/deduct) or idempotency_key (deposit) returns the original result without double-charging.
Async Usage
Every method is available in an async version via AsyncVelobase:
from velobase_billing import AsyncVelobase
async def main():
vb = AsyncVelobase(api_key="vb_live_xxx")
deposit = await vb.customers.deposit(customer_id="user_123", amount=1000)
customer = await vb.customers.get("user_123")
print(customer.balance.available)
await vb.close()
Context Manager
Both clients support context managers to ensure the underlying HTTP connection is closed:
# Sync
with Velobase(api_key="vb_live_xxx") as vb:
customer = vb.customers.get("user_123")
# Async
async with AsyncVelobase(api_key="vb_live_xxx") as vb:
customer = await vb.customers.get("user_123")
Configuration
vb = Velobase(
api_key="vb_live_xxx", # Required. Your Velobase API key.
base_url="https://api.velobase.io", # Optional. Override the API endpoint.
timeout=30.0, # Optional. Request timeout in seconds (default: 30).
max_retries=2, # Optional. Retry count on 5xx/network errors (default: 2).
)
Usage Examples
Deposit with idempotency
# Safe to retry — the second call returns the same result without double-charging
result = vb.customers.deposit(
customer_id="user_123",
amount=500,
idempotency_key="order_abc_payment",
description="Purchase of 500 credits",
)
print(result.added_amount) # 500.0
print(result.is_idempotent_replay) # False on first call, True on retries
Deposit with wallet and expiry
result = vb.customers.deposit(
customer_id="user_123",
amount=1000,
wallet="bonus",
source="annual_campaign",
starts_at="2025-01-01T00:00:00Z",
expires_at="2025-12-31T23:59:59Z",
description="Annual bonus credits",
)
print(result.wallet) # "bonus"
print(result.source) # "annual_campaign"
print(result.starts_at) # "2025-01-01T00:00:00.000Z"
print(result.expires_at) # "2025-12-31T23:59:59.000Z"
Deposit with customer metadata
result = vb.customers.deposit(
customer_id="user_123",
amount=1000,
name="Alice",
email="alice@example.com",
metadata={"plan": "pro", "source": "stripe"},
)
Full billing flow (freeze-then-consume)
CUSTOMER = "user_123"
JOB_ID = "video_gen_001"
# Check balance before starting
before = vb.customers.get(CUSTOMER)
print(f"Available: {before.balance.available}")
# Freeze the estimated cost
vb.billing.freeze(
customer_id=CUSTOMER,
amount=100,
transaction_id=JOB_ID,
business_type="TASK",
description="1080p video, ~60s",
)
# ... do the work ...
# Settle with the actual cost (partial consumption)
result = vb.billing.consume(
transaction_id=JOB_ID,
actual_amount=73,
)
print(f"Charged: {result.consumed_amount}") # 73.0
print(f"Returned: {result.returned_amount}") # 27.0
# Verify final balance
after = vb.customers.get(CUSTOMER)
print(f"Available: {after.balance.available}")
Direct deduct (without freezing)
CUSTOMER = "user_123"
TXN_ID = "api_call_001"
result = vb.billing.deduct(
customer_id=CUSTOMER,
amount=5,
transaction_id=TXN_ID,
business_type="TASK",
description="API call charge",
)
print(f"Deducted: {result.deducted_amount}") # 5.0
print(f"At: {result.deducted_at}")
Freeze with credit_types filter
# Only freeze from specific credit categories
freeze = vb.billing.freeze(
customer_id="user_123",
amount=50,
transaction_id="job_abc",
credit_types=["BONUS", "DEFAULT"],
)
Query transaction ledger
# List all ledger entries (default limit=20)
ledger = vb.customers.ledger("user_123")
for entry in ledger.items:
print(entry.operation_type, entry.amount, entry.credit_type, entry.created_at)
print(f"Total: {ledger.total_count}")
# Filter by operation type
grants = vb.customers.ledger("user_123", operation_type="GRANT")
# Filter by transaction_id
txn_entries = vb.customers.ledger("user_123", transaction_id="job_abc")
# Paginate with cursor
page1 = vb.customers.ledger("user_123", limit=10)
if page1.has_more:
page2 = vb.customers.ledger("user_123", limit=10, cursor=page1.next_cursor)
Customer balance structure
customer = vb.customers.get("user_123")
# Wallets are keyed by wallet/category name.
default_wallet = customer.wallets["default"]
default_wallet.total # total deposited
default_wallet.used # total consumed
default_wallet.frozen # currently frozen (pending)
default_wallet.available # total - used - frozen
# Sources inside each wallet preserve validity windows.
for source in default_wallet.sources:
print(source.source) # "default", "stripe_checkout", etc.
print(source.available)
print(source.starts_at) # None or ISO date string
print(source.expires_at) # None or ISO date string
# Compatibility: customer.balance and customer.accounts are still available as
# derived views for older Python code.
customer.balance.available
customer.accounts[0].credit_type
API Reference
vb.customers.deposit(...) -> DepositResponse
Deposit credits. Creates the customer if they don't exist.
| Parameter | Type | Required | Description |
|---|---|---|---|
customer_id |
str |
Yes | Your unique customer identifier |
amount |
int |
Yes | Integer credits to deposit (must be > 0) |
wallet |
str |
No | Wallet/category to credit. Defaults to "default" on server. |
source |
str |
No | Optional source label for this grant. Defaults to "default" on server. |
credit_type |
str |
No | Deprecated alias for wallet, kept for compatibility. |
starts_at |
str |
No | ISO datetime string. When the credits become active. |
expires_at |
str |
No | ISO datetime string. When the credits expire. Must be after starts_at. |
idempotency_key |
str |
No | Prevents duplicate deposits on retry |
name |
str |
No | Customer display name |
email |
str |
No | Customer email |
metadata |
dict |
No | Arbitrary key-value metadata |
description |
str |
No | Description for the deposit |
Returns: DepositResponse with fields customer_id, account_id, wallet, source, total_amount, added_amount, starts_at, expires_at, record_id, is_idempotent_replay
vb.customers.get(customer_id) -> CustomerResponse
Retrieve a customer's balance and account details.
Returns: CustomerResponse with fields id, name, email, metadata, balance, accounts, created_at
vb.customers.ledger(customer_id, ...) -> LedgerResponse
Query a customer's transaction history with filtering and cursor-based pagination.
| Parameter | Type | Required | Description |
|---|---|---|---|
customer_id |
str |
Yes | Customer identifier |
limit |
int |
No | Page size (1–100, default 20) |
cursor |
str |
No | Cursor from a previous next_cursor for pagination |
operation_type |
str |
No | Filter by operation type: FREEZE, CONSUME, UNFREEZE, GRANT, EXPIRE |
transaction_id |
str |
No | Filter by transaction ID |
Returns: LedgerResponse with fields items (list of LedgerEntry), total_count, has_more, next_cursor
Each LedgerEntry has: id, operation_type, amount, wallet, source, credit_type (legacy alias for wallet), transaction_id, business_type, description, account_id, status, created_at
vb.billing.freeze(...) -> FreezeResponse
Freeze credits before performing work.
| Parameter | Type | Required | Description |
|---|---|---|---|
customer_id |
str |
Yes | Customer identifier |
amount |
float |
Yes | Amount to freeze (must be > 0) |
transaction_id |
str |
Yes | Your unique ID for this operation (idempotency key) |
credit_types |
list[str] |
No | Restrict freeze to specific credit categories |
business_type |
BusinessType |
No | Business category. See business_type for accepted values. |
description |
str |
No | Human-readable description |
Returns: FreezeResponse with fields transaction_id, frozen_amount, freeze_details, is_idempotent_replay
vb.billing.consume(...) -> ConsumeResponse
Settle a frozen amount. Supports partial consumption.
| Parameter | Type | Required | Description |
|---|---|---|---|
transaction_id |
str |
Yes | The transaction_id from the freeze |
actual_amount |
float |
No | Actual amount to charge. Defaults to full frozen amount. |
Returns: ConsumeResponse with fields transaction_id, consumed_amount, returned_amount, consume_details, consumed_at, is_idempotent_replay
vb.billing.unfreeze(...) -> UnfreezeResponse
Release a frozen amount back to the customer.
| Parameter | Type | Required | Description |
|---|---|---|---|
transaction_id |
str |
Yes | The transaction_id from the freeze |
Returns: UnfreezeResponse with fields transaction_id, unfrozen_amount, unfreeze_details, unfrozen_at, is_idempotent_replay
vb.billing.deduct(...) -> DeductResponse
Directly deduct credits without freezing first. Useful for immediate charges.
| Parameter | Type | Required | Description |
|---|---|---|---|
customer_id |
str |
Yes | Customer identifier |
amount |
float |
Yes | Amount to deduct (must be > 0) |
transaction_id |
str |
Yes | Your unique ID for this operation (idempotency key) |
credit_types |
list[str] |
No | Restrict deduction to specific credit categories |
business_type |
BusinessType |
No | Business category. See business_type for accepted values. |
description |
str |
No | Human-readable description |
Returns: DeductResponse with fields transaction_id, deducted_amount, deduct_details, deducted_at, is_idempotent_replay
transaction_id
transaction_id uniquely identifies one billing operation (freeze → consume/unfreeze cycle, or a single deduct) and acts as its idempotency key. The server uses it to prevent double-charging on retries.
Recommended format: {customer_id}_{uuid}
import uuid
# Generate once per billing operation, then persist it
transaction_id = f"{customer_id}_{uuid.uuid4().hex}"
# e.g. "user_123_a3f8c21d4e0b4a9f8c1d2e3f4a5b6c7d"
Rules:
- Generate once and store — create the UUID before calling
freeze()ordeduct(), save it to your database, and reuse the same value on retries - Never regenerate at the call site — calling
uuid.uuid4()insidefreeze()produces a different ID on every attempt, breaking idempotency - Unique within your project — two different billing operations must not share the same
transaction_id
# Wrong — new UUID on every call, idempotency broken on retry
vb.billing.freeze(
customer_id=customer_id,
amount=50,
transaction_id=f"{customer_id}_{uuid.uuid4().hex}", # ❌ regenerated each time
)
# Correct — UUID generated once and persisted before calling freeze
transaction_id = db.get_or_create_transaction_id(operation_id, customer_id)
# e.g. db.get_or_create_transaction_id returns an existing ID or
# stores f"{customer_id}_{uuid.uuid4().hex}" on first call
vb.billing.freeze(customer_id=customer_id, amount=50, transaction_id=transaction_id)
# Safe to retry — same transaction_id returns the original result
vb.billing.freeze(customer_id=customer_id, amount=50, transaction_id=transaction_id)
business_type
business_type is an optional parameter on freeze() and deduct() that categorises the billing operation for analytics and reconciliation. The SDK validates the value client-side and raises ValueError before sending any network request.
Accepted values:
| Value | Description |
|---|---|
"UNDEFINED" |
Default / unclassified (server default when omitted) |
"TASK" |
Async task execution (e.g. video generation, image processing) |
"ORDER" |
One-time purchase or order fulfilment |
"MEMBERSHIP" |
Membership plan credit grant |
"SUBSCRIPTION" |
Subscription renewal credit grant |
"FREE_TRIAL" |
Free-trial credit grant |
"ADMIN_GRANT" |
Manually granted credits by an admin |
from velobase_billing import Velobase, BusinessType
vb = Velobase(api_key="vb_live_xxx")
vb.billing.freeze(
customer_id="user_123",
amount=50,
transaction_id="job_abc",
business_type="TASK", # ✅ IDE autocomplete + client-side validation
)
vb.billing.freeze(
customer_id="user_123",
amount=50,
transaction_id="job_abc",
business_type="INVALID_VAL", # ❌ raises ValueError before making a network call
)
Error Handling
All API errors raise typed exceptions that inherit from VelobaseError:
from velobase_billing import VelobaseError, ValidationError, AuthenticationError, NotFoundError
try:
vb.billing.freeze(
customer_id="user_123",
amount=999999,
transaction_id="job_xyz",
)
except ValidationError as e:
# 400 — bad request or insufficient balance
print(e.message) # "insufficient balance"
except AuthenticationError as e:
# 401 — invalid or missing API key
pass
except NotFoundError as e:
# 404 — customer not found
pass
except VelobaseError as e:
# catch-all for other API errors
print(e.status, e.type, e.message)
| Exception | HTTP Status | When |
|---|---|---|
AuthenticationError |
401 | Invalid or missing API key |
ValidationError |
400 | Bad params, insufficient balance |
NotFoundError |
404 | Customer or resource not found |
ConflictError |
409 | Conflicting operation |
InternalError |
500 | Server-side error (auto-retried) |
Retries
The SDK automatically retries on 5xx errors and network failures with exponential backoff (0.5s, 1s, 2s..., capped at 5s). Retries are safe because all Velobase write operations are idempotent.
4xx errors (validation, auth, not found) are never retried.
License
MIT
Project details
Release history Release notifications | RSS feed
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 velobase_billing-0.2.2.tar.gz.
File metadata
- Download URL: velobase_billing-0.2.2.tar.gz
- Upload date:
- Size: 12.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.25
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
148a51a0c993867293fa839489020f25be8e1a08d3a2459f8cbadec1f4b70577
|
|
| MD5 |
7235cf78ac530f2bd4cba08834a68ef7
|
|
| BLAKE2b-256 |
b779861bba97864de1fb9ea2d058d401a71c433c4b0520e7019ed110191fe2f9
|
File details
Details for the file velobase_billing-0.2.2-py3-none-any.whl.
File metadata
- Download URL: velobase_billing-0.2.2-py3-none-any.whl
- Upload date:
- Size: 15.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.25
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bebfd9b9e2afb6e897288382ff2aa05117e3c5d2fc1314553fa9e0c9c31e133e
|
|
| MD5 |
ec1a8a2945aa417dead6616c2f84aa7e
|
|
| BLAKE2b-256 |
83bfd7538894e6dbbc2ea176516903bf39594d8729e3fbb36779cd8271da133d
|