Skip to content

Confirmations

Risky tools — spend, mass-message, destructive, or live-deploy actions — never fire unconfirmed, whether you talk to Octo through the dashboard, email, or the API. Over HTTP, this is the same “YES machine” used everywhere else, exposed as a two-call exchange.

  1. You ask for something consequential in a stateful conversation (i.e. using previous_response_id on /v1/responses, or conversation on /v1/chat/completions — see Statefulness).
  2. Octo replies with assistant text asking you to confirm, quoting a short action code — e.g. “This will send 412 SMS. Reply YES 4821 to proceed.” On /v1/responses the code is also machine-readable at metadata.octo_pending_action.
  3. You reply “YES 4821” in the next call of the same conversation. Octo executes the parked action and returns the result.
a = client.responses.create(
model="claude-sonnet-4-6",
input="Send the appointment reminder to all of list B.",
)
# a.output_text -> "This will text 412 people. Reply YES 4821 to send."
# a.metadata -> { "octo_pending_action": "4821" }
b = client.responses.create(
model="claude-sonnet-4-6",
input="YES 4821",
previous_response_id=a.id,
)
# b.output_text -> "Sent to 412 contacts."

Anything that spends money parks identically. Buying a phone number is a good example — Octo searches availability (a safe read), presents options priced in credits, then parks the purchase until you confirm:

a = client.responses.create(
model="claude-sonnet-4-6",
input="Buy us a Seattle (206) number for our voice agent.",
)
# a.output_text -> "Found (206) 555-1234 — 192 credits/month, voice + SMS.
# Buying charges a recurring monthly fee. Reply YES 2214 to confirm."
# a.metadata -> { "octo_pending_action": "2214" }
b = client.responses.create(
model="claude-sonnet-4-6",
input="YES 2214",
previous_response_id=a.id,
)
# b.output_text -> "Purchased +1 (206) 555-1234 — voice + SMS are ready."

Prices are quoted in credits (your workspace balance), never dollars. Only US and CA numbers can be bought this way; other countries require a one-time regulatory bundle, and Octo will link you to the in-app approval flow instead of attempting a charge.

When you ask Octo to delete or change many matching resources at once (“delete all closed conversations”, “clean up my Slack channels”, “cancel every running campaign”), it calls a single bulk tooldelete_conversations, delete_channels, delete_contacts, delete_campaigns, undeploy_agents, or cancel_campaigns — rather than looping a per-item tool. You get one confirmation that states the blast radius, not one per item:

a = client.responses.create(
model="claude-sonnet-4-6",
input="Delete all my closed conversations.",
)
# a.output_text -> "This will delete 232 conversations (e.g. \"Acme\", \"Globex\"…)
# and their messages. Reply YES 4821 to proceed."
b = client.responses.create(model="claude-sonnet-4-6", input="YES 4821", previous_response_id=a.id)
# b.output_text -> "Deleted 232 conversations."

Each bulk tool takes either a filter (e.g. status, channel, tag, older than a date) or an explicit list of ids. A few behaviors worth knowing:

  • Full-workspace wipes need a count handshake. A filter of { all: true } won’t run until you echo the live count back as confirm_count — a guard against accidentally clearing an entire resource type.
  • A confirmation can be drift-aborted. The count you were quoted is a snapshot. If the matching set has grown more than ~10% by the time you reply YES, Octo refuses and re-asks with the new number rather than deleting more than you saw.
  • Running campaigns are protected. delete_campaigns skips campaigns that are still running unless you explicitly include them; cancel them first.

A /v1/chat/completions call made without a conversation param has no thread to park a “YES” on — there’s nowhere for the confirmation to live between calls. So stateless calls run safe tools only: anything consequential is simply not offered. If you need Octo to be able to take risky actions, use a stateful conversation (conversation or previous_response_id).

There is no second factor over the API distinct from the key itself — the same API key that asks for a risky action is the key that confirms it. This is the same security model described in Authentication: treat the key like a password, and rely on rate limits and spend thresholds as the backstop.