Reference · 14 operations
Actions: email, sequences, CRM and notifications
Last updated from API version 2026-10-06 at (live spec, refreshed hourly). openapi.json
Crm
actions.crm.hubspot.upsert_contact
Create or update HubSpot contacts (fill empty fields, never delete).
/api/v1/actions/crm/hubspot/upsert_contact- Scope
- act
- Credits
- See the catalog entry
- Idempotency-Key
- required
- Class
- external_write
- Approval floor
- optional
- Runs
- Async
- Targets
- contact, contacts, rows
- Max targets
- 1,000
- Dry run
- Supported
External write. Creates or updates the HubSpot contact of each
workspace contact, matched by email (a contact without one is
skipped NO_EMAIL). Sends email, first and last name, job title,
company, phone, LinkedIn URL and city (input.fields narrows it).
Conflict policy: never a blind create, the record is
looked up first; an existing record gets only its empty fields
filled (a different CRM value is kept and counted as a conflict)
unless input.onConflict is overwrite; an empty value is never
sent, so nothing is ever cleared or deleted. Several matching
records fail the target CRM_DUPLICATE_RECORDS and nothing is
written. Uses the workspace's own HubSpot connection only (none:
422 CRM_NOT_CONNECTED, also on a dry run); free (CRM writes are
0 credits). Always async and reviewed by default: the action is
created pending_approval (the workspace default; always for
onConflict: overwrite, the dashboard agent and MCP, whatever the
key's mode) with each target's write frozen and the current
HubSpot values shown next to the new ones; a person approves it in
the app, then it runs. Right before each write the write is rebuilt
from the contact: a contact changed since the approval is skipped
CONTENT_CHANGED. A record is created at most once per action,
even when the target is run again; a create refused 409 (made
meanwhile) is filled instead. Target outcomes: succeeded with
{hubspotId, action: created|updated|unchanged, written, kept, conflicts}, or CRM_AUTH (after one token refresh),
CRM_REFUSED, CRM_OUTCOME_UNKNOWN (a create that may have
happened: never retried blindly), UPSTREAM_UNAVAILABLE (failed
before any write, retried). dryRun: true reads HubSpot for the
first 10 targets in parallel (40 s budget, no write) and returns
writes.crm counts and, for the first 5, the per-field diff
(set, fill, same, keep, overwrite).
Parameters
- Idempotency-Keyheaderstring
1 to 255 printable ASCII characters, scoped to the API key (a rotated key shares its predecessor's keys) and kept 24 h. Accepted on every POST, PATCH and DELETE (ignored on GET); required from v1 keys where the operation says
x-owlbound-idempotent: required(missing:400 VALIDATION_ERROR,param: Idempotency-Key). Same key and body after completion: the stored response is replayed withIdempotent-Replayed: true. Results over 64 KB are not stored; their replay is409 IDEMPOTENCY_RESULT_UNAVAILABLEwithdetails.originalRequestId(check state with a GET). Same key while the first request runs:409 IDEMPOTENCY_IN_PROGRESSwithRetry-After. Same key, other body or operation:422 IDEMPOTENCY_KEY_REUSED. A request that ended 5xx may be retried with the same key. 4xx results are stored and replayed too: a402 INSUFFICIENT_CREDITSreplays even after a top-up, so use a new Idempotency-Key after adding credits.min length 1max length 255 - Owlbound-Dry-Runheaderboolean
true: same as body fielddryRun: true(action operations). - Owlbound-Planheaderstring
A
planIdfrom a dry run: same as the body fieldplanId(action operations). A different bodyplanIdis400.pattern ^plan_[0-9a-f]{48}$
Request body (required) · application/json
CrmHubSpotUpsertContactRequest
- targetone ofrequired0
+ Show child fields− Hide child fields
- inputobject0
+ Show child fields− Hide child fields
- onConflictstring
fillEmpty(default): write only the fields the CRM has empty.overwrite: also replace CRM values that differ. Never clears a field.fillEmptyoverwrite - fieldsarray of string
The contact fields to send (default all; the email is always sent).
emailfirstNamelastNametitlecompanyphonelinkedInUrllocation - requestApprovalboolean0
Ask a person even when the key's approval mode would not.
- dryRunboolean0
Same as the
Owlbound-Dry-Runheader. - planIdstring
Apply this dry run's plan (same as
Owlbound-Plan). Not withdryRun: true.pattern ^plan_[0-9a-f]{48}$ - modestring
auto(default) runs inline when the operation is sync, one target would run and the class is not send;syncforces inline (422 ACTION_NOT_SYNCwhen it cannot);asyncforces a run.autosyncasync - notifyActionNotify0
Events for this action.
targetEvents: truealso writes oneaction.target.completed/action.target.failedevent per target (feed and webhooks); without it only action-level events are written, to keep the volume down.webhookis accepted and has no effect (webhook endpoints subscribe by event type).
Responses
200The dry-run plan (with the per-field CRM diff of the first targets).Show schema
ActionRunResponse
- dataone ofrequired0
+ Show child fields− Hide child fields
One of
with action
- 0
One action run. Read it again with
GET /api/v1/actions/{id}. - resultanyrequired0
The target's result, or null when it was skipped or failed.
with plan
- 0
What a real call would do, from every check up to the first side effect. Nothing is reserved, sent, written or charged. The plan is stored for 15 minutes: send its
planIdwith the real call to run exactly what was reviewed (409 PLAN_STALEwith a new plan if anything bound changed or it expired).
- 0
Metadata on every success response.
202The action was created: pending_approval until a person approves it in the app, then queued and run.Show schema
pending_approval until a person approves it in the app, then queued and run.ActionResponse
- dataobjectrequired0
+ Show child fields− Hide child fields
- 0
One action run. Read it again with
GET /api/v1/actions/{id}.
- 0
Metadata on every success response.
Errors
400 The request is invalid (VALIDATION_ERROR, CURSOR_MISMATCH, UNSUPPORTED_FILTER) · 401 Missing, invalid, expired or revoked key · 403 Missing scope, suspended workspace or unavailable identity · 404 Unknown resource or route. A resource in another workspace is also 404 · 409 IDEMPOTENCY_IN_PROGRESS (the first request with this key is still
running; `Retry-After` is the lease left, at most 60 s) or
IDEMPOTENCY_RESULT_UNAVAILABLE (the original result was too large to
store; `details.originalRequestId`).
· 422 IDEMPOTENCY_KEY_REUSED (the key was used with another body or operation), or an action that cannot run · 429 Rate limit reached · 500 Unexpected error · 503 The backend is unreachable; retry after `Retry-After`. Every error is {error: {type, code, message, requestId, docUrl}}; see error codes.
curl -X POST "https://app.owlbound.ai/api/v1/actions/crm/hubspot/upsert_contact" \
-H "Authorization: Bearer $OWLBOUND_API_KEY" \
-H "Owlbound-Version: 2026-10-06" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"target": {
"kind": "contact",
"contactId": "<contactId>"
},
"dryRun": true
}'The example is a dry run: it returns a plan and runs, writes and charges nothing. See dry runs and plans.
actions.crm.hubspot.upsert_company
Create or update the HubSpot companies of contacts (fill empty fields, never delete).
/api/v1/actions/crm/hubspot/upsert_company- Scope
- act
- Credits
- See the catalog entry
- Idempotency-Key
- required
- Class
- external_write
- Approval floor
- optional
- Runs
- Async
- Targets
- contact, contacts, rows
- Max targets
- 1,000
- Dry run
- Supported
External write. Creates or updates the HubSpot company of each
workspace contact's company, matched by its domain (a contact
without one is skipped NO_DOMAIN). Sends the domain and the
company name. One write per domain: other contacts of the same
company are skipped DUPLICATE_DOMAIN and listed in the written
target's contactIds.
Conflict policy: never a blind create, the record is
looked up first; an existing record gets only its empty fields
filled (a different CRM value is kept and counted as a conflict)
unless input.onConflict is overwrite; an empty value is never
sent, so nothing is ever cleared or deleted. Several matching
records fail the target CRM_DUPLICATE_RECORDS and nothing is
written. Uses the workspace's own HubSpot connection only (none:
422 CRM_NOT_CONNECTED, also on a dry run); free (CRM writes are
0 credits). Always async and reviewed by default: the action is
created pending_approval (the workspace default; always for
onConflict: overwrite, the dashboard agent and MCP, whatever the
key's mode) with each target's write frozen and the current
HubSpot values shown next to the new ones; a person approves it in
the app, then it runs. Right before each write the write is rebuilt
from the contact: a contact changed since the approval is skipped
CONTENT_CHANGED. A record is created at most once per action,
even when the target is run again; a create refused 409 (made
meanwhile) is filled instead. Target outcomes: succeeded with
{hubspotId, action: created|updated|unchanged, written, kept, conflicts}, or CRM_AUTH (after one token refresh),
CRM_REFUSED, CRM_OUTCOME_UNKNOWN (a create that may have
happened: never retried blindly), UPSTREAM_UNAVAILABLE (failed
before any write, retried). dryRun: true reads HubSpot for the
first 10 targets in parallel (40 s budget, no write) and returns
writes.crm counts and, for the first 5, the per-field diff
(set, fill, same, keep, overwrite).
Parameters
- Idempotency-Keyheaderstring
1 to 255 printable ASCII characters, scoped to the API key (a rotated key shares its predecessor's keys) and kept 24 h. Accepted on every POST, PATCH and DELETE (ignored on GET); required from v1 keys where the operation says
x-owlbound-idempotent: required(missing:400 VALIDATION_ERROR,param: Idempotency-Key). Same key and body after completion: the stored response is replayed withIdempotent-Replayed: true. Results over 64 KB are not stored; their replay is409 IDEMPOTENCY_RESULT_UNAVAILABLEwithdetails.originalRequestId(check state with a GET). Same key while the first request runs:409 IDEMPOTENCY_IN_PROGRESSwithRetry-After. Same key, other body or operation:422 IDEMPOTENCY_KEY_REUSED. A request that ended 5xx may be retried with the same key. 4xx results are stored and replayed too: a402 INSUFFICIENT_CREDITSreplays even after a top-up, so use a new Idempotency-Key after adding credits.min length 1max length 255 - Owlbound-Dry-Runheaderboolean
true: same as body fielddryRun: true(action operations). - Owlbound-Planheaderstring
A
planIdfrom a dry run: same as the body fieldplanId(action operations). A different bodyplanIdis400.pattern ^plan_[0-9a-f]{48}$
Request body (required) · application/json
CrmHubSpotUpsertCompanyRequest
- targetone ofrequired0
+ Show child fields− Hide child fields
- inputobject0
+ Show child fields− Hide child fields
- onConflictstring
fillEmpty(default): write only the fields the CRM has empty.overwrite: also replace CRM values that differ. Never clears a field.fillEmptyoverwrite - requestApprovalboolean0
Ask a person even when the key's approval mode would not.
- dryRunboolean0
Same as the
Owlbound-Dry-Runheader. - planIdstring
Apply this dry run's plan (same as
Owlbound-Plan). Not withdryRun: true.pattern ^plan_[0-9a-f]{48}$ - modestring
auto(default) runs inline when the operation is sync, one target would run and the class is not send;syncforces inline (422 ACTION_NOT_SYNCwhen it cannot);asyncforces a run.autosyncasync - notifyActionNotify0
Events for this action.
targetEvents: truealso writes oneaction.target.completed/action.target.failedevent per target (feed and webhooks); without it only action-level events are written, to keep the volume down.webhookis accepted and has no effect (webhook endpoints subscribe by event type).
Responses
200The dry-run plan (with the per-field CRM diff of the first targets).Show schema
ActionRunResponse
- dataone ofrequired0
+ Show child fields− Hide child fields
One of
with action
- 0
One action run. Read it again with
GET /api/v1/actions/{id}. - resultanyrequired0
The target's result, or null when it was skipped or failed.
with plan
- 0
What a real call would do, from every check up to the first side effect. Nothing is reserved, sent, written or charged. The plan is stored for 15 minutes: send its
planIdwith the real call to run exactly what was reviewed (409 PLAN_STALEwith a new plan if anything bound changed or it expired).
- 0
Metadata on every success response.
202The action was created: pending_approval until a person approves it in the app, then queued and run.Show schema
pending_approval until a person approves it in the app, then queued and run.ActionResponse
- dataobjectrequired0
+ Show child fields− Hide child fields
- 0
One action run. Read it again with
GET /api/v1/actions/{id}.
- 0
Metadata on every success response.
Errors
400 The request is invalid (VALIDATION_ERROR, CURSOR_MISMATCH, UNSUPPORTED_FILTER) · 401 Missing, invalid, expired or revoked key · 403 Missing scope, suspended workspace or unavailable identity · 404 Unknown resource or route. A resource in another workspace is also 404 · 409 IDEMPOTENCY_IN_PROGRESS (the first request with this key is still
running; `Retry-After` is the lease left, at most 60 s) or
IDEMPOTENCY_RESULT_UNAVAILABLE (the original result was too large to
store; `details.originalRequestId`).
· 422 IDEMPOTENCY_KEY_REUSED (the key was used with another body or operation), or an action that cannot run · 429 Rate limit reached · 500 Unexpected error · 503 The backend is unreachable; retry after `Retry-After`. Every error is {error: {type, code, message, requestId, docUrl}}; see error codes.
curl -X POST "https://app.owlbound.ai/api/v1/actions/crm/hubspot/upsert_company" \
-H "Authorization: Bearer $OWLBOUND_API_KEY" \
-H "Owlbound-Version: 2026-10-06" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"target": {
"kind": "contact",
"contactId": "<contactId>"
},
"dryRun": true
}'The example is a dry run: it returns a plan and runs, writes and charges nothing. See dry runs and plans.
actions.crm.hubspot.enroll_sequence
Enroll contacts in a HubSpot sequence.
/api/v1/actions/crm/hubspot/enroll_sequence- Scope
- act
- Credits
- 6 credits per target
- Idempotency-Key
- required
- Class
- send
- Approval floor
- optional
- Runs
- Async
- Targets
- contact, contacts
- Max targets
- 1,000
- Dry run
- Supported
Send. Enrolls each contact in a HubSpot sequence (HubSpot then
emails them from the sender's mailbox). The contact is found in
HubSpot by its email (no HubSpot contact: skipped
CRM_CONTACT_NOT_FOUND; run crm.hubspot.upsert_contact first). A
contact HubSpot already has in a sequence (enrolled by a play, in
HubSpot or by another tool) is skipped ALREADY_ENROLLED, read
before anything is claimed and again from HubSpot's answer.
Uses the workspace's own HubSpot connection only (none:
422 CRM_NOT_CONNECTED). Class send, async, identity required
(the key's bound identity, else identityId): a person is sent this
at most once per identity within 30 days, whatever the path, retry
or content. Reviewed by default: the action is created
pending_approval (the workspace default; the dashboard agent and
MCP always) with each target's content frozen; a person approves it
in the app, then it runs. Each target passes the send gate first
(suppressed, excluded, unsubscribed or bounced contacts, an active
sequence, an earlier send, the principal's daily sends; a contact
edited since the approval is CONTENT_CHANGED). An answer that may
mean sent (timeout, 5xx) ends OUTCOME_UNKNOWN and is never sent
again. 6 credits (connector.outreach.send) per confirmed send;
anything else is free.
Parameters
- Idempotency-Keyheaderstring
1 to 255 printable ASCII characters, scoped to the API key (a rotated key shares its predecessor's keys) and kept 24 h. Accepted on every POST, PATCH and DELETE (ignored on GET); required from v1 keys where the operation says
x-owlbound-idempotent: required(missing:400 VALIDATION_ERROR,param: Idempotency-Key). Same key and body after completion: the stored response is replayed withIdempotent-Replayed: true. Results over 64 KB are not stored; their replay is409 IDEMPOTENCY_RESULT_UNAVAILABLEwithdetails.originalRequestId(check state with a GET). Same key while the first request runs:409 IDEMPOTENCY_IN_PROGRESSwithRetry-After. Same key, other body or operation:422 IDEMPOTENCY_KEY_REUSED. A request that ended 5xx may be retried with the same key. 4xx results are stored and replayed too: a402 INSUFFICIENT_CREDITSreplays even after a top-up, so use a new Idempotency-Key after adding credits.min length 1max length 255 - Owlbound-Dry-Runheaderboolean
true: same as body fielddryRun: true(action operations). - Owlbound-Planheaderstring
A
planIdfrom a dry run: same as the body fieldplanId(action operations). A different bodyplanIdis400.pattern ^plan_[0-9a-f]{48}$
Request body (required) · application/json
CrmHubSpotEnrollSequenceRequest
- targetone ofrequired0
+ Show child fields− Hide child fields
- inputobjectrequired0
+ Show child fields− Hide child fields
- sequenceIdstringrequiredpattern ^[A-Za-z0-9_-]{1,64}$
- hubspotUserIdstringrequired
The HubSpot user who owns the sequence and the sending mailbox.
pattern ^[A-Za-z0-9_-]{1,64}$ - senderEmailstringrequiredmin length 1
- senderAliasAddressstring0
- requestApprovalboolean0
Ask a person even when the key's approval mode would not.
- identityIdstring | null0
The identity the send is made for (its claim's subject). A bound key uses its identity (another is
403 IDENTITY_NOT_ALLOWED); an unbound key must name one (422 IDENTITY_REQUIRED). - dryRunboolean0
Same as the
Owlbound-Dry-Runheader. - planIdstring
Apply this dry run's plan (same as
Owlbound-Plan). Not withdryRun: true.pattern ^plan_[0-9a-f]{48}$ - modestring
auto(default) runs inline when the operation is sync, one target would run and the class is not send;syncforces inline (422 ACTION_NOT_SYNCwhen it cannot);asyncforces a run.autosyncasync - notifyActionNotify0
Events for this action.
targetEvents: truealso writes oneaction.target.completed/action.target.failedevent per target (feed and webhooks); without it only action-level events are written, to keep the volume down.webhookis accepted and has no effect (webhook endpoints subscribe by event type).
Responses
200The dry-run plan (the rendered content of the first targets).Show schema
ActionRunResponse
- dataone ofrequired0
+ Show child fields− Hide child fields
One of
with action
- 0
One action run. Read it again with
GET /api/v1/actions/{id}. - resultanyrequired0
The target's result, or null when it was skipped or failed.
with plan
- 0
What a real call would do, from every check up to the first side effect. Nothing is reserved, sent, written or charged. The plan is stored for 15 minutes: send its
planIdwith the real call to run exactly what was reviewed (409 PLAN_STALEwith a new plan if anything bound changed or it expired).
- 0
Metadata on every success response.
202The action was created: pending_approval until a person approves it in the app (reviewed by default), then queued and run.Show schema
pending_approval until a person approves it in the app (reviewed by default), then queued and run.ActionResponse
- dataobjectrequired0
+ Show child fields− Hide child fields
- 0
One action run. Read it again with
GET /api/v1/actions/{id}.
- 0
Metadata on every success response.
Errors
400 The request is invalid (VALIDATION_ERROR, CURSOR_MISMATCH, UNSUPPORTED_FILTER) · 401 Missing, invalid, expired or revoked key · 402 Not enough credits (INSUFFICIENT_CREDITS). With an Idempotency-Key this
result is stored and replayed: after adding credits, retry with a new
Idempotency-Key.
· 403 Missing scope, suspended workspace or unavailable identity · 404 Unknown resource or route. A resource in another workspace is also 404 · 409 IDEMPOTENCY_IN_PROGRESS (the first request with this key is still
running; `Retry-After` is the lease left, at most 60 s) or
IDEMPOTENCY_RESULT_UNAVAILABLE (the original result was too large to
store; `details.originalRequestId`).
· 422 IDEMPOTENCY_KEY_REUSED (the key was used with another body or operation), or an action that cannot run · 429 Rate limit reached · 500 Unexpected error · 503 The backend is unreachable; retry after `Retry-After`. Every error is {error: {type, code, message, requestId, docUrl}}; see error codes.
curl -X POST "https://app.owlbound.ai/api/v1/actions/crm/hubspot/enroll_sequence" \
-H "Authorization: Bearer $OWLBOUND_API_KEY" \
-H "Owlbound-Version: 2026-10-06" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"target": {
"kind": "contact",
"contactId": "<contactId>"
},
"input": {
"sequenceId": "<sequenceId>",
"hubspotUserId": "<hubspotUserId>",
"senderEmail": "<senderEmail>"
},
"dryRun": true
}'The example is a dry run: it returns a plan and runs, writes and charges nothing. See dry runs and plans.
actions.crm.emailbison.send_email
Send an email to each contact through EmailBison.
/api/v1/actions/crm/emailbison/send_email- Scope
- act
- Credits
- 6 credits per target
- Idempotency-Key
- required
- Class
- send
- Approval floor
- optional
- Runs
- Async
- Targets
- contact, contacts
- Max targets
- 1,000
- Dry run
- Supported
Send. Emails each contact through a one-lead EmailBison campaign
(the subject and body are templates filled from the stored contact:
{{firstName}}, {{company}}, ...; a missing field is skipped
MISSING_TEMPLATE_VARIABLES). Only the campaign start sends: a
failure while building the campaign sends nothing (NOT_SENT).
Uses the workspace's own EmailBison connection only (none:
422 CRM_NOT_CONNECTED). Class send, async, identity required
(the key's bound identity, else identityId): a person is sent this
at most once per identity within 30 days, whatever the path, retry
or content. Reviewed by default: the action is created
pending_approval (the workspace default; the dashboard agent and
MCP always) with each target's content frozen; a person approves it
in the app, then it runs. Each target passes the send gate first
(suppressed, excluded, unsubscribed or bounced contacts, an active
sequence, an earlier send, the principal's daily sends; a contact
edited since the approval is CONTENT_CHANGED). An answer that may
mean sent (timeout, 5xx) ends OUTCOME_UNKNOWN and is never sent
again. 6 credits (connector.outreach.send) per confirmed send;
anything else is free.
Parameters
- Idempotency-Keyheaderstring
1 to 255 printable ASCII characters, scoped to the API key (a rotated key shares its predecessor's keys) and kept 24 h. Accepted on every POST, PATCH and DELETE (ignored on GET); required from v1 keys where the operation says
x-owlbound-idempotent: required(missing:400 VALIDATION_ERROR,param: Idempotency-Key). Same key and body after completion: the stored response is replayed withIdempotent-Replayed: true. Results over 64 KB are not stored; their replay is409 IDEMPOTENCY_RESULT_UNAVAILABLEwithdetails.originalRequestId(check state with a GET). Same key while the first request runs:409 IDEMPOTENCY_IN_PROGRESSwithRetry-After. Same key, other body or operation:422 IDEMPOTENCY_KEY_REUSED. A request that ended 5xx may be retried with the same key. 4xx results are stored and replayed too: a402 INSUFFICIENT_CREDITSreplays even after a top-up, so use a new Idempotency-Key after adding credits.min length 1max length 255 - Owlbound-Dry-Runheaderboolean
true: same as body fielddryRun: true(action operations). - Owlbound-Planheaderstring
A
planIdfrom a dry run: same as the body fieldplanId(action operations). A different bodyplanIdis400.pattern ^plan_[0-9a-f]{48}$
Request body (required) · application/json
CrmEmailBisonSendEmailRequest
- targetone ofrequired0
+ Show child fields− Hide child fields
- inputobjectrequired0
+ Show child fields− Hide child fields
- subjectstringrequired
Template; at most 200 characters once filled.
min length 1max length 400 - bodystringrequired
Template.
min length 1max length 50000 - campaignNamePrefixstringmax length 100
- requestApprovalboolean0
Ask a person even when the key's approval mode would not.
- identityIdstring | null0
The identity the send is made for (its claim's subject). A bound key uses its identity (another is
403 IDENTITY_NOT_ALLOWED); an unbound key must name one (422 IDENTITY_REQUIRED). - dryRunboolean0
Same as the
Owlbound-Dry-Runheader. - planIdstring
Apply this dry run's plan (same as
Owlbound-Plan). Not withdryRun: true.pattern ^plan_[0-9a-f]{48}$ - modestring
auto(default) runs inline when the operation is sync, one target would run and the class is not send;syncforces inline (422 ACTION_NOT_SYNCwhen it cannot);asyncforces a run.autosyncasync - notifyActionNotify0
Events for this action.
targetEvents: truealso writes oneaction.target.completed/action.target.failedevent per target (feed and webhooks); without it only action-level events are written, to keep the volume down.webhookis accepted and has no effect (webhook endpoints subscribe by event type).
Responses
200The dry-run plan (the rendered content of the first targets).Show schema
ActionRunResponse
- dataone ofrequired0
+ Show child fields− Hide child fields
One of
with action
- 0
One action run. Read it again with
GET /api/v1/actions/{id}. - resultanyrequired0
The target's result, or null when it was skipped or failed.
with plan
- 0
What a real call would do, from every check up to the first side effect. Nothing is reserved, sent, written or charged. The plan is stored for 15 minutes: send its
planIdwith the real call to run exactly what was reviewed (409 PLAN_STALEwith a new plan if anything bound changed or it expired).
- 0
Metadata on every success response.
202The action was created: pending_approval until a person approves it in the app (reviewed by default), then queued and run.Show schema
pending_approval until a person approves it in the app (reviewed by default), then queued and run.ActionResponse
- dataobjectrequired0
+ Show child fields− Hide child fields
- 0
One action run. Read it again with
GET /api/v1/actions/{id}.
- 0
Metadata on every success response.
Errors
400 The request is invalid (VALIDATION_ERROR, CURSOR_MISMATCH, UNSUPPORTED_FILTER) · 401 Missing, invalid, expired or revoked key · 402 Not enough credits (INSUFFICIENT_CREDITS). With an Idempotency-Key this
result is stored and replayed: after adding credits, retry with a new
Idempotency-Key.
· 403 Missing scope, suspended workspace or unavailable identity · 404 Unknown resource or route. A resource in another workspace is also 404 · 409 IDEMPOTENCY_IN_PROGRESS (the first request with this key is still
running; `Retry-After` is the lease left, at most 60 s) or
IDEMPOTENCY_RESULT_UNAVAILABLE (the original result was too large to
store; `details.originalRequestId`).
· 422 IDEMPOTENCY_KEY_REUSED (the key was used with another body or operation), or an action that cannot run · 429 Rate limit reached · 500 Unexpected error · 503 The backend is unreachable; retry after `Retry-After`. Every error is {error: {type, code, message, requestId, docUrl}}; see error codes.
curl -X POST "https://app.owlbound.ai/api/v1/actions/crm/emailbison/send_email" \
-H "Authorization: Bearer $OWLBOUND_API_KEY" \
-H "Owlbound-Version: 2026-10-06" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"target": {
"kind": "contact",
"contactId": "<contactId>"
},
"input": {
"subject": "<subject>",
"body": "<body>"
},
"dryRun": true
}'The example is a dry run: it returns a plan and runs, writes and charges nothing. See dry runs and plans.
Http
Call your own HTTP endpoint once per contact.
/api/v1/actions/http/call- Scope
- act
- Credits
- 8 credits per target
- Idempotency-Key
- required
- Class
- external_write
- Approval floor
- optional
- Runs
- Async
- Targets
- contact, contacts
- Max targets
- 1,000
- Dry run
- Supported
External write. Sends one HTTP request per workspace contact to the
caller's own endpoint. method is GET, POST, PUT or PATCH (no
DELETE). The URL must be https without credentials in it; every
address it resolves to, and every redirect hop, is checked when the
request is made (private, loopback, link-local and metadata
addresses are refused, also after a redirect or a DNS change), and
the target fails URL_BLOCKED or REDIRECT_BLOCKED.
{{contact.field}} in the URL, header values and body strings is
filled from the stored contact. Headers: only Content-Type, Accept,
User-Agent and custom X-* (not forwarding or credential-like
ones; anything else is 400). Credentials come only from an
http_auth connector (auth.connectorId: a credential, the exact
hosts it may go to, its header and scheme); any other connector is
422 CONNECTOR_NOT_ALLOWED, a URL off its hosts (or with a
template before the path) 422 HOST_NOT_ALLOWED. The credential is
added when the request is made, dropped on a redirect to another
origin, and never stored, shown in the plan or the approval, or
returned (a response echoing it, raw, URL-encoded or base64, is
redacted). Caps: request body 64 KB (BODY_TOO_LARGE), 20
headers, 10 s per request (HTTP_TIMEOUT), response read up to
256 KB with the first 4,000 characters in the result. 8 credits
(api.webhook.call) per request answered 2xx; any other answer
fails the target HTTP_<status> and is free. Always async and
reviewed by default: the action is created pending_approval (the
workspace default; the dashboard agent and MCP always) with every
target's whole request frozen; a person approves it in the app,
then it runs. Right before each request it is rebuilt from the
contact: a contact changed since the approval is skipped
CONTENT_CHANGED. Each target's request is sent at most once; a
target run again after its request may have left fails
HTTP_OUTCOME_UNKNOWN, never sends twice. dryRun: true sends
nothing and returns the first 5 requests (no secret) with their URL
checks.
Parameters
- Idempotency-Keyheaderstring
1 to 255 printable ASCII characters, scoped to the API key (a rotated key shares its predecessor's keys) and kept 24 h. Accepted on every POST, PATCH and DELETE (ignored on GET); required from v1 keys where the operation says
x-owlbound-idempotent: required(missing:400 VALIDATION_ERROR,param: Idempotency-Key). Same key and body after completion: the stored response is replayed withIdempotent-Replayed: true. Results over 64 KB are not stored; their replay is409 IDEMPOTENCY_RESULT_UNAVAILABLEwithdetails.originalRequestId(check state with a GET). Same key while the first request runs:409 IDEMPOTENCY_IN_PROGRESSwithRetry-After. Same key, other body or operation:422 IDEMPOTENCY_KEY_REUSED. A request that ended 5xx may be retried with the same key. 4xx results are stored and replayed too: a402 INSUFFICIENT_CREDITSreplays even after a top-up, so use a new Idempotency-Key after adding credits.min length 1max length 255 - Owlbound-Dry-Runheaderboolean
true: same as body fielddryRun: true(action operations). - Owlbound-Planheaderstring
A
planIdfrom a dry run: same as the body fieldplanId(action operations). A different bodyplanIdis400.pattern ^plan_[0-9a-f]{48}$
Request body (required) · application/json
HttpCallRequest
- targetone ofrequired0
+ Show child fields− Hide child fields
- inputobjectrequired0
+ Show child fields− Hide child fields
- methodstringrequiredGETPOSTPUTPATCH
- urlstringrequired
https only; may hold
{{contact.field}}.max length 2000 - headersobject0
Only Content-Type, Accept, User-Agent and custom X-* headers (not X-Forwarded-*, X-Real-IP or credential-like X-Api-Key, X-Auth-Token); credentials come from
auth. - bodyany0
A string (sent as text/plain unless Content-Type is set) or a JSON object or array (sent as JSON). Not with GET. At most 64 KB once filled.
- authobject0
+ Show child fields− Hide child fields
- connectorIdstringrequired0
An
http_authconnector of this workspace (a credential with its allowed hosts, header and scheme). Any other connector type is422 CONNECTOR_NOT_ALLOWED; a URL not on its hosts is422 HOST_NOT_ALLOWED.
- requestApprovalboolean0
Ask a person even when the key's approval mode would not.
- dryRunboolean0
Same as the
Owlbound-Dry-Runheader. - planIdstring
Apply this dry run's plan (same as
Owlbound-Plan). Not withdryRun: true.pattern ^plan_[0-9a-f]{48}$ - modestring
auto(default) runs inline when the operation is sync, one target would run and the class is not send;syncforces inline (422 ACTION_NOT_SYNCwhen it cannot);asyncforces a run.autosyncasync - notifyActionNotify0
Events for this action.
targetEvents: truealso writes oneaction.target.completed/action.target.failedevent per target (feed and webhooks); without it only action-level events are written, to keep the volume down.webhookis accepted and has no effect (webhook endpoints subscribe by event type).
Responses
200The dry-run plan (the first requests, no secret).Show schema
ActionRunResponse
- dataone ofrequired0
+ Show child fields− Hide child fields
One of
with action
- 0
One action run. Read it again with
GET /api/v1/actions/{id}. - resultanyrequired0
The target's result, or null when it was skipped or failed.
with plan
- 0
What a real call would do, from every check up to the first side effect. Nothing is reserved, sent, written or charged. The plan is stored for 15 minutes: send its
planIdwith the real call to run exactly what was reviewed (409 PLAN_STALEwith a new plan if anything bound changed or it expired).
- 0
Metadata on every success response.
202The action was created: pending_approval until a person approves it in the app (reviewed by default), then queued and run.Show schema
pending_approval until a person approves it in the app (reviewed by default), then queued and run.ActionResponse
- dataobjectrequired0
+ Show child fields− Hide child fields
- 0
One action run. Read it again with
GET /api/v1/actions/{id}.
- 0
Metadata on every success response.
Errors
400 The request is invalid (VALIDATION_ERROR, CURSOR_MISMATCH, UNSUPPORTED_FILTER) · 401 Missing, invalid, expired or revoked key · 402 Not enough credits (INSUFFICIENT_CREDITS). With an Idempotency-Key this
result is stored and replayed: after adding credits, retry with a new
Idempotency-Key.
· 403 Missing scope, suspended workspace or unavailable identity · 404 Unknown resource or route. A resource in another workspace is also 404 · 409 IDEMPOTENCY_IN_PROGRESS (the first request with this key is still
running; `Retry-After` is the lease left, at most 60 s) or
IDEMPOTENCY_RESULT_UNAVAILABLE (the original result was too large to
store; `details.originalRequestId`).
· 422 IDEMPOTENCY_KEY_REUSED (the key was used with another body or operation), or an action that cannot run · 429 Rate limit reached · 500 Unexpected error · 503 The backend is unreachable; retry after `Retry-After`. Every error is {error: {type, code, message, requestId, docUrl}}; see error codes.
curl -X POST "https://app.owlbound.ai/api/v1/actions/http/call" \
-H "Authorization: Bearer $OWLBOUND_API_KEY" \
-H "Owlbound-Version: 2026-10-06" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"target": {
"kind": "contact",
"contactId": "<contactId>"
},
"input": {
"method": "GET",
"url": "https://example.com"
},
"dryRun": true
}'The example is a dry run: it returns a plan and runs, writes and charges nothing. See dry runs and plans.
Send an email to each contact from the identity's mailbox.
/api/v1/actions/email/send- Scope
- act
- Credits
- 2 credits per target
- Idempotency-Key
- required
- Class
- send
- Approval floor
- optional
- Runs
- Async
- Targets
- contact, contacts
- Max targets
- 1,000
- Dry run
- Supported
Send. One email per contact, from the identity's own connected
mailbox (the connected Gmail or Outlook account sequences send
from; exactly one active mailbox, else 403 IDENTITY_UNAVAILABLE),
to each target contact's stored address (none: skipped NO_EMAIL).
subject (at most 200 characters once filled) and body (at most
50,000) are templates: {{firstName}}, {{lastName}},
{{company}}, {{title}}, ... are filled from the stored
contact; a target missing a field is skipped
MISSING_TEMPLATE_VARIABLES. isHtml: true sends the body as HTML.
Always async and reviewed by default: the action is created
pending_approval (the workspace default; the dashboard agent and
MCP always) with every target's rendered content frozen; a person
approves it in the app, then it runs. Right before each send the
content is rendered again from the contact: a contact changed since
the approval is skipped CONTENT_CHANGED. Every send passes the
send gate: the key is re-checked (API_KEY_REVOKED); suppressed,
excluded or unsubscribed contacts are skipped, and so is a contact
an active sequence is working on (IN_SEQUENCE).
A person whose email bounced is skipped BOUNCED. Never twice on
this channel: a person this identity already emailed within 30 days
is skipped (ALREADY_SENT, ALREADY_CONTACTED), whatever sent it:
Owlbound's send log on the identity's mailboxes (sequences, plays,
the inbox and the API write it before and after each request),
sequence emails sent from the workspace sender, the person's synced
email conversations (outbound messages, including ones sent from
the mailbox itself), EmailBison emails and succeeded API actions;
checked again right before the request, once the mailbox is free.
The identity's daily email limit and health apply as for sequences;
a send that must wait is scheduled and runs later. A send whose
outcome is unknown (timeout, provider 5xx) fails OUTCOME_UNKNOWN
for a person to check; it is never retried.
2 credits (sequence.email.send) per email sent.
Parameters
- Idempotency-Keyheaderstring
1 to 255 printable ASCII characters, scoped to the API key (a rotated key shares its predecessor's keys) and kept 24 h. Accepted on every POST, PATCH and DELETE (ignored on GET); required from v1 keys where the operation says
x-owlbound-idempotent: required(missing:400 VALIDATION_ERROR,param: Idempotency-Key). Same key and body after completion: the stored response is replayed withIdempotent-Replayed: true. Results over 64 KB are not stored; their replay is409 IDEMPOTENCY_RESULT_UNAVAILABLEwithdetails.originalRequestId(check state with a GET). Same key while the first request runs:409 IDEMPOTENCY_IN_PROGRESSwithRetry-After. Same key, other body or operation:422 IDEMPOTENCY_KEY_REUSED. A request that ended 5xx may be retried with the same key. 4xx results are stored and replayed too: a402 INSUFFICIENT_CREDITSreplays even after a top-up, so use a new Idempotency-Key after adding credits.min length 1max length 255 - Owlbound-Dry-Runheaderboolean
true: same as body fielddryRun: true(action operations). - Owlbound-Planheaderstring
A
planIdfrom a dry run: same as the body fieldplanId(action operations). A different bodyplanIdis400.pattern ^plan_[0-9a-f]{48}$
Request body (required) · application/json
EmailSendRequest
- targetone ofrequired0
+ Show child fields− Hide child fields
- inputobjectrequired0
+ Show child fields− Hide child fields
- subjectstringrequired
Template; at most 200 characters once filled.
min length 1max length 400 - bodystringrequired
Template; at most 50,000 characters once filled.
min length 1max length 100000 - isHtmlboolean0
Send the body as HTML (default plain text).
- requestApprovalboolean0
Ask a person even when the key's approval mode would not.
- identityIdstring | null0
The identity that sends. A bound key uses its identity (another is
403 IDENTITY_NOT_ALLOWED); an unbound key must name one (422 IDENTITY_REQUIRED); a session defaults to the member's own. - dryRunboolean0
Same as the
Owlbound-Dry-Runheader. - planIdstring
Apply this dry run's plan (same as
Owlbound-Plan). Not withdryRun: true.pattern ^plan_[0-9a-f]{48}$ - modestring
auto(default) runs inline when the operation is sync, one target would run and the class is not send;syncforces inline (422 ACTION_NOT_SYNCwhen it cannot);asyncforces a run.autosyncasync - notifyActionNotify0
Events for this action.
targetEvents: truealso writes oneaction.target.completed/action.target.failedevent per target (feed and webhooks); without it only action-level events are written, to keep the volume down.webhookis accepted and has no effect (webhook endpoints subscribe by event type).
Responses
200The dry-run plan (the rendered content of the first targets).Show schema
ActionRunResponse
- dataone ofrequired0
+ Show child fields− Hide child fields
One of
with action
- 0
One action run. Read it again with
GET /api/v1/actions/{id}. - resultanyrequired0
The target's result, or null when it was skipped or failed.
with plan
- 0
What a real call would do, from every check up to the first side effect. Nothing is reserved, sent, written or charged. The plan is stored for 15 minutes: send its
planIdwith the real call to run exactly what was reviewed (409 PLAN_STALEwith a new plan if anything bound changed or it expired).
- 0
Metadata on every success response.
202The action was created: pending_approval until a person approves it in the app (reviewed by default), then queued and run.Show schema
pending_approval until a person approves it in the app (reviewed by default), then queued and run.ActionResponse
- dataobjectrequired0
+ Show child fields− Hide child fields
- 0
One action run. Read it again with
GET /api/v1/actions/{id}.
- 0
Metadata on every success response.
Errors
400 The request is invalid (VALIDATION_ERROR, CURSOR_MISMATCH, UNSUPPORTED_FILTER) · 401 Missing, invalid, expired or revoked key · 402 Not enough credits (INSUFFICIENT_CREDITS). With an Idempotency-Key this
result is stored and replayed: after adding credits, retry with a new
Idempotency-Key.
· 403 Missing scope, suspended workspace or unavailable identity · 404 Unknown resource or route. A resource in another workspace is also 404 · 409 IDEMPOTENCY_IN_PROGRESS (the first request with this key is still
running; `Retry-After` is the lease left, at most 60 s) or
IDEMPOTENCY_RESULT_UNAVAILABLE (the original result was too large to
store; `details.originalRequestId`).
· 422 IDEMPOTENCY_KEY_REUSED (the key was used with another body or operation), or an action that cannot run · 429 Rate limit reached · 500 Unexpected error · 503 The backend is unreachable; retry after `Retry-After`. Every error is {error: {type, code, message, requestId, docUrl}}; see error codes.
curl -X POST "https://app.owlbound.ai/api/v1/actions/email/send" \
-H "Authorization: Bearer $OWLBOUND_API_KEY" \
-H "Owlbound-Version: 2026-10-06" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"target": {
"kind": "contact",
"contactId": "<contactId>"
},
"input": {
"subject": "<subject>",
"body": "<body>"
},
"dryRun": true
}'The example is a dry run: it returns a plan and runs, writes and charges nothing. See dry runs and plans.
Send a WhatsApp message to each contact.
/api/v1/actions/whatsapp/send- Scope
- act
- Credits
- 6 credits per target
- Idempotency-Key
- required
- Class
- send
- Approval floor
- optional
- Runs
- Async
- Targets
- contact, contacts
- Max targets
- 1,000
- Dry run
- Supported
Send. One WhatsApp message per contact (at most 4,096 characters
once filled), from the identity's own connected WhatsApp account
(none: 403 IDENTITY_UNAVAILABLE), to each target
contact's stored phone number (none or unusable: skipped
NO_PHONE). {{firstName}}, {{company}}, ... are filled from
the stored contact; a target missing a field is skipped
MISSING_TEMPLATE_VARIABLES. No media in this version.
Always async and reviewed by default: the action is created
pending_approval (the workspace default; the dashboard agent and
MCP always) with every target's rendered content frozen; a person
approves it in the app, then it runs. Right before each send the
content is rendered again from the contact: a contact changed since
the approval is skipped CONTENT_CHANGED. Every send passes the
send gate: the key is re-checked (API_KEY_REVOKED); suppressed,
excluded or unsubscribed contacts are skipped, and so is a contact
an active sequence is working on (IN_SEQUENCE).
Never twice on this channel: a person this identity already sent a
WhatsApp message within 30 days is skipped (ALREADY_SENT,
ALREADY_CONTACTED): Owlbound's send log on the identity's
WhatsApp account, the person's synced WhatsApp conversations and
succeeded API actions; checked again right before the request. The
account's WhatsApp limit (50 a day) and health apply; a send that
must wait is scheduled and runs later. A send whose outcome is
unknown fails OUTCOME_UNKNOWN for a person to check; it is never
retried.
6 credits (connector.outreach.send) per message sent.
Parameters
- Idempotency-Keyheaderstring
1 to 255 printable ASCII characters, scoped to the API key (a rotated key shares its predecessor's keys) and kept 24 h. Accepted on every POST, PATCH and DELETE (ignored on GET); required from v1 keys where the operation says
x-owlbound-idempotent: required(missing:400 VALIDATION_ERROR,param: Idempotency-Key). Same key and body after completion: the stored response is replayed withIdempotent-Replayed: true. Results over 64 KB are not stored; their replay is409 IDEMPOTENCY_RESULT_UNAVAILABLEwithdetails.originalRequestId(check state with a GET). Same key while the first request runs:409 IDEMPOTENCY_IN_PROGRESSwithRetry-After. Same key, other body or operation:422 IDEMPOTENCY_KEY_REUSED. A request that ended 5xx may be retried with the same key. 4xx results are stored and replayed too: a402 INSUFFICIENT_CREDITSreplays even after a top-up, so use a new Idempotency-Key after adding credits.min length 1max length 255 - Owlbound-Dry-Runheaderboolean
true: same as body fielddryRun: true(action operations). - Owlbound-Planheaderstring
A
planIdfrom a dry run: same as the body fieldplanId(action operations). A different bodyplanIdis400.pattern ^plan_[0-9a-f]{48}$
Request body (required) · application/json
WhatsAppSendRequest
- targetone ofrequired0
+ Show child fields− Hide child fields
- inputobjectrequired0
+ Show child fields− Hide child fields
- messagestringrequired
Template; at most 4,096 characters once filled.
min length 1max length 8192 - requestApprovalboolean0
Ask a person even when the key's approval mode would not.
- identityIdstring | null0
The identity that sends. A bound key uses its identity (another is
403 IDENTITY_NOT_ALLOWED); an unbound key must name one (422 IDENTITY_REQUIRED); a session defaults to the member's own. - dryRunboolean0
Same as the
Owlbound-Dry-Runheader. - planIdstring
Apply this dry run's plan (same as
Owlbound-Plan). Not withdryRun: true.pattern ^plan_[0-9a-f]{48}$ - modestring
auto(default) runs inline when the operation is sync, one target would run and the class is not send;syncforces inline (422 ACTION_NOT_SYNCwhen it cannot);asyncforces a run.autosyncasync - notifyActionNotify0
Events for this action.
targetEvents: truealso writes oneaction.target.completed/action.target.failedevent per target (feed and webhooks); without it only action-level events are written, to keep the volume down.webhookis accepted and has no effect (webhook endpoints subscribe by event type).
Responses
200The dry-run plan (the rendered content of the first targets).Show schema
ActionRunResponse
- dataone ofrequired0
+ Show child fields− Hide child fields
One of
with action
- 0
One action run. Read it again with
GET /api/v1/actions/{id}. - resultanyrequired0
The target's result, or null when it was skipped or failed.
with plan
- 0
What a real call would do, from every check up to the first side effect. Nothing is reserved, sent, written or charged. The plan is stored for 15 minutes: send its
planIdwith the real call to run exactly what was reviewed (409 PLAN_STALEwith a new plan if anything bound changed or it expired).
- 0
Metadata on every success response.
202The action was created: pending_approval until a person approves it in the app (reviewed by default), then queued and run.Show schema
pending_approval until a person approves it in the app (reviewed by default), then queued and run.ActionResponse
- dataobjectrequired0
+ Show child fields− Hide child fields
- 0
One action run. Read it again with
GET /api/v1/actions/{id}.
- 0
Metadata on every success response.
Errors
400 The request is invalid (VALIDATION_ERROR, CURSOR_MISMATCH, UNSUPPORTED_FILTER) · 401 Missing, invalid, expired or revoked key · 402 Not enough credits (INSUFFICIENT_CREDITS). With an Idempotency-Key this
result is stored and replayed: after adding credits, retry with a new
Idempotency-Key.
· 403 Missing scope, suspended workspace or unavailable identity · 404 Unknown resource or route. A resource in another workspace is also 404 · 409 IDEMPOTENCY_IN_PROGRESS (the first request with this key is still
running; `Retry-After` is the lease left, at most 60 s) or
IDEMPOTENCY_RESULT_UNAVAILABLE (the original result was too large to
store; `details.originalRequestId`).
· 422 IDEMPOTENCY_KEY_REUSED (the key was used with another body or operation), or an action that cannot run · 429 Rate limit reached · 500 Unexpected error · 503 The backend is unreachable; retry after `Retry-After`. Every error is {error: {type, code, message, requestId, docUrl}}; see error codes.
curl -X POST "https://app.owlbound.ai/api/v1/actions/whatsapp/send" \
-H "Authorization: Bearer $OWLBOUND_API_KEY" \
-H "Owlbound-Version: 2026-10-06" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"target": {
"kind": "contact",
"contactId": "<contactId>"
},
"input": {
"message": "<message>"
},
"dryRun": true
}'The example is a dry run: it returns a plan and runs, writes and charges nothing. See dry runs and plans.
Notify
Post a Slack message about each contact.
/api/v1/actions/notify/slack- Scope
- act
- Credits
- See the catalog entry
- Idempotency-Key
- required
- Class
- external_write
- Approval floor
- optional
- Runs
- Async
- Targets
- contact, contacts
- Max targets
- 1,000
- Dry run
- Supported
External write. One message per target contact in channelId of
the workspace's own Slack connection: a Slack connector
(connectorId, else the workspace's Slack app install; none:
422 NOTIFY_NOT_CONNECTED). text is a template filled from the
stored contact ({{firstName}}, {{company}}, ...; a missing
field is skipped MISSING_TEMPLATE_VARIABLES); blocks (Block Kit,
at most 50, sent as given) and threadTs (reply in a thread) are
optional. A refusal by Slack fails the target SLACK_<ERROR>
(e.g. SLACK_CHANNEL_NOT_FOUND, SLACK_NOT_IN_CHANNEL).
Always async and reviewed by default: the action is created
pending_approval (the workspace default; the dashboard agent and
MCP always) with every target's message frozen; a person approves
it in the app, then it runs. Right before each post the message is
rendered again from the contact: a contact changed since the
approval is skipped CONTENT_CHANGED. Each target is posted at
most once: a target run again after its post may have left, or a
post whose outcome is unknown (timeout, network), fails
NOTIFY_OUTCOME_UNKNOWN and is never posted twice. No person
outside the workspace is contacted, so no send limit applies.
Free (connector.notification.send).
Parameters
- Idempotency-Keyheaderstring
1 to 255 printable ASCII characters, scoped to the API key (a rotated key shares its predecessor's keys) and kept 24 h. Accepted on every POST, PATCH and DELETE (ignored on GET); required from v1 keys where the operation says
x-owlbound-idempotent: required(missing:400 VALIDATION_ERROR,param: Idempotency-Key). Same key and body after completion: the stored response is replayed withIdempotent-Replayed: true. Results over 64 KB are not stored; their replay is409 IDEMPOTENCY_RESULT_UNAVAILABLEwithdetails.originalRequestId(check state with a GET). Same key while the first request runs:409 IDEMPOTENCY_IN_PROGRESSwithRetry-After. Same key, other body or operation:422 IDEMPOTENCY_KEY_REUSED. A request that ended 5xx may be retried with the same key. 4xx results are stored and replayed too: a402 INSUFFICIENT_CREDITSreplays even after a top-up, so use a new Idempotency-Key after adding credits.min length 1max length 255 - Owlbound-Dry-Runheaderboolean
true: same as body fielddryRun: true(action operations). - Owlbound-Planheaderstring
A
planIdfrom a dry run: same as the body fieldplanId(action operations). A different bodyplanIdis400.pattern ^plan_[0-9a-f]{48}$
Request body (required) · application/json
NotifySlackRequest
- targetone ofrequired0
+ Show child fields− Hide child fields
- inputobjectrequired0
+ Show child fields− Hide child fields
- channelIdstringrequired
The Slack channel id (e.g. C0123456789).
min length 2max length 80 - textstringrequired
Template; at most 40,000 characters once filled.
min length 1max length 80000 - blocksarray of object
Block Kit blocks, sent as given (not templated).
max items 50 - threadTsstring
Reply in this message's thread.
pattern ^[0-9]+\.[0-9]+$ - connectorIdstring0
A Slack connector of the workspace; without one, the workspace's Slack app install.
- requestApprovalboolean0
Ask a person even when the key's approval mode would not.
- dryRunboolean0
Same as the
Owlbound-Dry-Runheader. - planIdstring
Apply this dry run's plan (same as
Owlbound-Plan). Not withdryRun: true.pattern ^plan_[0-9a-f]{48}$ - modestring
auto(default) runs inline when the operation is sync, one target would run and the class is not send;syncforces inline (422 ACTION_NOT_SYNCwhen it cannot);asyncforces a run.autosyncasync - notifyActionNotify0
Events for this action.
targetEvents: truealso writes oneaction.target.completed/action.target.failedevent per target (feed and webhooks); without it only action-level events are written, to keep the volume down.webhookis accepted and has no effect (webhook endpoints subscribe by event type).
Responses
200The dry-run plan (the first messages as they would post).Show schema
ActionRunResponse
- dataone ofrequired0
+ Show child fields− Hide child fields
One of
with action
- 0
One action run. Read it again with
GET /api/v1/actions/{id}. - resultanyrequired0
The target's result, or null when it was skipped or failed.
with plan
- 0
What a real call would do, from every check up to the first side effect. Nothing is reserved, sent, written or charged. The plan is stored for 15 minutes: send its
planIdwith the real call to run exactly what was reviewed (409 PLAN_STALEwith a new plan if anything bound changed or it expired).
- 0
Metadata on every success response.
202The action was created: pending_approval until a person approves it in the app (reviewed by default), then queued and run.Show schema
pending_approval until a person approves it in the app (reviewed by default), then queued and run.ActionResponse
- dataobjectrequired0
+ Show child fields− Hide child fields
- 0
One action run. Read it again with
GET /api/v1/actions/{id}.
- 0
Metadata on every success response.
Errors
400 The request is invalid (VALIDATION_ERROR, CURSOR_MISMATCH, UNSUPPORTED_FILTER) · 401 Missing, invalid, expired or revoked key · 402 Not enough credits (INSUFFICIENT_CREDITS). With an Idempotency-Key this
result is stored and replayed: after adding credits, retry with a new
Idempotency-Key.
· 403 Missing scope, suspended workspace or unavailable identity · 404 Unknown resource or route. A resource in another workspace is also 404 · 409 IDEMPOTENCY_IN_PROGRESS (the first request with this key is still
running; `Retry-After` is the lease left, at most 60 s) or
IDEMPOTENCY_RESULT_UNAVAILABLE (the original result was too large to
store; `details.originalRequestId`).
· 422 IDEMPOTENCY_KEY_REUSED (the key was used with another body or operation), or an action that cannot run · 429 Rate limit reached · 500 Unexpected error · 503 The backend is unreachable; retry after `Retry-After`. Every error is {error: {type, code, message, requestId, docUrl}}; see error codes.
curl -X POST "https://app.owlbound.ai/api/v1/actions/notify/slack" \
-H "Authorization: Bearer $OWLBOUND_API_KEY" \
-H "Owlbound-Version: 2026-10-06" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"target": {
"kind": "contact",
"contactId": "<contactId>"
},
"input": {
"channelId": "<channelId>",
"text": "<text>"
},
"dryRun": true
}'The example is a dry run: it returns a plan and runs, writes and charges nothing. See dry runs and plans.
Post a Google Chat message about each contact.
/api/v1/actions/notify/google_chat- Scope
- act
- Credits
- See the catalog entry
- Idempotency-Key
- required
- Class
- external_write
- Approval floor
- optional
- Runs
- Async
- Targets
- contact, contacts
- Max targets
- 1,000
- Dry run
- Supported
External write. One message per target contact through the
workspace's Google Chat connector (connectorId; another kind is
422 CONNECTOR_NOT_ALLOWED): to space (spaces/..., through the
Chat API with the connector's OAuth token), else to the connector's
incoming webhook, else to its space. Webhook URLs and tokens stay
on the connector; they are never taken from the request. text is
a template filled from the stored contact (at most 4,096
characters; a missing field is skipped
MISSING_TEMPLATE_VARIABLES); threadKey threads the messages. A
refusal by Google fails the target GOOGLE_CHAT_<status>.
Always async and reviewed by default: the action is created
pending_approval (the workspace default; the dashboard agent and
MCP always) with every target's message frozen; a person approves
it in the app, then it runs. Right before each post the message is
rendered again from the contact: a contact changed since the
approval is skipped CONTENT_CHANGED. Each target is posted at
most once: a target run again after its post may have left, or a
post whose outcome is unknown (timeout, network), fails
NOTIFY_OUTCOME_UNKNOWN and is never posted twice. No person
outside the workspace is contacted, so no send limit applies.
Free (connector.notification.send).
Parameters
- Idempotency-Keyheaderstring
1 to 255 printable ASCII characters, scoped to the API key (a rotated key shares its predecessor's keys) and kept 24 h. Accepted on every POST, PATCH and DELETE (ignored on GET); required from v1 keys where the operation says
x-owlbound-idempotent: required(missing:400 VALIDATION_ERROR,param: Idempotency-Key). Same key and body after completion: the stored response is replayed withIdempotent-Replayed: true. Results over 64 KB are not stored; their replay is409 IDEMPOTENCY_RESULT_UNAVAILABLEwithdetails.originalRequestId(check state with a GET). Same key while the first request runs:409 IDEMPOTENCY_IN_PROGRESSwithRetry-After. Same key, other body or operation:422 IDEMPOTENCY_KEY_REUSED. A request that ended 5xx may be retried with the same key. 4xx results are stored and replayed too: a402 INSUFFICIENT_CREDITSreplays even after a top-up, so use a new Idempotency-Key after adding credits.min length 1max length 255 - Owlbound-Dry-Runheaderboolean
true: same as body fielddryRun: true(action operations). - Owlbound-Planheaderstring
A
planIdfrom a dry run: same as the body fieldplanId(action operations). A different bodyplanIdis400.pattern ^plan_[0-9a-f]{48}$
Request body (required) · application/json
NotifyGoogleChatRequest
- targetone ofrequired0
+ Show child fields− Hide child fields
- inputobjectrequired0
+ Show child fields− Hide child fields
- connectorIdstringrequired0
A Google Chat connector of the workspace.
- spacestring
Post to this space through the Chat API (the connector's OAuth token); without it, the connector's webhook or space.
pattern ^spaces/[A-Za-z0-9_-]+$ - textstringrequired
Template; at most 4,096 characters once filled.
min length 1max length 8192 - threadKeystring
Thread the messages under this key.
max length 200 - requestApprovalboolean0
Ask a person even when the key's approval mode would not.
- dryRunboolean0
Same as the
Owlbound-Dry-Runheader. - planIdstring
Apply this dry run's plan (same as
Owlbound-Plan). Not withdryRun: true.pattern ^plan_[0-9a-f]{48}$ - modestring
auto(default) runs inline when the operation is sync, one target would run and the class is not send;syncforces inline (422 ACTION_NOT_SYNCwhen it cannot);asyncforces a run.autosyncasync - notifyActionNotify0
Events for this action.
targetEvents: truealso writes oneaction.target.completed/action.target.failedevent per target (feed and webhooks); without it only action-level events are written, to keep the volume down.webhookis accepted and has no effect (webhook endpoints subscribe by event type).
Responses
200The dry-run plan (the first messages as they would post).Show schema
ActionRunResponse
- dataone ofrequired0
+ Show child fields− Hide child fields
One of
with action
- 0
One action run. Read it again with
GET /api/v1/actions/{id}. - resultanyrequired0
The target's result, or null when it was skipped or failed.
with plan
- 0
What a real call would do, from every check up to the first side effect. Nothing is reserved, sent, written or charged. The plan is stored for 15 minutes: send its
planIdwith the real call to run exactly what was reviewed (409 PLAN_STALEwith a new plan if anything bound changed or it expired).
- 0
Metadata on every success response.
202The action was created: pending_approval until a person approves it in the app (reviewed by default), then queued and run.Show schema
pending_approval until a person approves it in the app (reviewed by default), then queued and run.ActionResponse
- dataobjectrequired0
+ Show child fields− Hide child fields
- 0
One action run. Read it again with
GET /api/v1/actions/{id}.
- 0
Metadata on every success response.
Errors
400 The request is invalid (VALIDATION_ERROR, CURSOR_MISMATCH, UNSUPPORTED_FILTER) · 401 Missing, invalid, expired or revoked key · 402 Not enough credits (INSUFFICIENT_CREDITS). With an Idempotency-Key this
result is stored and replayed: after adding credits, retry with a new
Idempotency-Key.
· 403 Missing scope, suspended workspace or unavailable identity · 404 Unknown resource or route. A resource in another workspace is also 404 · 409 IDEMPOTENCY_IN_PROGRESS (the first request with this key is still
running; `Retry-After` is the lease left, at most 60 s) or
IDEMPOTENCY_RESULT_UNAVAILABLE (the original result was too large to
store; `details.originalRequestId`).
· 422 IDEMPOTENCY_KEY_REUSED (the key was used with another body or operation), or an action that cannot run · 429 Rate limit reached · 500 Unexpected error · 503 The backend is unreachable; retry after `Retry-After`. Every error is {error: {type, code, message, requestId, docUrl}}; see error codes.
curl -X POST "https://app.owlbound.ai/api/v1/actions/notify/google_chat" \
-H "Authorization: Bearer $OWLBOUND_API_KEY" \
-H "Owlbound-Version: 2026-10-06" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"target": {
"kind": "contact",
"contactId": "<contactId>"
},
"input": {
"connectorId": "<connectorId>",
"text": "<text>"
},
"dryRun": true
}'The example is a dry run: it returns a plan and runs, writes and charges nothing. See dry runs and plans.
Sequences
Enroll contacts into an existing sequence (reviewed by a person first).
/api/v1/actions/sequences/enroll- Scope
- act
- Credits
- 4 credits per target
- Idempotency-Key
- required
- Class
- send
- Approval floor
- optional
- Runs
- Async
- Targets
- contact, contacts, rows
- Max targets
- 200
- Dry run
- Supported
Send class: an enrollment starts outreach to a real person. Enrolls
each contact (or each table row's linked contact) into
input.sequenceId through the app's enrollment (the same guards as
the app and v0 /sequences/{id}/enroll; never forced). The sequence
sends as its assigned identity: a key bound to another identity is
403 IDENTITY_NOT_ALLOWED, a sequence without one 422 IDENTITY_REQUIRED, a paused one 422 SEQUENCE_INACTIVE, one
without steps 422 SEQUENCE_NO_STEPS, another workspace's 404.
Skipped targets (dry run and run): SUPPRESSED (archived or opted
out), UNSUBSCRIBED, BOUNCED (email sequences), EXCLUDED
(exclusion lists), MISSING_CHANNEL (no email or LinkedIn URL a
step needs), ALREADY_ENROLLED (in flight in this sequence, or
enrolled by the API in the last 30 days), IN_SEQUENCE (active in
another sequence), NO_CONTACT (a row without a linked contact).
Always async and reviewed by default: the action is created
pending_approval (the workspace default; always for the dashboard
agent and MCP, whatever the key's mode) with each target frozen
(sequence, identity, contact); the identity's owner (or a person
they delegated approvals to) approves it in the app, then it runs.
A target is enrolled at most once, even when run again; a sequence
moved to another identity since the approval is skipped
CONTENT_CHANGED. The messages are then sent by the sequence, with
its own limits, working hours and guards. Each enrolled contact
spends one of the key's daily sends (caps.dailySends; over it the
target is scheduled with reason KEY_DAILY_SENDS until the next
UTC day, then enrolled; past 6 days from the action's creation it
is skipped KEY_DAILY_SENDS; nothing is charged while it waits).
4 credits
(sequence.enrollment) per enrolled contact, charged on success
only. dryRun: true returns who would be enrolled and who skipped,
and enrolls nothing.
Parameters
- Idempotency-Keyheaderstring
1 to 255 printable ASCII characters, scoped to the API key (a rotated key shares its predecessor's keys) and kept 24 h. Accepted on every POST, PATCH and DELETE (ignored on GET); required from v1 keys where the operation says
x-owlbound-idempotent: required(missing:400 VALIDATION_ERROR,param: Idempotency-Key). Same key and body after completion: the stored response is replayed withIdempotent-Replayed: true. Results over 64 KB are not stored; their replay is409 IDEMPOTENCY_RESULT_UNAVAILABLEwithdetails.originalRequestId(check state with a GET). Same key while the first request runs:409 IDEMPOTENCY_IN_PROGRESSwithRetry-After. Same key, other body or operation:422 IDEMPOTENCY_KEY_REUSED. A request that ended 5xx may be retried with the same key. 4xx results are stored and replayed too: a402 INSUFFICIENT_CREDITSreplays even after a top-up, so use a new Idempotency-Key after adding credits.min length 1max length 255 - Owlbound-Dry-Runheaderboolean
true: same as body fielddryRun: true(action operations). - Owlbound-Planheaderstring
A
planIdfrom a dry run: same as the body fieldplanId(action operations). A different bodyplanIdis400.pattern ^plan_[0-9a-f]{48}$
Request body (required) · application/json
SequenceEnrollRequest
- targetone ofrequired0
+ Show child fields− Hide child fields
- inputobjectrequired0
+ Show child fields− Hide child fields
- sequenceIdstringrequired0
An existing sequence of this workspace.
- requestApprovalboolean0
Ask a person even when the key's approval mode would not.
- identityIdstring | null0
Optional; when given it must be the sequence's sending identity.
- dryRunboolean0
Same as the
Owlbound-Dry-Runheader. - planIdstring
Apply this dry run's plan (same as
Owlbound-Plan). Not withdryRun: true.pattern ^plan_[0-9a-f]{48}$ - modestring
auto(default) runs inline when the operation is sync, one target would run and the class is not send;syncforces inline (422 ACTION_NOT_SYNCwhen it cannot);asyncforces a run.autosyncasync - notifyActionNotify0
Events for this action.
targetEvents: truealso writes oneaction.target.completed/action.target.failedevent per target (feed and webhooks); without it only action-level events are written, to keep the volume down.webhookis accepted and has no effect (webhook endpoints subscribe by event type).
Responses
200The dry-run plan (who would be enrolled, who skipped and why).Show schema
ActionRunResponse
- dataone ofrequired0
+ Show child fields− Hide child fields
One of
with action
- 0
One action run. Read it again with
GET /api/v1/actions/{id}. - resultanyrequired0
The target's result, or null when it was skipped or failed.
with plan
- 0
What a real call would do, from every check up to the first side effect. Nothing is reserved, sent, written or charged. The plan is stored for 15 minutes: send its
planIdwith the real call to run exactly what was reviewed (409 PLAN_STALEwith a new plan if anything bound changed or it expired).
- 0
Metadata on every success response.
202The action was created: pending_approval until a person approves it in the app, then queued and run.Show schema
pending_approval until a person approves it in the app, then queued and run.ActionResponse
- dataobjectrequired0
+ Show child fields− Hide child fields
- 0
One action run. Read it again with
GET /api/v1/actions/{id}.
- 0
Metadata on every success response.
Errors
400 The request is invalid (VALIDATION_ERROR, CURSOR_MISMATCH, UNSUPPORTED_FILTER) · 401 Missing, invalid, expired or revoked key · 403 Missing scope, suspended workspace or unavailable identity · 404 Unknown resource or route. A resource in another workspace is also 404 · 409 IDEMPOTENCY_IN_PROGRESS (the first request with this key is still
running; `Retry-After` is the lease left, at most 60 s) or
IDEMPOTENCY_RESULT_UNAVAILABLE (the original result was too large to
store; `details.originalRequestId`).
· 422 IDEMPOTENCY_KEY_REUSED (the key was used with another body or operation), or an action that cannot run · 429 Rate limit reached · 500 Unexpected error · 503 The backend is unreachable; retry after `Retry-After`. Every error is {error: {type, code, message, requestId, docUrl}}; see error codes.
curl -X POST "https://app.owlbound.ai/api/v1/actions/sequences/enroll" \
-H "Authorization: Bearer $OWLBOUND_API_KEY" \
-H "Owlbound-Version: 2026-10-06" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"target": {
"kind": "contact",
"contactId": "<contactId>"
},
"input": {
"sequenceId": "<sequenceId>"
},
"dryRun": true
}'The example is a dry run: it returns a plan and runs, writes and charges nothing. See dry runs and plans.
Pause contacts' enrollments in a sequence.
/api/v1/actions/sequences/pause- Scope
- act
- Credits
- See the catalog entry
- Idempotency-Key
- required
- Class
- write
- Approval floor
- none
- Runs
- Inline for one target, else async
- Targets
- contact, contacts, rows
- Max targets
- 500
- Dry run
- Supported
Per contact (or table row's linked contact), its enrollment in
input.sequenceId (a sequence of this workspace; another's is 404;
a key bound to an identity may only change that identity's
sequences, else 403 IDENTITY_NOT_ALLOWED).
An active or waiting enrollment is paused by hand (it stays paused when
the sequence is resumed); otherwise skipped NOT_ENROLLED or
NOT_ACTIVE. Write class, scope act, free, no approval; inline for one
contact. dryRun: true changes nothing.
Parameters
- Idempotency-Keyheaderstring
1 to 255 printable ASCII characters, scoped to the API key (a rotated key shares its predecessor's keys) and kept 24 h. Accepted on every POST, PATCH and DELETE (ignored on GET); required from v1 keys where the operation says
x-owlbound-idempotent: required(missing:400 VALIDATION_ERROR,param: Idempotency-Key). Same key and body after completion: the stored response is replayed withIdempotent-Replayed: true. Results over 64 KB are not stored; their replay is409 IDEMPOTENCY_RESULT_UNAVAILABLEwithdetails.originalRequestId(check state with a GET). Same key while the first request runs:409 IDEMPOTENCY_IN_PROGRESSwithRetry-After. Same key, other body or operation:422 IDEMPOTENCY_KEY_REUSED. A request that ended 5xx may be retried with the same key. 4xx results are stored and replayed too: a402 INSUFFICIENT_CREDITSreplays even after a top-up, so use a new Idempotency-Key after adding credits.min length 1max length 255 - Owlbound-Dry-Runheaderboolean
true: same as body fielddryRun: true(action operations). - Owlbound-Planheaderstring
A
planIdfrom a dry run: same as the body fieldplanId(action operations). A different bodyplanIdis400.pattern ^plan_[0-9a-f]{48}$
Request body (required) · application/json
SequenceStateRequest
- targetone ofrequired0
+ Show child fields− Hide child fields
- inputobjectrequired0
+ Show child fields− Hide child fields
- sequenceIdstringrequired0
A sequence of this workspace.
- requestApprovalboolean0
Resume only; ask a person even when the key's approval mode would not.
- identityIdstring | null0
Resume only; when given it must be the sequence's sending identity.
- dryRunboolean0
Same as the
Owlbound-Dry-Runheader. - planIdstring
Apply this dry run's plan (same as
Owlbound-Plan). Not withdryRun: true.pattern ^plan_[0-9a-f]{48}$ - modestring
auto(default) runs inline when the operation is sync, one target would run and the class is not send;syncforces inline (422 ACTION_NOT_SYNCwhen it cannot);asyncforces a run.autosyncasync - notifyActionNotify0
Events for this action.
targetEvents: truealso writes oneaction.target.completed/action.target.failedevent per target (feed and webhooks); without it only action-level events are written, to keep the volume down.webhookis accepted and has no effect (webhook endpoints subscribe by event type).
Responses
200The finished action and its result (one target), or the dry-run plan.Show schema
ActionRunResponse
- dataone ofrequired0
+ Show child fields− Hide child fields
One of
with action
- 0
One action run. Read it again with
GET /api/v1/actions/{id}. - resultanyrequired0
The target's result, or null when it was skipped or failed.
with plan
- 0
What a real call would do, from every check up to the first side effect. Nothing is reserved, sent, written or charged. The plan is stored for 15 minutes: send its
planIdwith the real call to run exactly what was reviewed (409 PLAN_STALEwith a new plan if anything bound changed or it expired).
- 0
Metadata on every success response.
202An async run was created (queued); poll it or read the event feed.Show schema
ActionResponse
- dataobjectrequired0
+ Show child fields− Hide child fields
- 0
One action run. Read it again with
GET /api/v1/actions/{id}.
- 0
Metadata on every success response.
Errors
400 The request is invalid (VALIDATION_ERROR, CURSOR_MISMATCH, UNSUPPORTED_FILTER) · 401 Missing, invalid, expired or revoked key · 403 Missing scope, suspended workspace or unavailable identity · 404 Unknown resource or route. A resource in another workspace is also 404 · 409 IDEMPOTENCY_IN_PROGRESS (the first request with this key is still
running; `Retry-After` is the lease left, at most 60 s) or
IDEMPOTENCY_RESULT_UNAVAILABLE (the original result was too large to
store; `details.originalRequestId`).
· 422 IDEMPOTENCY_KEY_REUSED (the key was used with another body or operation), or an action that cannot run · 429 Rate limit reached · 500 Unexpected error · 503 The backend is unreachable; retry after `Retry-After`. Every error is {error: {type, code, message, requestId, docUrl}}; see error codes.
curl -X POST "https://app.owlbound.ai/api/v1/actions/sequences/pause" \
-H "Authorization: Bearer $OWLBOUND_API_KEY" \
-H "Owlbound-Version: 2026-10-06" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"target": {
"kind": "contact",
"contactId": "<contactId>"
},
"input": {
"sequenceId": "<sequenceId>"
},
"dryRun": true
}'The example is a dry run: it returns a plan and runs, writes and charges nothing. See dry runs and plans.
Stop contacts' enrollments in a sequence.
/api/v1/actions/sequences/stop- Scope
- act
- Credits
- See the catalog entry
- Idempotency-Key
- required
- Class
- write
- Approval floor
- none
- Runs
- Inline for one target, else async
- Targets
- contact, contacts, rows
- Max targets
- 500
- Dry run
- Supported
Per contact (or table row's linked contact), its enrollment in
input.sequenceId (a sequence of this workspace; another's is 404;
a key bound to an identity may only change that identity's
sequences, else 403 IDENTITY_NOT_ALLOWED).
An active, waiting or paused enrollment ends as completed (a stop never
starts a chained sequence and marks nothing unsubscribed; nothing is
deleted); otherwise skipped NOT_ENROLLED or ALREADY_ENDED. Write
class, scope act, free, no approval; inline for one contact.
Parameters
- Idempotency-Keyheaderstring
1 to 255 printable ASCII characters, scoped to the API key (a rotated key shares its predecessor's keys) and kept 24 h. Accepted on every POST, PATCH and DELETE (ignored on GET); required from v1 keys where the operation says
x-owlbound-idempotent: required(missing:400 VALIDATION_ERROR,param: Idempotency-Key). Same key and body after completion: the stored response is replayed withIdempotent-Replayed: true. Results over 64 KB are not stored; their replay is409 IDEMPOTENCY_RESULT_UNAVAILABLEwithdetails.originalRequestId(check state with a GET). Same key while the first request runs:409 IDEMPOTENCY_IN_PROGRESSwithRetry-After. Same key, other body or operation:422 IDEMPOTENCY_KEY_REUSED. A request that ended 5xx may be retried with the same key. 4xx results are stored and replayed too: a402 INSUFFICIENT_CREDITSreplays even after a top-up, so use a new Idempotency-Key after adding credits.min length 1max length 255 - Owlbound-Dry-Runheaderboolean
true: same as body fielddryRun: true(action operations). - Owlbound-Planheaderstring
A
planIdfrom a dry run: same as the body fieldplanId(action operations). A different bodyplanIdis400.pattern ^plan_[0-9a-f]{48}$
Request body (required) · application/json
SequenceStateRequest
- targetone ofrequired0
+ Show child fields− Hide child fields
- inputobjectrequired0
+ Show child fields− Hide child fields
- sequenceIdstringrequired0
A sequence of this workspace.
- requestApprovalboolean0
Resume only; ask a person even when the key's approval mode would not.
- identityIdstring | null0
Resume only; when given it must be the sequence's sending identity.
- dryRunboolean0
Same as the
Owlbound-Dry-Runheader. - planIdstring
Apply this dry run's plan (same as
Owlbound-Plan). Not withdryRun: true.pattern ^plan_[0-9a-f]{48}$ - modestring
auto(default) runs inline when the operation is sync, one target would run and the class is not send;syncforces inline (422 ACTION_NOT_SYNCwhen it cannot);asyncforces a run.autosyncasync - notifyActionNotify0
Events for this action.
targetEvents: truealso writes oneaction.target.completed/action.target.failedevent per target (feed and webhooks); without it only action-level events are written, to keep the volume down.webhookis accepted and has no effect (webhook endpoints subscribe by event type).
Responses
200The finished action and its result (one target), or the dry-run plan.Show schema
ActionRunResponse
- dataone ofrequired0
+ Show child fields− Hide child fields
One of
with action
- 0
One action run. Read it again with
GET /api/v1/actions/{id}. - resultanyrequired0
The target's result, or null when it was skipped or failed.
with plan
- 0
What a real call would do, from every check up to the first side effect. Nothing is reserved, sent, written or charged. The plan is stored for 15 minutes: send its
planIdwith the real call to run exactly what was reviewed (409 PLAN_STALEwith a new plan if anything bound changed or it expired).
- 0
Metadata on every success response.
202An async run was created (queued); poll it or read the event feed.Show schema
ActionResponse
- dataobjectrequired0
+ Show child fields− Hide child fields
- 0
One action run. Read it again with
GET /api/v1/actions/{id}.
- 0
Metadata on every success response.
Errors
400 The request is invalid (VALIDATION_ERROR, CURSOR_MISMATCH, UNSUPPORTED_FILTER) · 401 Missing, invalid, expired or revoked key · 403 Missing scope, suspended workspace or unavailable identity · 404 Unknown resource or route. A resource in another workspace is also 404 · 409 IDEMPOTENCY_IN_PROGRESS (the first request with this key is still
running; `Retry-After` is the lease left, at most 60 s) or
IDEMPOTENCY_RESULT_UNAVAILABLE (the original result was too large to
store; `details.originalRequestId`).
· 422 IDEMPOTENCY_KEY_REUSED (the key was used with another body or operation), or an action that cannot run · 429 Rate limit reached · 500 Unexpected error · 503 The backend is unreachable; retry after `Retry-After`. Every error is {error: {type, code, message, requestId, docUrl}}; see error codes.
curl -X POST "https://app.owlbound.ai/api/v1/actions/sequences/stop" \
-H "Authorization: Bearer $OWLBOUND_API_KEY" \
-H "Owlbound-Version: 2026-10-06" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"target": {
"kind": "contact",
"contactId": "<contactId>"
},
"input": {
"sequenceId": "<sequenceId>"
},
"dryRun": true
}'The example is a dry run: it returns a plan and runs, writes and charges nothing. See dry runs and plans.
Resume paused enrollments in a sequence (reviewed by a person first).
/api/v1/actions/sequences/resume- Scope
- act
- Credits
- See the catalog entry
- Idempotency-Key
- required
- Class
- send
- Approval floor
- optional
- Runs
- Async
- Targets
- contact, contacts, rows
- Max targets
- 500
- Dry run
- Supported
Per contact (or table row's linked contact), its enrollment in
input.sequenceId (a sequence of this workspace; another's is 404;
a key bound to an identity may only change that identity's
sequences, else 403 IDENTITY_NOT_ALLOWED).
Send class: resuming restarts outreach. Reviewed like an enrollment (the
sequence's identity's owner approves it in the app; always for the
dashboard agent and MCP); each target frozen (sequence, identity,
contact, enrollment). Right before resuming, the enrollment must still be
paused, the sequence active under the same identity, and the contact
still contactable (SUPPRESSED, UNSUBSCRIBED, EXCLUDED keep it
paused). Skipped: NOT_ENROLLED, NOT_PAUSED. A paused sequence is 422 SEQUENCE_INACTIVE. Free; the sequence's own steps, limits and guards
apply to what it then sends.
Parameters
- Idempotency-Keyheaderstring
1 to 255 printable ASCII characters, scoped to the API key (a rotated key shares its predecessor's keys) and kept 24 h. Accepted on every POST, PATCH and DELETE (ignored on GET); required from v1 keys where the operation says
x-owlbound-idempotent: required(missing:400 VALIDATION_ERROR,param: Idempotency-Key). Same key and body after completion: the stored response is replayed withIdempotent-Replayed: true. Results over 64 KB are not stored; their replay is409 IDEMPOTENCY_RESULT_UNAVAILABLEwithdetails.originalRequestId(check state with a GET). Same key while the first request runs:409 IDEMPOTENCY_IN_PROGRESSwithRetry-After. Same key, other body or operation:422 IDEMPOTENCY_KEY_REUSED. A request that ended 5xx may be retried with the same key. 4xx results are stored and replayed too: a402 INSUFFICIENT_CREDITSreplays even after a top-up, so use a new Idempotency-Key after adding credits.min length 1max length 255 - Owlbound-Dry-Runheaderboolean
true: same as body fielddryRun: true(action operations). - Owlbound-Planheaderstring
A
planIdfrom a dry run: same as the body fieldplanId(action operations). A different bodyplanIdis400.pattern ^plan_[0-9a-f]{48}$
Request body (required) · application/json
SequenceStateRequest
- targetone ofrequired0
+ Show child fields− Hide child fields
- inputobjectrequired0
+ Show child fields− Hide child fields
- sequenceIdstringrequired0
A sequence of this workspace.
- requestApprovalboolean0
Resume only; ask a person even when the key's approval mode would not.
- identityIdstring | null0
Resume only; when given it must be the sequence's sending identity.
- dryRunboolean0
Same as the
Owlbound-Dry-Runheader. - planIdstring
Apply this dry run's plan (same as
Owlbound-Plan). Not withdryRun: true.pattern ^plan_[0-9a-f]{48}$ - modestring
auto(default) runs inline when the operation is sync, one target would run and the class is not send;syncforces inline (422 ACTION_NOT_SYNCwhen it cannot);asyncforces a run.autosyncasync - notifyActionNotify0
Events for this action.
targetEvents: truealso writes oneaction.target.completed/action.target.failedevent per target (feed and webhooks); without it only action-level events are written, to keep the volume down.webhookis accepted and has no effect (webhook endpoints subscribe by event type).
Responses
200The finished action and its result (one target), or the dry-run plan.Show schema
ActionRunResponse
- dataone ofrequired0
+ Show child fields− Hide child fields
One of
with action
- 0
One action run. Read it again with
GET /api/v1/actions/{id}. - resultanyrequired0
The target's result, or null when it was skipped or failed.
with plan
- 0
What a real call would do, from every check up to the first side effect. Nothing is reserved, sent, written or charged. The plan is stored for 15 minutes: send its
planIdwith the real call to run exactly what was reviewed (409 PLAN_STALEwith a new plan if anything bound changed or it expired).
- 0
Metadata on every success response.
202The action was created: pending_approval until a person approves it in the app, then queued and run.Show schema
pending_approval until a person approves it in the app, then queued and run.ActionResponse
- dataobjectrequired0
+ Show child fields− Hide child fields
- 0
One action run. Read it again with
GET /api/v1/actions/{id}.
- 0
Metadata on every success response.
Errors
400 The request is invalid (VALIDATION_ERROR, CURSOR_MISMATCH, UNSUPPORTED_FILTER) · 401 Missing, invalid, expired or revoked key · 403 Missing scope, suspended workspace or unavailable identity · 404 Unknown resource or route. A resource in another workspace is also 404 · 409 IDEMPOTENCY_IN_PROGRESS (the first request with this key is still
running; `Retry-After` is the lease left, at most 60 s) or
IDEMPOTENCY_RESULT_UNAVAILABLE (the original result was too large to
store; `details.originalRequestId`).
· 422 IDEMPOTENCY_KEY_REUSED (the key was used with another body or operation), or an action that cannot run · 429 Rate limit reached · 500 Unexpected error · 503 The backend is unreachable; retry after `Retry-After`. Every error is {error: {type, code, message, requestId, docUrl}}; see error codes.
curl -X POST "https://app.owlbound.ai/api/v1/actions/sequences/resume" \
-H "Authorization: Bearer $OWLBOUND_API_KEY" \
-H "Owlbound-Version: 2026-10-06" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"target": {
"kind": "contact",
"contactId": "<contactId>"
},
"input": {
"sequenceId": "<sequenceId>"
},
"dryRun": true
}'The example is a dry run: it returns a plan and runs, writes and charges nothing. See dry runs and plans.
Plays
Start a deployed play for contacts (reviewed by a person first).
/api/v1/actions/plays/trigger- Scope
- act
- Credits
- See the catalog entry
- Idempotency-Key
- required
- Class
- send
- Approval floor
- optional
- Runs
- Async
- Targets
- contact, contacts, rows
- Max targets
- 200
- Dry run
- Supported
Send class: a play can contact people. Starts the play's deployed
version (never the draft) for each contact (or each table row's
linked contact), as the app's manual start does; its nodes then run
in the play executor, every send through the send gate (contact
rules, limits, the identity's counters), and its own Human review
nodes still apply. A play that is not deployed is 422 PLAY_NOT_DEPLOYED, a paused one 422 PLAY_INACTIVE, one whose
cost has no bound (a list loop without Max items, or a cycle) 422 PLAY_COST_UNBOUNDED, another workspace's 404. A play with nodes
that contact people (LinkedIn, email, sequence enrollment) runs as
one identity (the key's bound identity, or identityId): its
LinkedIn nodes send through that identity's account, and its
sequence nodes must enroll into that identity's sequences (403 IDENTITY_NOT_ALLOWED). Skipped targets (dry run and run):
SUPPRESSED, UNSUBSCRIBED, BOUNCED, EXCLUDED (plays that
contact people), ARCHIVED, ALREADY_ENROLLED (in flight in this
play), REENROLLMENT_NOT_ALLOWED, ALREADY_TRIGGERED (started on
this play version by the API in the last 30 days), NO_CONTACT.
Always async; the approval follows what the play can do: a play
that contacts people is reviewed like any send (the workspace
default; always for the dashboard agent and MCP), one that writes
to an outside system (CRM, HTTP, Slack) like an external write,
any other only over the key's credit threshold. Each target is
frozen (play, deployed version, contact, identity): a play
redeployed since the approval is skipped CONTENT_CHANGED. A
contact is started at most once per play version, even when run
again. Credits: each started contact holds the play's maximum path
cost (every node at its price, the costliest branch, loops at
their Max items), counted against maxCredits, the key's
caps.dailyCredits and the wallet (skipped with
RUN_CAPPED, CAP_EXCEEDED or INSUFFICIENT_CREDITS otherwise);
the nodes are charged their usual prices from that hold, a node
that would go past it stops the run RUN_CAPPED, and what is left
is released when the run ends. Starting a play that contacts people
spends one of the key's daily sends (caps.dailySends).
dryRun: true returns the version, the per-contact maximum and who
would be started, and starts nothing.
Parameters
- Idempotency-Keyheaderstring
1 to 255 printable ASCII characters, scoped to the API key (a rotated key shares its predecessor's keys) and kept 24 h. Accepted on every POST, PATCH and DELETE (ignored on GET); required from v1 keys where the operation says
x-owlbound-idempotent: required(missing:400 VALIDATION_ERROR,param: Idempotency-Key). Same key and body after completion: the stored response is replayed withIdempotent-Replayed: true. Results over 64 KB are not stored; their replay is409 IDEMPOTENCY_RESULT_UNAVAILABLEwithdetails.originalRequestId(check state with a GET). Same key while the first request runs:409 IDEMPOTENCY_IN_PROGRESSwithRetry-After. Same key, other body or operation:422 IDEMPOTENCY_KEY_REUSED. A request that ended 5xx may be retried with the same key. 4xx results are stored and replayed too: a402 INSUFFICIENT_CREDITSreplays even after a top-up, so use a new Idempotency-Key after adding credits.min length 1max length 255 - Owlbound-Dry-Runheaderboolean
true: same as body fielddryRun: true(action operations). - Owlbound-Planheaderstring
A
planIdfrom a dry run: same as the body fieldplanId(action operations). A different bodyplanIdis400.pattern ^plan_[0-9a-f]{48}$
Request body (required) · application/json
PlayTriggerRequest
- targetone ofrequired0
+ Show child fields− Hide child fields
- inputobjectrequired0
+ Show child fields− Hide child fields
- workflowIdstringrequired0
A deployed, active play of this workspace.
- requestApprovalboolean0
Ask a person even when the key's approval mode would not.
- identityIdstring | null0
The identity a play that contacts people runs as (its LinkedIn nodes send through its account). Required on an unbound key for such a play; a bound key may only name its own.
- maxCreditsnumber
Caps the sum of the started contacts' holds (each the play's maximum path cost); contacts past it are skipped
RUN_CAPPED.min 0 - dryRunboolean0
Same as the
Owlbound-Dry-Runheader. - planIdstring
Apply this dry run's plan (same as
Owlbound-Plan). Not withdryRun: true.pattern ^plan_[0-9a-f]{48}$ - modestring
auto(default) runs inline when the operation is sync, one target would run and the class is not send;syncforces inline (422 ACTION_NOT_SYNCwhen it cannot);asyncforces a run.autosyncasync - notifyActionNotify0
Events for this action.
targetEvents: truealso writes oneaction.target.completed/action.target.failedevent per target (feed and webhooks); without it only action-level events are written, to keep the volume down.webhookis accepted and has no effect (webhook endpoints subscribe by event type).
Responses
200The dry-run plan (the play version, its per-contact maximum, who would be started and who skipped).Show schema
ActionRunResponse
- dataone ofrequired0
+ Show child fields− Hide child fields
One of
with action
- 0
One action run. Read it again with
GET /api/v1/actions/{id}. - resultanyrequired0
The target's result, or null when it was skipped or failed.
with plan
- 0
What a real call would do, from every check up to the first side effect. Nothing is reserved, sent, written or charged. The plan is stored for 15 minutes: send its
planIdwith the real call to run exactly what was reviewed (409 PLAN_STALEwith a new plan if anything bound changed or it expired).
- 0
Metadata on every success response.
202The action was created: pending_approval until a person approves it in the app (when the play's class asks for it), then queued and run.Show schema
pending_approval until a person approves it in the app (when the play's class asks for it), then queued and run.ActionResponse
- dataobjectrequired0
+ Show child fields− Hide child fields
- 0
One action run. Read it again with
GET /api/v1/actions/{id}.
- 0
Metadata on every success response.
Errors
400 The request is invalid (VALIDATION_ERROR, CURSOR_MISMATCH, UNSUPPORTED_FILTER) · 401 Missing, invalid, expired or revoked key · 403 Missing scope, suspended workspace or unavailable identity · 404 Unknown resource or route. A resource in another workspace is also 404 · 409 IDEMPOTENCY_IN_PROGRESS (the first request with this key is still
running; `Retry-After` is the lease left, at most 60 s) or
IDEMPOTENCY_RESULT_UNAVAILABLE (the original result was too large to
store; `details.originalRequestId`).
· 422 IDEMPOTENCY_KEY_REUSED (the key was used with another body or operation), or an action that cannot run · 429 Rate limit reached · 500 Unexpected error · 503 The backend is unreachable; retry after `Retry-After`. Every error is {error: {type, code, message, requestId, docUrl}}; see error codes.
curl -X POST "https://app.owlbound.ai/api/v1/actions/plays/trigger" \
-H "Authorization: Bearer $OWLBOUND_API_KEY" \
-H "Owlbound-Version: 2026-10-06" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"target": {
"kind": "contact",
"contactId": "<contactId>"
},
"input": {
"workflowId": "<workflowId>"
},
"dryRun": true
}'The example is a dry run: it returns a plan and runs, writes and charges nothing. See dry runs and plans.
Models on this page
Shared objects the operations above link to. Clients must ignore fields they do not know.
ActionTargetContactobject
- kind"contact"required0
- contactIdstringrequired0
ActionTargetContactsobject
- kind"contacts"required0
- contactIdsarray of stringrequired
Deduplicated, order kept.
min items 1max items 1000
ActionTargetRowsobject
Rows of one table; each row's linked contact is the subject. A row
without a linked contact is skipped NO_CONTACT (push it to
contacts first); two rows of one contact act once
(DUPLICATE_TARGET). Not available yet for some workspaces: the
call answers 409 TABLE_STORE_UNSUPPORTED.
- kind"rows"required0
- tableIdstringrequired0
- rowIdsarray of stringrequired
Deduplicated, order kept.
min items 1max items 1000
ActionNotifyobject
Events for this action. targetEvents: true also writes one
action.target.completed / action.target.failed event per target
(feed and webhooks); without it only action-level events are written,
to keep the volume down. webhook is accepted and has no effect
(webhook endpoints subscribe by event type).
- targetEventsbooleandefault false
- webhookboolean0
Actionobject
One action run. Read it again with GET /api/v1/actions/{id}.
- idstringrequired0
- kindstringrequired0
The operation id.
- classstringrequired
Safety class: decides scope, approval, caps and enforcement.
readwritespendexternal_writesend - statusstringrequiredpending_approvalqueuedrunningsucceededfailedrejectedcanceledexpiredpartial
- identityIdstring | nullrequired0
- 0
- costobjectrequired0
+ Show child fields− Hide child fields
- estimatednumberrequired0
- chargednumberrequired0
- reservationIdstring | nullrequired0
- 0
- targetobjectrequired0
+ Show child fields− Hide child fields
- kindstringrequired0
- tableIdstring0
- viewIdstring0
- columnIdstring0
The send column this action runs (tables.rows.push target column).
- outputobject | nullrequired0
- resultanyrequired0
The single target's result (sync), or null; bulk results are per target.
- 0
- 0
Who started the action.
agentis an LLM-driven caller (the in-app agent, MCP) acting for the key or member inonBehalfOf. - createdAtnumberrequired0
- updatedAtnumberrequired0
ActionApprovalobject
- modestringrequirednonealwaysover_threshold
- urlstring | nullrequired0
- decidedBystring | nullrequired0
- decidedAtnumber | nullrequired0
- expiresAtnumber | nullrequired0
ActionCountsobject
- totalintegerrequired0
- queuedintegerrequired0
- scheduledintegerrequired0
- runningintegerrequired0
- succeededintegerrequired0
- skippedintegerrequired0
- failedintegerrequired0
- canceledintegerrequired0
ActionErrorobject
- codestringrequired0
- messagestringrequired0
ActionActorobject
Who started the action. agent is an LLM-driven caller (the in-app
agent, MCP) acting for the key or member in onBehalfOf.
- kindstringrequiredapiKeysessionagentjob
- idstringrequired0
API key id, user id, agent run id or job id.
- onBehalfOfobject0
Agent only.
+ Show child fields− Hide child fields
- kindstringrequiredapiKeysession
- idstringrequired0
Planobject
What a real call would do, from every check up to the first side
effect. Nothing is reserved, sent, written or charged. The plan is
stored for 15 minutes: send its planId with the real call to run
exactly what was reviewed (409 PLAN_STALE with a new plan if
anything bound changed or it expired).
- operationIdstringrequired0
- classstringrequired
Safety class: decides scope, approval, caps and enforcement.
readwritespendexternal_writesend - identityIdstring | nullrequired0
- targetsobjectrequired0
+ Show child fields− Hide child fields
- totalintegerrequired0
- wouldRunintegerrequired0
- wouldSkiparray of objectrequired0
+ Show child fields− Hide child fields
- reasonstringrequired0
- countintegerrequired0
- samplearray of stringrequired0
- creditsobjectrequired0
+ Show child fields− Hide child fields
- estimatedMinnumberrequired0
- estimatedMaxnumberrequired0
- availablenumberrequired0
- capRemainingnumber | nullrequired0
- quotaobject | nullrequired0
LinkedIn sends: the daily budget the action spends (an estimate: warm-up, health and the identity's other sends move it; the send gate decides at send time).
bucketis the daily limit's key,identityRemainingTodaywhat the identity's account has left today,keyRemainingTodaythe key's, user's or agent's daily sends left (null: uncapped),wouldDeferthe targets that would wait for a later day,firstSlotAt/lastSlotAtwhen the first and last of them would go (ms; null past 7 days). Null for other operations.+ Show child fields− Hide child fields
- bucketstringrequired0
- identityRemainingTodaynumberrequired0
- keyRemainingTodaynumber | nullrequired0
- wouldDefernumberrequired0
- firstSlotAtnumber | nullrequired0
- lastSlotAtnumber | nullrequired0
- approvalobjectrequired0
+ Show child fields− Hide child fields
- requiredbooleanrequired0
- reasonstring | nullrequiredkey_modeoperation_floorover_thresholdagentrequestedidentity_owner
- writesobjectrequired0
- previewarray of anyrequired0
The first targets that would run (at most 5).
- planIdstringrequired
Valid until
expiresAt, for the principal that made the dry run only.pattern ^plan_[0-9a-f]{48}$ - expiresAtnumberrequired0
When the plan expires (epoch milliseconds).
ResponseMetaobject
Metadata on every success response.
- requestIdstringrequired0
- 0
- pagePageMeta0
Cursor pagination.
hasMoreis the only end signal; pages may be short. - dryRunboolean0
True only on a dry-run response (action operations).
CreditsMetaobject
- usednumberrequired0
Credits charged by this request. Reads are free.
- balancenumber0
- estimatednumber0
Dry run only (action operations).
- availablenumber0
Dry run only (action operations).
- reservationIdstring | null0
Async operations only; the job settles this reservation.
PageMetaobject
Cursor pagination. hasMore is the only end signal; pages may be short.
- nextCursorstring | nullrequired0
- hasMorebooleanrequired0
- limitintegerrequiredmin 1max 200