Qefro Python Backend SDK
qefro-backend is the Python framework for building Qefro Business Tool
handlers and Business Flows. Register your tools and flows, serve the signed
Qefro webhook, and let the Qefro Runtime orchestrate everything — including
multi-step flows and customer verification.
Wire-compatible with the JavaScript
and Rust SDKs: same signed
protocol, same capabilities.list schema, same 8 flow step types.
Install
pip install qefro-backend
Zero runtime dependencies — it uses only the Python standard library.
Quick start
from qefro_backend import Qefro
app = Qefro(signing_secret="dev-secret") # or QEFRO_SIGNING_SECRET from env
@app.tool("get_orders", auth="required", lookup={"required": ["email"]})
async def get_orders(ctx):
customer = ctx.customer.require()
return [{"orderId": "ord_1", "customerId": customer["id"]}]
app.run(8088) # POST http://0.0.0.0:8088/qefro
Set the same signing secret in Admin Console → Business Tools → SDK
Connections, then Sync Tools. Handlers may be sync or async.
Customer authorization
Register a provider to resolve and verify customers. auth="required" tools
authorize automatically before the handler runs; a challenge outcome pauses
the tool and asks the customer for a code, then resumes.
from qefro_backend import Qefro, CustomerProvider
app = Qefro("dev-secret")
class Customers(CustomerProvider):
async def lookup(self, ctx):
return {"id": ctx.identity.get("phone", "demo")}
async def authorize(self, ctx):
if not ctx.response:
return ctx.auth.sms_otp(ctx.customer["id"], "Enter the code we texted you.")
if ctx.response.strip() != "123456":
return ctx.auth.sms_otp(ctx.customer["id"], "Wrong code, try again.")
return ctx.auth.success(ctx.customer, {"type": "bearer_token", "access_token": "demo"})
app.customer(Customers())
Customer Hub (optional)
When QEFRO_CUSTOMER_HUB_ENABLED=true, tools can call Hub via
platform.customer on tool.invoke (or QEFRO_CUSTOMER_HUB_URL + service
token). Hub is optional — defaults keep existing apps working
(ENABLED=false, OPTIONAL=true). Soft-skip returns None / no-ops when
Hub is off or unreachable; set QEFRO_CUSTOMER_HUB_OPTIONAL=false to hard-fail.
@app.tool("create_reservation", auth="none")
async def create_reservation(ctx):
customer = await ctx.customer.resolve({"whatsapp_number": ctx.identity.get("phone")})
# Hub properties: ctx.customer.id, .phone_number, .whatsapp_number, .display_name
await ctx.timeline.append({
"event_type": "reservation.created",
"payload": {"code": "R-1001"},
})
await ctx.membership.attach({"solution_id": "restaurant-pro"})
await ctx.consent.grant({"purpose": "marketing"})
return {"customer_id": customer["id"] if customer else None}
ctx.storage (when present in your stack) remains independent — Hub is never
the sole path. External CRM auth via app.customer(provider) is unchanged.
Business Flows
Flows describe how your Business Tools are orchestrated. They are metadata
only — the SDK advertises them through capabilities.list and the Qefro
Runtime discovers, validates, versions, and executes them. Nothing runs inside
the SDK.
(
app.flow({
"id": "order_lookup", # immutable identity — renaming `name` never creates a new flow
"name": "Order Lookup",
"description": "Look up customer orders",
"category": "crm",
"tags": ["customer", "orders"],
"intent": ["track order", "where is my order"],
"inputs": ["email"],
"outputs": ["get_orders"],
})
.ask("email", field="email", prompt="Please enter your email.")
.tool("orders", tool_ref="get_orders")
.complete("done", message="Here are your recent orders.")
)
Every step needs a unique id; tool steps reference an existing Business Tool
by tool_ref. Step builders: .ask() .tool() .challenge() .upload() .condition() .delay() .approval() .complete(). A duplicate/empty flow or step id raises
FlowError. See examples/order-approval for a full
ask → tool → condition → approval → OTP-authenticated tool → complete flow.
Protocol
| Message | Purpose |
|---|---|
ping |
Health / Test Connection |
capabilities.list |
Discover tools and business flows for Sync Tools |
tools.list |
Legacy tool-only discovery (still supported) |
tool.invoke |
Run a handler |
tool.resume |
Continue after a customer challenge reply |
Requests are HMAC-SHA256 signed (X-Qefro-Signature / X-Qefro-Timestamp,
payload v1:<timestamp>:<body>). Responses include X-Qefro-Protocol,
X-Qefro-SDK, and X-Qefro-Version.
Examples
export QEFRO_SIGNING_SECRET=dev-secret
python examples/basic/server.py # ask -> tool -> complete
python examples/order-approval/server.py # condition + approval + OTP-authenticated tool
Docs
Development
pip install -e ".[dev]"
pytest
License
MIT
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 qefro_backend-1.1.0.tar.gz.
File metadata
- Download URL: qefro_backend-1.1.0.tar.gz
- Upload date:
- Size: 19.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b46963a8fef0e5e1f0570fb90f118b03a8ed763bc8e86a93ba882c4efbbec70b
|
|
| MD5 |
a89a1824c326700ffebd318b79a31da7
|
|
| BLAKE2b-256 |
f48661b1c8a7745f0905e9dcdbfa70e0f61f4bed3d080ad78e29d824465770d1
|
File details
Details for the file qefro_backend-1.1.0-py3-none-any.whl.
File metadata
- Download URL: qefro_backend-1.1.0-py3-none-any.whl
- Upload date:
- Size: 20.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dc7d38f5d57bea42c02f1c8a4d25312eda351e9083a43da19d5f522715e1e9e7
|
|
| MD5 |
d4735b7cc10a28abadcce823d0d3475a
|
|
| BLAKE2b-256 |
34614d12ffff4edb693e93709bb7d8f9ce0e1354926453f666c924f63d07467c
|