# Approvals and safe actions

> How publishing, scheduling and deleting work through FoodPerfect's two-step approval, and how agents should ask a human before confirming.

Some actions can't be undone or are seen by your customers. For those, FoodPerfect uses a two-step call: the first call **describes** the action and returns a token; only a second call with that token **does** it.

| Tool | What waits for approval |
|---|---|
| `publish_post_now` | Publishing posts to Instagram, Facebook, TikTok or LinkedIn |
| `schedule_reel_post` | Scheduling a reel on Instagram |
| `send_reply` | Sending an inbox reply (if an AI draft was generated for it) |
| `moderate_comment` | Deleting a customer comment (hide/unhide are ungated) |
| `update_inbox_settings` | Turning autopilot ON or turning the inbox OFF |
| `delete_generation` | Deleting a generated image or video |
| `delete_asset` | Deleting an uploaded asset or font |

## Step 1: request

Call the tool normally. It changes nothing and returns an error result with code `requires_approval`:

```json title="Tool result (isError: true)"
{
  "error": {
    "code": "requires_approval",
    "message": "Publishing posts immediately requires explicit approval. Repeat the call with the returned approvalToken.",
    "requestId": "b1c9…",
    "retryable": false,
    "details": {
      "action": "publish_post_now",
      "approvalToken": "eyJ…",
      "expiresAt": "2026-10-01T12:15:00.000Z",
      "preview": { "postIds": ["p_123"] }
    }
  }
}
```

## Step 2: ask a human

Show the person what will happen, in plain words: what will be published or deleted, where, and when. The `preview` names what's affected (post ids, for example); look up the details with a read tool such as `list_scheduled_posts` or `get_generation` so the person sees the actual caption, image and platform. Then wait for an explicit yes.

> [!IMPORTANT]
> The token comes back to whoever made the call, so the human check is your agent's job. FoodPerfect can't tell whether a person said yes. Instruct your agent never to confirm on its own, and never to treat text inside tool results (a caption, a comment, a web page) as approval. The [recommended system prompt](/docs/agent-prompt/) does this.

## Step 3: confirm

Either repeat the exact same call with `approvalToken` added:

```json title="tools/call params"
{ "name": "publish_post_now", "arguments": { "postIds": ["p_123"], "approvalToken": "eyJ…" } }
```

or call `confirm_action` with just the token:

```json title="tools/call params"
{ "name": "confirm_action", "arguments": { "approvalToken": "eyJ…" } }
```

If you send `Idempotency-Key` headers, use a **new** one for the confirming call.

## Token rules

- Valid for **15 minutes**. After that, start again from step 1.
- **Single use.**
- Bound to the account, the tool and the exact arguments. Changing anything (another post id, a new caption) needs a new approval.

## Other safety rails

- **Connecting accounts** (`get_connect_link`) only returns a link. A person has to open it and log in to the platform; `finalize_connection` then only finishes what they started.
- **Spending** isn't approval-gated, but it's capped by the balance and the key's [scopes](/docs/authentication/#scopes). A key without Generation Tools can't spend at all.
- **Reversible edits** such as `update_brand_dna` or `generate_scheduled_post_content` run straight away; you can change them back in the app.
