Browse the docs

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

ClassMeansScopeApproval
readNo side effect anywhere. Free.readNever
writeChanges workspace data only (tags, owner, fields).writeNever
spendSpends credits on data or AI. Nobody outside is touched.enrichOnly over the key's credit threshold
external_writeWrites to a system you own: CRM, Slack, HTTP.actBy default
sendTouches a real person or account.actBy 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: true on 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.decided event (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:

  1. The key is checked again (a revoked key stops at its next batch: API_KEY_REVOKED).
  2. Suppressed, excluded, unsubscribed or bounced contacts are skipped, and so is anyone an active sequence is working on (IN_SEQUENCE).
  3. 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.
  4. The identity's daily limits, warm-up and health apply as for sequences. A send that must wait is scheduled and runs later.
  5. 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.