Browse the docs

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).

post/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 with Idempotent-Replayed: true. Results over 64 KB are not stored; their replay is 409 IDEMPOTENCY_RESULT_UNAVAILABLE with details.originalRequestId (check state with a GET). Same key while the first request runs: 409 IDEMPOTENCY_IN_PROGRESS with Retry-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: a 402 INSUFFICIENT_CREDITS replays 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 field dryRun: true (action operations).

  • Owlbound-Planheaderstring

    A planId from a dry run: same as the body field planId (action operations). A different body planId is 400.

    pattern ^plan_[0-9a-f]{48}$

Request body (required) · application/json

CrmHubSpotUpsertContactRequest

  • targetone ofrequired
    0
    + Show child fields
  • inputobject
    0
    + Show 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
    • requestApprovalboolean

      Ask a person even when the key's approval mode would not.

      0
  • dryRunboolean

    Same as the Owlbound-Dry-Run header.

    0
  • planIdstring

    Apply this dry run's plan (same as Owlbound-Plan). Not with dryRun: 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; sync forces inline (422 ACTION_NOT_SYNC when it cannot); async forces a run.

    autosyncasync
  • 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).

    0

Responses

200
The dry-run plan (with the per-field CRM diff of the first targets).
Show schema

ActionRunResponse

  • dataone ofrequired
    0
    + Show child fields

    One of

    with action

    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
    • resultanyrequired

      The target's result, or null when it was skipped or failed.

      0

    with plan

    • planPlanrequired

      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).

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0
202
The action was created: pending_approval until a person approves it in the app, then queued and run.
Show schema

ActionResponse

  • dataobjectrequired
    0
    + Show child fields
    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0

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).

post/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 with Idempotent-Replayed: true. Results over 64 KB are not stored; their replay is 409 IDEMPOTENCY_RESULT_UNAVAILABLE with details.originalRequestId (check state with a GET). Same key while the first request runs: 409 IDEMPOTENCY_IN_PROGRESS with Retry-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: a 402 INSUFFICIENT_CREDITS replays 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 field dryRun: true (action operations).

  • Owlbound-Planheaderstring

    A planId from a dry run: same as the body field planId (action operations). A different body planId is 400.

    pattern ^plan_[0-9a-f]{48}$

Request body (required) · application/json

CrmHubSpotUpsertCompanyRequest

  • targetone ofrequired
    0
    + Show child fields
  • inputobject
    0
    + Show 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
    • requestApprovalboolean

      Ask a person even when the key's approval mode would not.

      0
  • dryRunboolean

    Same as the Owlbound-Dry-Run header.

    0
  • planIdstring

    Apply this dry run's plan (same as Owlbound-Plan). Not with dryRun: 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; sync forces inline (422 ACTION_NOT_SYNC when it cannot); async forces a run.

    autosyncasync
  • 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).

    0

Responses

200
The dry-run plan (with the per-field CRM diff of the first targets).
Show schema

ActionRunResponse

  • dataone ofrequired
    0
    + Show child fields

    One of

    with action

    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
    • resultanyrequired

      The target's result, or null when it was skipped or failed.

      0

    with plan

    • planPlanrequired

      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).

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0
202
The action was created: pending_approval until a person approves it in the app, then queued and run.
Show schema

ActionResponse

  • dataobjectrequired
    0
    + Show child fields
    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0

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.

post/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 with Idempotent-Replayed: true. Results over 64 KB are not stored; their replay is 409 IDEMPOTENCY_RESULT_UNAVAILABLE with details.originalRequestId (check state with a GET). Same key while the first request runs: 409 IDEMPOTENCY_IN_PROGRESS with Retry-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: a 402 INSUFFICIENT_CREDITS replays 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 field dryRun: true (action operations).

  • Owlbound-Planheaderstring

    A planId from a dry run: same as the body field planId (action operations). A different body planId is 400.

    pattern ^plan_[0-9a-f]{48}$

Request body (required) · application/json

CrmHubSpotEnrollSequenceRequest

  • targetone ofrequired
    0
    + Show child fields
  • inputobjectrequired
    0
    + Show child fields
    • sequenceIdstringrequired
      pattern ^[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}$
    • senderEmailstringrequired
      min length 1
    • senderAliasAddressstring
      0
    • requestApprovalboolean

      Ask a person even when the key's approval mode would not.

      0
  • identityIdstring | null

    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).

    0
  • dryRunboolean

    Same as the Owlbound-Dry-Run header.

    0
  • planIdstring

    Apply this dry run's plan (same as Owlbound-Plan). Not with dryRun: 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; sync forces inline (422 ACTION_NOT_SYNC when it cannot); async forces a run.

    autosyncasync
  • 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).

    0

Responses

200
The dry-run plan (the rendered content of the first targets).
Show schema

ActionRunResponse

  • dataone ofrequired
    0
    + Show child fields

    One of

    with action

    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
    • resultanyrequired

      The target's result, or null when it was skipped or failed.

      0

    with plan

    • planPlanrequired

      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).

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0
202
The action was created: pending_approval until a person approves it in the app (reviewed by default), then queued and run.
Show schema

ActionResponse

  • dataobjectrequired
    0
    + Show child fields
    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0

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.

post/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 with Idempotent-Replayed: true. Results over 64 KB are not stored; their replay is 409 IDEMPOTENCY_RESULT_UNAVAILABLE with details.originalRequestId (check state with a GET). Same key while the first request runs: 409 IDEMPOTENCY_IN_PROGRESS with Retry-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: a 402 INSUFFICIENT_CREDITS replays 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 field dryRun: true (action operations).

  • Owlbound-Planheaderstring

    A planId from a dry run: same as the body field planId (action operations). A different body planId is 400.

    pattern ^plan_[0-9a-f]{48}$

Request body (required) · application/json

CrmEmailBisonSendEmailRequest

  • targetone ofrequired
    0
    + Show child fields
  • inputobjectrequired
    0
    + Show child fields
    • subjectstringrequired

      Template; at most 200 characters once filled.

      min length 1max length 400
    • bodystringrequired

      Template.

      min length 1max length 50000
    • campaignNamePrefixstring
      max length 100
    • requestApprovalboolean

      Ask a person even when the key's approval mode would not.

      0
  • identityIdstring | null

    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).

    0
  • dryRunboolean

    Same as the Owlbound-Dry-Run header.

    0
  • planIdstring

    Apply this dry run's plan (same as Owlbound-Plan). Not with dryRun: 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; sync forces inline (422 ACTION_NOT_SYNC when it cannot); async forces a run.

    autosyncasync
  • 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).

    0

Responses

200
The dry-run plan (the rendered content of the first targets).
Show schema

ActionRunResponse

  • dataone ofrequired
    0
    + Show child fields

    One of

    with action

    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
    • resultanyrequired

      The target's result, or null when it was skipped or failed.

      0

    with plan

    • planPlanrequired

      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).

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0
202
The action was created: pending_approval until a person approves it in the app (reviewed by default), then queued and run.
Show schema

ActionResponse

  • dataobjectrequired
    0
    + Show child fields
    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0

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

actions.http.call

Call your own HTTP endpoint once per contact.

post/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 with Idempotent-Replayed: true. Results over 64 KB are not stored; their replay is 409 IDEMPOTENCY_RESULT_UNAVAILABLE with details.originalRequestId (check state with a GET). Same key while the first request runs: 409 IDEMPOTENCY_IN_PROGRESS with Retry-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: a 402 INSUFFICIENT_CREDITS replays 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 field dryRun: true (action operations).

  • Owlbound-Planheaderstring

    A planId from a dry run: same as the body field planId (action operations). A different body planId is 400.

    pattern ^plan_[0-9a-f]{48}$

Request body (required) · application/json

HttpCallRequest

  • targetone ofrequired
    0
    + Show child fields
  • inputobjectrequired
    0
    + Show child fields
    • methodstringrequired
      GETPOSTPUTPATCH
    • urlstringrequired

      https only; may hold {{contact.field}}.

      max length 2000
    • headersobject

      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.

      0
    • bodyany

      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.

      0
    • authobject
      0
      + Show child fields
      • connectorIdstringrequired

        An http_auth connector of this workspace (a credential with its allowed hosts, header and scheme). Any other connector type is 422 CONNECTOR_NOT_ALLOWED; a URL not on its hosts is 422 HOST_NOT_ALLOWED.

        0
    • requestApprovalboolean

      Ask a person even when the key's approval mode would not.

      0
  • dryRunboolean

    Same as the Owlbound-Dry-Run header.

    0
  • planIdstring

    Apply this dry run's plan (same as Owlbound-Plan). Not with dryRun: 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; sync forces inline (422 ACTION_NOT_SYNC when it cannot); async forces a run.

    autosyncasync
  • 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).

    0

Responses

200
The dry-run plan (the first requests, no secret).
Show schema

ActionRunResponse

  • dataone ofrequired
    0
    + Show child fields

    One of

    with action

    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
    • resultanyrequired

      The target's result, or null when it was skipped or failed.

      0

    with plan

    • planPlanrequired

      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).

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0
202
The action was created: pending_approval until a person approves it in the app (reviewed by default), then queued and run.
Show schema

ActionResponse

  • dataobjectrequired
    0
    + Show child fields
    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0

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.

Email

actions.email.send

Send an email to each contact from the identity's mailbox.

post/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 with Idempotent-Replayed: true. Results over 64 KB are not stored; their replay is 409 IDEMPOTENCY_RESULT_UNAVAILABLE with details.originalRequestId (check state with a GET). Same key while the first request runs: 409 IDEMPOTENCY_IN_PROGRESS with Retry-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: a 402 INSUFFICIENT_CREDITS replays 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 field dryRun: true (action operations).

  • Owlbound-Planheaderstring

    A planId from a dry run: same as the body field planId (action operations). A different body planId is 400.

    pattern ^plan_[0-9a-f]{48}$

Request body (required) · application/json

EmailSendRequest

  • targetone ofrequired
    0
    + Show child fields
  • inputobjectrequired
    0
    + Show 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
    • isHtmlboolean

      Send the body as HTML (default plain text).

      0
    • requestApprovalboolean

      Ask a person even when the key's approval mode would not.

      0
  • identityIdstring | null

    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.

    0
  • dryRunboolean

    Same as the Owlbound-Dry-Run header.

    0
  • planIdstring

    Apply this dry run's plan (same as Owlbound-Plan). Not with dryRun: 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; sync forces inline (422 ACTION_NOT_SYNC when it cannot); async forces a run.

    autosyncasync
  • 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).

    0

Responses

200
The dry-run plan (the rendered content of the first targets).
Show schema

ActionRunResponse

  • dataone ofrequired
    0
    + Show child fields

    One of

    with action

    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
    • resultanyrequired

      The target's result, or null when it was skipped or failed.

      0

    with plan

    • planPlanrequired

      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).

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0
202
The action was created: pending_approval until a person approves it in the app (reviewed by default), then queued and run.
Show schema

ActionResponse

  • dataobjectrequired
    0
    + Show child fields
    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0

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.

Whatsapp

actions.whatsapp.send

Send a WhatsApp message to each contact.

post/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 with Idempotent-Replayed: true. Results over 64 KB are not stored; their replay is 409 IDEMPOTENCY_RESULT_UNAVAILABLE with details.originalRequestId (check state with a GET). Same key while the first request runs: 409 IDEMPOTENCY_IN_PROGRESS with Retry-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: a 402 INSUFFICIENT_CREDITS replays 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 field dryRun: true (action operations).

  • Owlbound-Planheaderstring

    A planId from a dry run: same as the body field planId (action operations). A different body planId is 400.

    pattern ^plan_[0-9a-f]{48}$

Request body (required) · application/json

WhatsAppSendRequest

  • targetone ofrequired
    0
    + Show child fields
  • inputobjectrequired
    0
    + Show child fields
    • messagestringrequired

      Template; at most 4,096 characters once filled.

      min length 1max length 8192
    • requestApprovalboolean

      Ask a person even when the key's approval mode would not.

      0
  • identityIdstring | null

    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.

    0
  • dryRunboolean

    Same as the Owlbound-Dry-Run header.

    0
  • planIdstring

    Apply this dry run's plan (same as Owlbound-Plan). Not with dryRun: 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; sync forces inline (422 ACTION_NOT_SYNC when it cannot); async forces a run.

    autosyncasync
  • 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).

    0

Responses

200
The dry-run plan (the rendered content of the first targets).
Show schema

ActionRunResponse

  • dataone ofrequired
    0
    + Show child fields

    One of

    with action

    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
    • resultanyrequired

      The target's result, or null when it was skipped or failed.

      0

    with plan

    • planPlanrequired

      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).

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0
202
The action was created: pending_approval until a person approves it in the app (reviewed by default), then queued and run.
Show schema

ActionResponse

  • dataobjectrequired
    0
    + Show child fields
    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0

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

actions.notify.slack

Post a Slack message about each contact.

post/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 with Idempotent-Replayed: true. Results over 64 KB are not stored; their replay is 409 IDEMPOTENCY_RESULT_UNAVAILABLE with details.originalRequestId (check state with a GET). Same key while the first request runs: 409 IDEMPOTENCY_IN_PROGRESS with Retry-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: a 402 INSUFFICIENT_CREDITS replays 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 field dryRun: true (action operations).

  • Owlbound-Planheaderstring

    A planId from a dry run: same as the body field planId (action operations). A different body planId is 400.

    pattern ^plan_[0-9a-f]{48}$

Request body (required) · application/json

NotifySlackRequest

  • targetone ofrequired
    0
    + Show child fields
  • inputobjectrequired
    0
    + Show 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]+$
    • connectorIdstring

      A Slack connector of the workspace; without one, the workspace's Slack app install.

      0
    • requestApprovalboolean

      Ask a person even when the key's approval mode would not.

      0
  • dryRunboolean

    Same as the Owlbound-Dry-Run header.

    0
  • planIdstring

    Apply this dry run's plan (same as Owlbound-Plan). Not with dryRun: 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; sync forces inline (422 ACTION_NOT_SYNC when it cannot); async forces a run.

    autosyncasync
  • 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).

    0

Responses

200
The dry-run plan (the first messages as they would post).
Show schema

ActionRunResponse

  • dataone ofrequired
    0
    + Show child fields

    One of

    with action

    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
    • resultanyrequired

      The target's result, or null when it was skipped or failed.

      0

    with plan

    • planPlanrequired

      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).

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0
202
The action was created: pending_approval until a person approves it in the app (reviewed by default), then queued and run.
Show schema

ActionResponse

  • dataobjectrequired
    0
    + Show child fields
    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0

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.

actions.notify.google_chat

Post a Google Chat message about each contact.

post/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 with Idempotent-Replayed: true. Results over 64 KB are not stored; their replay is 409 IDEMPOTENCY_RESULT_UNAVAILABLE with details.originalRequestId (check state with a GET). Same key while the first request runs: 409 IDEMPOTENCY_IN_PROGRESS with Retry-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: a 402 INSUFFICIENT_CREDITS replays 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 field dryRun: true (action operations).

  • Owlbound-Planheaderstring

    A planId from a dry run: same as the body field planId (action operations). A different body planId is 400.

    pattern ^plan_[0-9a-f]{48}$

Request body (required) · application/json

NotifyGoogleChatRequest

  • targetone ofrequired
    0
    + Show child fields
  • inputobjectrequired
    0
    + Show child fields
    • connectorIdstringrequired

      A Google Chat connector of the workspace.

      0
    • 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
    • requestApprovalboolean

      Ask a person even when the key's approval mode would not.

      0
  • dryRunboolean

    Same as the Owlbound-Dry-Run header.

    0
  • planIdstring

    Apply this dry run's plan (same as Owlbound-Plan). Not with dryRun: 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; sync forces inline (422 ACTION_NOT_SYNC when it cannot); async forces a run.

    autosyncasync
  • 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).

    0

Responses

200
The dry-run plan (the first messages as they would post).
Show schema

ActionRunResponse

  • dataone ofrequired
    0
    + Show child fields

    One of

    with action

    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
    • resultanyrequired

      The target's result, or null when it was skipped or failed.

      0

    with plan

    • planPlanrequired

      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).

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0
202
The action was created: pending_approval until a person approves it in the app (reviewed by default), then queued and run.
Show schema

ActionResponse

  • dataobjectrequired
    0
    + Show child fields
    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0

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

actions.sequences.enroll

Enroll contacts into an existing sequence (reviewed by a person first).

post/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 with Idempotent-Replayed: true. Results over 64 KB are not stored; their replay is 409 IDEMPOTENCY_RESULT_UNAVAILABLE with details.originalRequestId (check state with a GET). Same key while the first request runs: 409 IDEMPOTENCY_IN_PROGRESS with Retry-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: a 402 INSUFFICIENT_CREDITS replays 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 field dryRun: true (action operations).

  • Owlbound-Planheaderstring

    A planId from a dry run: same as the body field planId (action operations). A different body planId is 400.

    pattern ^plan_[0-9a-f]{48}$

Request body (required) · application/json

SequenceEnrollRequest

  • targetone ofrequired
    0
    + Show child fields
  • inputobjectrequired
    0
    + Show child fields
    • sequenceIdstringrequired

      An existing sequence of this workspace.

      0
    • requestApprovalboolean

      Ask a person even when the key's approval mode would not.

      0
  • identityIdstring | null

    Optional; when given it must be the sequence's sending identity.

    0
  • dryRunboolean

    Same as the Owlbound-Dry-Run header.

    0
  • planIdstring

    Apply this dry run's plan (same as Owlbound-Plan). Not with dryRun: 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; sync forces inline (422 ACTION_NOT_SYNC when it cannot); async forces a run.

    autosyncasync
  • 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).

    0

Responses

200
The dry-run plan (who would be enrolled, who skipped and why).
Show schema

ActionRunResponse

  • dataone ofrequired
    0
    + Show child fields

    One of

    with action

    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
    • resultanyrequired

      The target's result, or null when it was skipped or failed.

      0

    with plan

    • planPlanrequired

      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).

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0
202
The action was created: pending_approval until a person approves it in the app, then queued and run.
Show schema

ActionResponse

  • dataobjectrequired
    0
    + Show child fields
    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0

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.

actions.sequences.pause

Pause contacts' enrollments in a sequence.

post/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 with Idempotent-Replayed: true. Results over 64 KB are not stored; their replay is 409 IDEMPOTENCY_RESULT_UNAVAILABLE with details.originalRequestId (check state with a GET). Same key while the first request runs: 409 IDEMPOTENCY_IN_PROGRESS with Retry-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: a 402 INSUFFICIENT_CREDITS replays 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 field dryRun: true (action operations).

  • Owlbound-Planheaderstring

    A planId from a dry run: same as the body field planId (action operations). A different body planId is 400.

    pattern ^plan_[0-9a-f]{48}$

Request body (required) · application/json

SequenceStateRequest

  • targetone ofrequired
    0
    + Show child fields
  • inputobjectrequired
    0
    + Show child fields
    • sequenceIdstringrequired

      A sequence of this workspace.

      0
    • requestApprovalboolean

      Resume only; ask a person even when the key's approval mode would not.

      0
  • identityIdstring | null

    Resume only; when given it must be the sequence's sending identity.

    0
  • dryRunboolean

    Same as the Owlbound-Dry-Run header.

    0
  • planIdstring

    Apply this dry run's plan (same as Owlbound-Plan). Not with dryRun: 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; sync forces inline (422 ACTION_NOT_SYNC when it cannot); async forces a run.

    autosyncasync
  • 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).

    0

Responses

200
The finished action and its result (one target), or the dry-run plan.
Show schema

ActionRunResponse

  • dataone ofrequired
    0
    + Show child fields

    One of

    with action

    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
    • resultanyrequired

      The target's result, or null when it was skipped or failed.

      0

    with plan

    • planPlanrequired

      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).

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0
202
An async run was created (queued); poll it or read the event feed.
Show schema

ActionResponse

  • dataobjectrequired
    0
    + Show child fields
    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0

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.

actions.sequences.stop

Stop contacts' enrollments in a sequence.

post/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 with Idempotent-Replayed: true. Results over 64 KB are not stored; their replay is 409 IDEMPOTENCY_RESULT_UNAVAILABLE with details.originalRequestId (check state with a GET). Same key while the first request runs: 409 IDEMPOTENCY_IN_PROGRESS with Retry-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: a 402 INSUFFICIENT_CREDITS replays 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 field dryRun: true (action operations).

  • Owlbound-Planheaderstring

    A planId from a dry run: same as the body field planId (action operations). A different body planId is 400.

    pattern ^plan_[0-9a-f]{48}$

Request body (required) · application/json

SequenceStateRequest

  • targetone ofrequired
    0
    + Show child fields
  • inputobjectrequired
    0
    + Show child fields
    • sequenceIdstringrequired

      A sequence of this workspace.

      0
    • requestApprovalboolean

      Resume only; ask a person even when the key's approval mode would not.

      0
  • identityIdstring | null

    Resume only; when given it must be the sequence's sending identity.

    0
  • dryRunboolean

    Same as the Owlbound-Dry-Run header.

    0
  • planIdstring

    Apply this dry run's plan (same as Owlbound-Plan). Not with dryRun: 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; sync forces inline (422 ACTION_NOT_SYNC when it cannot); async forces a run.

    autosyncasync
  • 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).

    0

Responses

200
The finished action and its result (one target), or the dry-run plan.
Show schema

ActionRunResponse

  • dataone ofrequired
    0
    + Show child fields

    One of

    with action

    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
    • resultanyrequired

      The target's result, or null when it was skipped or failed.

      0

    with plan

    • planPlanrequired

      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).

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0
202
An async run was created (queued); poll it or read the event feed.
Show schema

ActionResponse

  • dataobjectrequired
    0
    + Show child fields
    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0

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.

actions.sequences.resume

Resume paused enrollments in a sequence (reviewed by a person first).

post/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 with Idempotent-Replayed: true. Results over 64 KB are not stored; their replay is 409 IDEMPOTENCY_RESULT_UNAVAILABLE with details.originalRequestId (check state with a GET). Same key while the first request runs: 409 IDEMPOTENCY_IN_PROGRESS with Retry-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: a 402 INSUFFICIENT_CREDITS replays 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 field dryRun: true (action operations).

  • Owlbound-Planheaderstring

    A planId from a dry run: same as the body field planId (action operations). A different body planId is 400.

    pattern ^plan_[0-9a-f]{48}$

Request body (required) · application/json

SequenceStateRequest

  • targetone ofrequired
    0
    + Show child fields
  • inputobjectrequired
    0
    + Show child fields
    • sequenceIdstringrequired

      A sequence of this workspace.

      0
    • requestApprovalboolean

      Resume only; ask a person even when the key's approval mode would not.

      0
  • identityIdstring | null

    Resume only; when given it must be the sequence's sending identity.

    0
  • dryRunboolean

    Same as the Owlbound-Dry-Run header.

    0
  • planIdstring

    Apply this dry run's plan (same as Owlbound-Plan). Not with dryRun: 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; sync forces inline (422 ACTION_NOT_SYNC when it cannot); async forces a run.

    autosyncasync
  • 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).

    0

Responses

200
The finished action and its result (one target), or the dry-run plan.
Show schema

ActionRunResponse

  • dataone ofrequired
    0
    + Show child fields

    One of

    with action

    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
    • resultanyrequired

      The target's result, or null when it was skipped or failed.

      0

    with plan

    • planPlanrequired

      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).

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0
202
The action was created: pending_approval until a person approves it in the app, then queued and run.
Show schema

ActionResponse

  • dataobjectrequired
    0
    + Show child fields
    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0

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

actions.plays.trigger

Start a deployed play for contacts (reviewed by a person first).

post/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 with Idempotent-Replayed: true. Results over 64 KB are not stored; their replay is 409 IDEMPOTENCY_RESULT_UNAVAILABLE with details.originalRequestId (check state with a GET). Same key while the first request runs: 409 IDEMPOTENCY_IN_PROGRESS with Retry-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: a 402 INSUFFICIENT_CREDITS replays 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 field dryRun: true (action operations).

  • Owlbound-Planheaderstring

    A planId from a dry run: same as the body field planId (action operations). A different body planId is 400.

    pattern ^plan_[0-9a-f]{48}$

Request body (required) · application/json

PlayTriggerRequest

  • targetone ofrequired
    0
    + Show child fields
  • inputobjectrequired
    0
    + Show child fields
    • workflowIdstringrequired

      A deployed, active play of this workspace.

      0
    • requestApprovalboolean

      Ask a person even when the key's approval mode would not.

      0
  • identityIdstring | null

    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.

    0
  • 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
  • dryRunboolean

    Same as the Owlbound-Dry-Run header.

    0
  • planIdstring

    Apply this dry run's plan (same as Owlbound-Plan). Not with dryRun: 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; sync forces inline (422 ACTION_NOT_SYNC when it cannot); async forces a run.

    autosyncasync
  • 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).

    0

Responses

200
The dry-run plan (the play version, its per-contact maximum, who would be started and who skipped).
Show schema

ActionRunResponse

  • dataone ofrequired
    0
    + Show child fields

    One of

    with action

    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
    • resultanyrequired

      The target's result, or null when it was skipped or failed.

      0

    with plan

    • planPlanrequired

      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).

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0
202
The 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

ActionResponse

  • dataobjectrequired
    0
    + Show child fields
    • actionActionrequired

      One action run. Read it again with GET /api/v1/actions/{id}.

      0
  • metaResponseMetarequired

    Metadata on every success response.

    0

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"required
    0
  • contactIdstringrequired
    0
ActionTargetContactsobject
  • kind"contacts"required
    0
  • 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"required
    0
  • tableIdstringrequired
    0
  • 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).

  • targetEventsboolean
    default false
  • webhookboolean
    0
Actionobject

One action run. Read it again with GET /api/v1/actions/{id}.

  • idstringrequired
    0
  • kindstringrequired

    The operation id.

    0
  • classstringrequired

    Safety class: decides scope, approval, caps and enforcement.

    readwritespendexternal_writesend
  • statusstringrequired
    pending_approvalqueuedrunningsucceededfailedrejectedcanceledexpiredpartial
  • identityIdstring | nullrequired
    0
  • approvalActionApproval | nullrequired
    0
  • costobjectrequired
    0
    + Show child fields
    • estimatednumberrequired
      0
    • chargednumberrequired
      0
    • reservationIdstring | nullrequired
      0
  • countsActionCountsrequired
    0
  • targetobjectrequired
    0
    + Show child fields
    • kindstringrequired
      0
    • tableIdstring
      0
    • viewIdstring
      0
    • columnIdstring

      The send column this action runs (tables.rows.push target column).

      0
  • outputobject | nullrequired
    0
  • resultanyrequired

    The single target's result (sync), or null; bulk results are per target.

    0
  • errorActionErrorrequired
    0
  • actorActionActorrequired

    Who started the action. agent is an LLM-driven caller (the in-app agent, MCP) acting for the key or member in onBehalfOf.

    0
  • createdAtnumberrequired
    0
  • updatedAtnumberrequired
    0
ActionApprovalobject
  • modestringrequired
    nonealwaysover_threshold
  • urlstring | nullrequired
    0
  • decidedBystring | nullrequired
    0
  • decidedAtnumber | nullrequired
    0
  • expiresAtnumber | nullrequired
    0
ActionCountsobject
  • totalintegerrequired
    0
  • queuedintegerrequired
    0
  • scheduledintegerrequired
    0
  • runningintegerrequired
    0
  • succeededintegerrequired
    0
  • skippedintegerrequired
    0
  • failedintegerrequired
    0
  • canceledintegerrequired
    0
ActionErrorobject
  • codestringrequired
    0
  • messagestringrequired
    0
ActionActorobject

Who started the action. agent is an LLM-driven caller (the in-app agent, MCP) acting for the key or member in onBehalfOf.

  • kindstringrequired
    apiKeysessionagentjob
  • idstringrequired

    API key id, user id, agent run id or job id.

    0
  • onBehalfOfobject

    Agent only.

    0
    + Show child fields
    • kindstringrequired
      apiKeysession
    • idstringrequired
      0
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).

  • operationIdstringrequired
    0
  • classstringrequired

    Safety class: decides scope, approval, caps and enforcement.

    readwritespendexternal_writesend
  • identityIdstring | nullrequired
    0
  • targetsobjectrequired
    0
    + Show child fields
    • totalintegerrequired
      0
    • wouldRunintegerrequired
      0
    • wouldSkiparray of objectrequired
      0
      + Show child fields
      • reasonstringrequired
        0
      • countintegerrequired
        0
      • samplearray of stringrequired
        0
  • creditsobjectrequired
    0
    + Show child fields
    • estimatedMinnumberrequired
      0
    • estimatedMaxnumberrequired
      0
    • availablenumberrequired
      0
    • capRemainingnumber | nullrequired
      0
  • quotaobject | nullrequired

    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). bucket is the daily limit's key, identityRemainingToday what the identity's account has left today, keyRemainingToday the key's, user's or agent's daily sends left (null: uncapped), wouldDefer the targets that would wait for a later day, firstSlotAt / lastSlotAt when the first and last of them would go (ms; null past 7 days). Null for other operations.

    0
    + Show child fields
    • bucketstringrequired
      0
    • identityRemainingTodaynumberrequired
      0
    • keyRemainingTodaynumber | nullrequired
      0
    • wouldDefernumberrequired
      0
    • firstSlotAtnumber | nullrequired
      0
    • lastSlotAtnumber | nullrequired
      0
  • approvalobjectrequired
    0
    + Show child fields
    • requiredbooleanrequired
      0
    • reasonstring | nullrequired
      key_modeoperation_floorover_thresholdagentrequestedidentity_owner
  • writesobjectrequired
    0
  • previewarray of anyrequired

    The first targets that would run (at most 5).

    0
  • planIdstringrequired

    Valid until expiresAt, for the principal that made the dry run only.

    pattern ^plan_[0-9a-f]{48}$
  • expiresAtnumberrequired

    When the plan expires (epoch milliseconds).

    0
ResponseMetaobject

Metadata on every success response.

  • requestIdstringrequired
    0
  • creditsCreditsMetarequired
    0
  • Cursor pagination. hasMore is the only end signal; pages may be short.

    0
  • dryRunboolean

    True only on a dry-run response (action operations).

    0
CreditsMetaobject
  • usednumberrequired

    Credits charged by this request. Reads are free.

    0
  • balancenumber
    0
  • estimatednumber

    Dry run only (action operations).

    0
  • availablenumber

    Dry run only (action operations).

    0
  • reservationIdstring | null

    Async operations only; the job settles this reservation.

    0
PageMetaobject

Cursor pagination. hasMore is the only end signal; pages may be short.

  • nextCursorstring | nullrequired
    0
  • hasMorebooleanrequired
    0
  • limitintegerrequired
    min 1max 200

← All operations