Skip to main content

SDK application development

Package

npm install @qefro-ai/backend

Import:

import { Qefro } from '@qefro-ai/backend';
// or: import Qefro from '@qefro-ai/backend';

Application shape

Application
├── Tools app.tool
├── Flows app.flow (metadata; runtime executes)
├── Events app.event / webhook / schedule
├── Customer app.customer(provider) optional
├── Marketing app.marketing({…}) metadata
├── Organization app.organization({…}) metadata
└── HTTP app.listen → POST /qefro

Constructor

new Qefro({
signingSecret: string;
protocolVersion?: string; // default '1'
maxTimestampSkewSeconds?: number; // default 300
endpointPath?: string; // default '/qefro'
});

Middleware

app.use(async (ctx, next) => { await next(); });
app.before(async (ctx) => {});
app.after(async (ctx, result) => {});

Tool registration signatures

All supported by app.tool:

app.tool('name', handler);
app.tool('name', metadata, handler);
app.tool('name', handler, metadata);
app.tool({ name: 'name', …metadata }, handler);

See tools.md.

Flows

Flows are metadata only. The SDK advertises them on capabilities.list; the Qefro Runtime orchestrates execution.

app.flow({
id: 'reservation',
version: 1,
trigger: { type: 'conversation' }, // or event / schedule / webhook
})
.ask({ /* … */ })
.tool({ /* … */ })
.complete({ /* … */ });

Events, webhooks, schedules

app.event({ name: 'shopify.order.created', description: '…' }, async (ctx) => {});
app.webhook({ name: 'shipment.delivered' }, async (ctx) => {});
app.schedule({ name: 'daily.digest', cron: '0 9 * * *' }, async (ctx) => {});

Advertised under capabilities.list as events, webhooks, schedules.

Implementation detail: These handlers are stored and advertised only. The SDK /qefro dispatcher does not call them — the Qefro Runtime owns delivery.

Marketing & organization

Call once each:

app.marketing({ version: 1, audiences: [], variables: [], actions: [], landingPages: [], channels: [] });
app.organization({ version: 1, events: [], actions: [], tasks: [] });

Organization capability ids must be opaque (no . / app prefix).

Listen

const handle = await app.listen({ port: 8080, host: '0.0.0.0', path: '/qefro' });
// handle.url → http://0.0.0.0:8080/qefro
// await handle.close();

For embedding in an existing HTTP framework, use handleRaw(body, headers) (verifies signature then dispatches).

Shared code across models

Keep tools, schemas, and domain logic identical for external and managed deployments. Change packaging and registration only — see migration-external-to-managed.md.