Qefro Python Backend SDK
qefro-backend connects external business systems to Qefro
(ERP / POS / CRM) over the signed /qefro webhook.
It is not how you build a Marketplace App. Marketplace Apps are
metadata: qefro app init → validate → package → install.
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 talks to managed storage (ADR-002) the same way as the JS SDK —
never call Mongo or storage-service URLs yourself.
Customer Hub Person and Business Events
ctx.person queues Customer Hub mutations (note, tag, assign, activity,
merge) for the Qefro runtime. Declare events with app.business_event(...)
and publish them with ctx.emit(...) — delivery is outbox → ingest, not CRM
automation.
from qefro_backend import Qefro, on_person_created
app = Qefro("dev-secret")
app.business_event({"event_type": "quotation.created", "version": 1})
@app.tool("create_quotation", auth="none")
async def create_quotation(ctx):
await ctx.person.tag("vip")
ctx.emit({"event_type": "quotation.created", "event_id": "Q-1001", "data": {"amount": 125000}})
return {"id": "Q-1001"}
app.flow({"id": "welcome", "trigger": on_person_created()}).message("hi", "Hi").complete("done")
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() .message() .tag() .assign() .activity(). 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
Metadata
Release files for qefro-backend 1.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| qefro_backend-1.3.0.tar.gz | 39.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| qefro_backend-1.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 87.6 kB
Release files / qefro_backend-1.3.0.tar.gz
| Download URL | qefro_backend-1.3.0.tar.gz |
|---|---|
| Size | 39.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b5666ae9c86d85322b8f10d406ffbebd421e812f39228951f85bb17383171245
|
|
BLAKE2b-256 checksum How to use checksums |
34d9d78a68d116eed32d7d7a63d837126e28336f7349362c84d7b7bb0b0f94c9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / qefro_backend-1.3.0-py3-none-any.whl
| Download URL | qefro_backend-1.3.0-py3-none-any.whl |
|---|---|
| Size | 47.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ff8869b165b6c32c1becff17674ff0065cf3050d936dadf51cbf8cedd942c58c
|
|
BLAKE2b-256 checksum How to use checksums |
0ee2e7e224e3854d00752c5e4ff0d038c6b22f15f9b2ec640d7ce7d73369b447
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|