Python
quantumbpm-sdk — async-first, built on asyncio. Requires Python 3.10+.
Installation
pip install quantumbpm-sdk
Quick start
import asyncio
from quantumbpm import QuantumBPM, Vars, ZitadelTokenProvider
async def main():
provider = ZitadelTokenProvider(
"./service-account.json",
"https://auth.quantumbpm.com",
"your-zitadel-project-id",
)
async with QuantumBPM(
base_url="https://api.quantumbpm.com",
project_id="YOUR_PROJECT_ID",
token_provider=provider,
) as client:
result = await client.dmn.evaluate(
"loan-eligibility",
Vars().set("requestedAmt", 1000).set("creditScore", 720),
)
print(result)
asyncio.run(main())
The async context manager (async with) acquires a fresh bearer token on entry; calling methods directly outside the context also works — the SDK refreshes tokens on demand.
BPMN
draft = await client.bpmn.create_resource("loan-process", bpmn_xml)
await client.bpmn.deploy_resource(draft.id)
deployed = await client.bpmn.get_resource(draft.id)
process_def = deployed.processes[0]
workflow_id = await client.bpmn.start_instance(
process_def.id,
Vars().set("applicantID", "u-123"),
)
Correlation keys (businessId)
Stamp a caller-supplied correlation key (order number, ticket ID, etc.) on a new instance by passing business_id=. The key is inherited by every child instance, external job, user task, and DMN execution emitted by the process — workers read it as job.business_id.
await client.bpmn.start_instance(process_def.id, vars, business_id="ORDER-42")
# Filter listings by the same key.
page = await client.bpmn.list_instances(business_id="ORDER-42")
tasks = await client.bpmn.list_user_tasks(business_id="ORDER-42")
# DMN evaluations stamp it on the resulting execution row.
result = await client.dmn.evaluate("loan-eligibility", vars, business_id="ORDER-42")
External job workers
Decorator-based registration. The runtime uses asyncio task groups per task type for concurrency.
from quantumbpm import Vars, BpmnError
from quantumbpm.workers import Job
worker = client.new_worker(client_id="billing-svc")
stop = asyncio.Event()
@worker.handler("send-email", max_jobs=10, lock_duration="1m")
async def handle(job: Job) -> Vars:
recipient = job.vars.lookup("recipient")
await emailer.send(recipient)
return Vars().set("messageID", "msg-123") # → Complete
await worker.run(stop) # resolves when stop.set() is called
Raise a BpmnError to fail with a code boundary error events can catch:
raise BpmnError("INSUFFICIENT_FUNDS", Vars().set("balance", 12.0))
Any other raised exception is reported with the code WORKER_ERROR and the message attached as the error variable (truncated to 2048 bytes by default, configurable via the max_error_message_bytes worker option). A boundary error event with errorRef="WORKER_ERROR" will catch it once the retry budget is exhausted — see Failure handling.
Type-annotate Job[T] with a Pydantic model for opt-in typed dispatch — the runtime decodes job variables into T before invoking the handler:
from pydantic import BaseModel
class EmailJob(BaseModel):
recipient: str
subject: str
@worker.handler("send-email")
async def handle(job: Job[EmailJob]) -> Vars:
await emailer.send(job.typed.recipient, job.typed.subject)
return Vars()
Full reference
See the SDK README for the complete API surface and the Vars typed-access helpers.