Adapters and the API
Run standalone today, embed in any app tomorrow.
The engine is domain-blind. It approves a reference (targetType + targetId) and never
contains another app’s business logic. Two modes share one submission endpoint.
Standalone mode (today)
Form submissions use the built-in standalone.request target: the form’s values become the
payload, the payload feeds conditional rules, and the decision is recorded on the target.
POST /requests
{ "policyId": "…", "title": "Field team laptops",
"payload": { "amount": 9200, "category": "it-equipment" } }
Adapter mode (the suite contract)
A host app registers one adapter and hands off its own objects:
interface ApprovalTargetAdapter {
targetType: string; // e.g. "document.transmittal"
loadContext(orgId, targetId): Promise<{ code; title; href; snapshot }>;
applyDecision(orgId, targetId, finalAction, comment?): Promise<void>;
}
loadContext gives the engine display fields and the rule snapshot; applyDecision is called
exactly once on the terminal outcome. This is the entire integration surface: the host app
keeps its own workflow, and delegates only the act of approval.
Key endpoints
| Method and path | Purpose |
|---|---|
GET /policies · POST /policies | List and define policies |
POST /requests · GET /requests | Submit and list requests |
GET /requests/:id | Detail: stage map, payload, timeline |
GET /inbox · POST /assignments/:id/decide | Your queue and your decision |
POST /assignments/bulk-decide | Guardrailed bulk approve |
POST /external/decide | Magic-link + code decisions (public) |
GET /activity · GET /analytics/summary | Audit feed and dashboard rollup |
Realtime clients join org and user rooms over Socket.IO and receive inbox.changed,
request.changed, and notifications.changed signals.