# Confirmations on the OpenAI-compatible API

> How a risky action (a send, a spend, a delete) waits for confirmation over /v1/responses and /v1/chat/completions, how to confirm it, and why stateless calls can't.

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.

## The flow

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](/openai-api/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.

```python
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."
```

## Spend actions work the same way

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:

```python
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.

## Bulk operations confirm once

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 tool** — `delete_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:

```python
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.

## Stateless calls cannot confirm

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

## No separate step-up factor

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](/getting-started/authentication/#security-model-no-step-up-factor):
treat the key like a password, and rely on rate limits and spend thresholds as
the backstop.