In your own product
Let an approver decide inside your app: a hosted decision page, or one framed in your own UI with Deliverd Elements, and an inbox you render yourself.
Everything else in these docs sends a person to Deliverd to decide. A decision session brings the decision to them, inside the product you are building: one approver, one request, either on a hosted page you redirect to — like a checkout page — or in a frame on your own page. You make it from your server; Deliverd serves the screen, checks who is deciding, and records the decision like any other.
Make a session
curl -X POST "$DELIVERD_URL/api/v1/approvals/$ID/sessions" \
-H "Authorization: Bearer $DELIVERD_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "approver": "sarah@acme.com", "mode": "embedded", "origin": "https://app.acme.com" }'approver- Required. The approver, by email or user id. They must already be on the request — a member named on it, or an admitted guest.
modehosted(the default): a page of its own to send them to.embedded: a frame in your page, mounted with Elements.origin- Embedded only. The site that frames it, such as
https://app.example.com. It must be on the organisation's embedding allow-list (Admin → Security → Embedding), orhttp://localhostwhile you develop. returnUrl- Hosted only. Where to send them once they have decided, with
deliverd_sessionanddeliverd_decisionadded. https, orhttp://localhostwhile you develop. expiresIn- Seconds, from 60 to 86,400. An hour when omitted, and never past the request's own deadline.
The response is 201 with the session's id, its status, and — once only — its url and clientSecret. Neither is stored, so neither can be read back. It needs approvals:write; reading it back needs approvals:read.
A request is refused with 403 not_approver when the person is not on it, 403 origin_not_allowed when the framing site is not allowed, 400 invalid_return_url for a return address that is not https, and 409 when the request is no longer pending or already has 25 open sessions.
Hosted
Redirect the approver to url. They see the request, confirm who they are, and decide. With a returnUrl they come back to you with ?deliverd_session=…&deliverd_decision=approved added — then read the session from your server before acting on it.
Embedded, with Elements
Make the session with mode: "embedded" and your page's origin, hand the clientSecret to the browser, and mount it. The frame is served from Deliverd's origin with frame-ancestors set to your origin alone, so no other site can frame it, and your page cannot read it or press its buttons.
<div id="decision"></div>
<script src="https://deliverd.dev/elements/v1.js"></script>
<script>
Deliverd.mountDecision("#decision", {
clientSecret, // from your server, which made the session
onDecided(event) {
// Tell your server, which reads GET /api/v1/decision-sessions/{id}
// before acting. The browser's word is not enough.
},
});
</script>onReady- The frame has loaded and shows the request.
onVerified- The approver confirmed who they are.
onDecided- They decided:
{ decision, approvalStatus, sessionId, approvalId }. Confirm it from your server before you act. onExpired- The session ended while it was open.
The frame sizes itself to its content; pass autoResize: false to size it yourself, and unmount() on what mountDecision returns to take it away. Messages are only accepted from that frame, on Deliverd's origin, for that session.
Read it back
GET /api/v1/decision-sessions/{id} returns the session without its secret. Act on what this says, not on what a browser told you:
| Status | Meaning |
|---|---|
open | Made, and the approver has not yet confirmed who they are. |
verified | They confirmed it with the code sent to their address. The decision bar is showing. |
complete | They decided — through the session, or anywhere else. decision says what. |
expired | It ran out, you ended it, or the request was settled without them. |
approvalStatus is the request's own status, which another approver may have settled; the webhooks and callbacks you already use fire as they always do. POST /api/v1/decision-sessions/{id}/expire ends a session early; anything decided through it stays decided.
An approver's inbox
GET /api/v1/approvals?approver=sarah@acme.com&status=pending lists the requests naming one person — a member's id or email, or an admitted guest's email — so you can show them what is waiting in your own UI, and open a session on the one they pick. Somebody nobody has asked anything gets an empty list, not an error.
In the SDKs
From @deliverd/sdk 0.15.0 on npm and deliverd 0.12.0 on PyPI:
const session = await deliverd.approvals.createSession(approval.id, {
approver: "sarah@acme.com",
mode: "embedded",
origin: "https://app.acme.com",
});
// …later, from your server:
const { status, decision } = await deliverd.decisionSessions.get(session.id);
const inbox = await deliverd.approvals.list({ approver: "sarah@acme.com", status: "pending" });session = deliverd.approvals.create_session(approval.id, approver="sarah@acme.com", mode="hosted",
return_url="https://app.acme.com/refunds/1240")
done = deliverd.decision_sessions.get(session.id)
inbox = deliverd.approvals.list(approver="sarah@acme.com", status="pending")