Guide 05
Approvals and never-twice
Why sends wait for a person, who approves, and how Owlbound guarantees a person is never contacted twice.
Last updated from API version 2026-10-06 at (live spec, refreshed hourly). openapi.json
Anything that touches a real person (LinkedIn invitations, messages and InMail, emails, WhatsApp, sequence enrollment, posts) is a send action. Writes to your own systems (HubSpot, Slack, Google Chat, HTTP calls) are external_write. Both wait for a person by default, and both pass the same guards whatever path they come from.
Safety classes
| Class | Means | Scope | Approval |
|---|---|---|---|
read | No side effect anywhere. Free. | read | Never |
write | Changes workspace data only (tags, owner, fields). | write | Never |
spend | Spends credits on data or AI. Nobody outside is touched. | enrich | Only over the key's credit threshold |
external_write | Writes to a system you own: CRM, Slack, HTTP. | act | By default |
send | Touches a real person or account. | act | By default; always for public posts |
Who approves, and where
A send action is created pending_approval. The identity's owner (or a member they delegated approvals to) decides it in the app at /dashboard/approvals, after signing in again, with the rendered preview, the quota plan and per-target unticking. The action's approval.url links straight to it.
No API call can approve
Approve and reject are not API operations. No API key, CLI command, MCP tool or agent can approve anything: a person decides in the app. GET /approvals and GET /approvals/{id}are read only, so your code can see what is waiting. Calls made by agents and MCP always wait for approval, whatever the key's mode.
- Each key has an approval mode (always, over a threshold, or none), set in the Developer tab. The strictest of the key, the operation's floor and the workspace default wins; the workspace default is always.
- Pass
requestApproval: trueon a push to ask for a person even when the key would not. - An approval not decided within 72 hours expires and the action sends nothing. Rejected or unticked targets end
canceled. - The
approval.decidedevent (feed and webhooks) tells you when it was decided.
Approved content is frozen
At approval every target's rendered content is frozen. Right before each send it is rendered again from the contact; a target whose content changed since the approval is skipped CONTENT_CHANGED, never sent with content nobody saw.
Never twice
Every send, from the API, a sequence, a play or the app, goes through one send gate before the provider is called:
- The key is checked again (a revoked key stops at its next batch:
API_KEY_REVOKED). - Suppressed, excluded, unsubscribed or bounced contacts are skipped, and so is anyone an active sequence is working on (
IN_SEQUENCE). - A person this identity already sent the same kind of touch to within 30 days is skipped (
ALREADY_SENT,ALREADY_CONTACTED), whatever the key, path or idempotency key. - The identity's daily limits, warm-up and health apply as for sequences. A send that must wait is
scheduledand runs later. - Before the provider call, a claim is written on (identity, kind of touch, person). A retry, a crash or a second action can never pass it.
A send whose outcome is unknown (a timeout, a provider error after the request left) is checked against the provider. If that cannot settle it, the target fails OUTCOME_UNKNOWN for a person to check, and it is never retried by the API. A deduplicated send is never charged.