Alpha — under active development. Every alpha release may include breaking changes. Pin exact versions during alpha.
Integrate billing with Vatly, the European Merchant of Record for AI, software and
digital products, without tying your application to a framework or database.
Fluent adds chainable checkout/subscription builders, customer and subscription
handles, pluggable persistence, and a signed webhook-to-domain-event pipeline on
top of vatly-api-python. Your application owns authentication, entitlements, storage,
transactions and jobs. Python 3.10+, fully typed, async-first.
pip install vatly-fluent-python==0.1.0a3 vatly-api-python==0.1.0a3For development, run .agents/setup or uv sync --locked. Both development and
package metadata use the exact SDK version from PyPI.
from vatly import AsyncVatlyClient, RequestOptions
from vatly_fluent import Vatly, Wiring
async def start_subscription(plan_id: str, purchase_id: str):
async with AsyncVatlyClient() as api: # reads VATLY_API_KEY
vatly = Vatly(
Wiring(
api=api,
redirect_url_success="https://app.example.com/billing/return",
redirect_url_canceled="https://app.example.com/billing/canceled",
)
)
checkout = await (
vatly.subscribe()
.to_plan(plan_id)
.with_quantity(3)
.with_trial_days(14)
.with_metadata({"purchaseId": purchase_id})
.create(options=RequestOptions(idempotency_key=f"subscribe:{purchase_id}"))
)
return checkout.get_url()This creates a checkout, not a subscription. Confirm payment through signed
webhooks, not redirects. No API calls happen until create(). Use a new builder
per purchase and persist a stable operation ID before sending. SDK errors pass
through unchanged; FluentError means invalid local input or missing wiring.
Wire CheckoutRecords, Repository[LocalSubscription], Repository[Stored[Order]],
and WebhookCalls to your application's database. For entity-linked checkouts:
from vatly_fluent import EntityRef
# In an authorized handler, with vatly.wiring.checkouts configured:
builder = vatly.subscribe().to_plan(plan_id).for_entity(EntityRef("team", team_id))Ownership follows your stored checkout → order → subscription links. It never
falls back to customerId, which is only billing identity and may be shared or null.
Metadata is correlation data, not authorization. A signed webhook authenticates
Vatly's snapshot; it does not authorize a browser-provided entity ID.
Linkage follows each order line's relatedResources.subscriptionId, which names
the signup's subscription on the initial order, and productType="subscription"
renewal lines. subscription_plan product IDs remain plan references. A signed
order.paid snapshot can carry a null subscriptionId; reconcile the checkout to
read the current order. See Subscriptions.
Fluent never guesses a subscription from a customer, plan or quantity.
- Getting started and configuration
- Checkouts, customers, customer portal
- Subscriptions and orders
- Driver contracts and transactional webhooks
- Testing, porting decisions, release notes
- SQLite driver example, with atomic dedup, rollback and outbox tests
See the development guide for setup and contributor checks.
uv sync --locked
uv run ruff check .
uv run ruff format --check .
uv run mypy
uv run pytest
uv build
uv run twine check dist/*
uv run python scripts/check_package.pyCI runs these checks across Python 3.10–3.14. Release workflows are prepared but
require PyPI trusted-publisher configuration and an explicitly published GitHub
release. No release is triggered by pushing main.