> ## Documentation Index
> Fetch the complete documentation index at: https://docs.goosybear.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Actions that spend credits

> Why an action that costs money takes two calls, the confirmation id that joins them, and a worked round-trip you can follow line by line.

**An action that spends credits will not run in a single call.** The first call
tells you what it would do and runs nothing; the second, carrying a confirmation
id back, is what runs it. There is no single-call version, and no header or
argument turns one on.

That is the same rule the product follows on screen — nothing spends on an
assistant's own judgement ([How approvals work](/how-approvals-work)). Over the
API, the confirmation id is what a person's *yes* looks like.

Today the action on this shape is **`images.generate`**. Any action that spends
takes the same two-call form; the [reference](/api-reference/overview) says so on
each one.

## The round-trip

These two calls are one worked example, run start to finish. They assume your
key is in an environment variable — see [Quickstart](/quickstart) if you have
not made one yet.

```bash theme={null}
export GOOSY_API_KEY="your-key"
export GOOSY_WORKSPACE="your-workspace"
```

### Call 1 — ask what it would do

Send the request **without** `confirmation_id`.

```bash theme={null}
curl --request POST \
  --url "https://app.goosybear.ai/api/v1/tools/images.generate" \
  --header "Authorization: Bearer $GOOSY_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{
    \"board\": \"$BOARD_ID\",
    \"brief\": \"An isometric blueprint concept card for the AI Foundations course\",
    \"count\": 2,
    \"shape\": \"1:1\",
    \"workspace\": \"$GOOSY_WORKSPACE\"
  }"
```

```json theme={null}
{
  "ok": true,
  "status": "confirmation_required",
  "confirmation_id": "11c3d6cd-aa5f-4343-ab4c-409c643841c9",
  "request_summary": "2 images at 1:1: An isometric blueprint concept card for the AI Foundations course",
  "count": 2,
  "aspect_ratio": "1:1",
  "board_id": "…",
  "run_id": null,
  "workspace": "…",
  "working_in": { "label": "working in: …", "note": "this call only", "source": "call-override" }
}
```

Read three fields:

* **`status` is `confirmation_required`** and **`run_id` is `null`** — nothing
  ran.
* **`request_summary`** is one sentence describing what would be made. It is
  written for a person to read, so it is the thing to put in front of whoever is
  approving.
* **`confirmation_id`** is the token for the request you were just shown.

### Call 2 — confirm it

Send the same request again with the id added.

```bash theme={null}
curl --request POST \
  --url "https://app.goosybear.ai/api/v1/tools/images.generate" \
  --header "Authorization: Bearer $GOOSY_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{
    \"board\": \"$BOARD_ID\",
    \"brief\": \"An isometric blueprint concept card for the AI Foundations course\",
    \"count\": 2,
    \"shape\": \"1:1\",
    \"workspace\": \"$GOOSY_WORKSPACE\",
    \"confirmation_id\": \"11c3d6cd-aa5f-4343-ab4c-409c643841c9\"
  }"
```

```json theme={null}
{
  "ok": true,
  "status": "dispatched",
  "confirmation_id": null,
  "request_summary": null,
  "count": 2,
  "aspect_ratio": "1:1",
  "board_id": "…",
  "run_id": "run_06g4fs29ea55m7dc6n7vjj0201",
  "workspace": "…"
}
```

**`status` is `dispatched` and `run_id` names the run.** The work is under way
and the images land on the board you named.

## What the id is bound to

The confirmation id stands for **the request you were shown**, not for the
action in general. Three properties follow, and they are worth knowing before
you build against it.

**It is single use.** Send a consumed id again and the call is declined:

```json theme={null}
{
  "ok": false,
  "code": "confirmation_invalid",
  "message": "That confirmation is not usable — it was never issued here, has already been used, or expired (they last about 30 minutes). Call again with no confirmation_id to get a fresh one."
}
```

**It expires.** About thirty minutes. The same `confirmation_invalid` answer
covers an expired id, an id that was never issued, and one that belongs
somewhere else — deliberately one answer, so the code cannot be used to probe
for valid ids.

**Changing the arguments on call 2 does not change the run.** If you confirm
with a larger `count` than you proposed, **what runs is what you confirmed** —
the reply comes back carrying the original numbers. The id carries the approved
request with it, so the second call cannot quietly enlarge the first.

<Warning>
  Do not treat call 2's arguments as the source of truth. Build your integration
  so the thing a person approved is the thing that is sent — and check the
  `count` and `aspect_ratio` on the `dispatched` reply if you need to be sure
  what ran.
</Warning>

## Recovering from a refusal

Everything on this page answers `200`. **Branch on `ok`, never on the status
code** — a declined action is a successful HTTP response carrying a reason.

| `code`                 | What happened                                                  | What to do                                                                                                 |
| ---------------------- | -------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `confirmation_invalid` | The id was used, expired, or is not one this workspace issued. | Call again with no `confirmation_id` and confirm the fresh one.                                            |
| `board_not_found`      | That board is not in this workspace.                           | Check the board belongs to the workspace the call ran in — the `working_in` field says which one that was. |
| `workspace_ambiguous`  | Your key reaches several workspaces and none was chosen.       | Pass `workspace`, or set a default. [Choosing a workspace](/api/workspace-context).                        |
| `invalid_arguments`    | The arguments did not match what the action takes.             | The reply names the fields.                                                                                |

The full list is on [Errors and refusals](/api/errors-and-refusals).

## A pattern that works

<Steps>
  <Step title="Propose, and stop">
    Call without `confirmation_id`. Store the `confirmation_id` and the
    `request_summary` together.
  </Step>

  <Step title="Put the summary in front of a person">
    `request_summary` is one plain sentence and is written for exactly this.
    Show it; do not re-describe the request yourself.
  </Step>

  <Step title="Confirm within the window">
    Send the id back on the second call. If more than about half an hour has
    passed, propose again rather than retrying — an expired id cannot be
    revived.
  </Step>

  <Step title="Record what ran">
    Log the `run_id` and the `working_in` block beside it. It is the fastest way
    to catch an integration quietly spending in the wrong workspace.
  </Step>
</Steps>

## Checking the balance first

`credits.balance` takes no workspace — credits belong to the account, not to a
single workspace — and is worth calling before an unattended job spends.

```bash theme={null}
curl --request POST \
  --url "https://app.goosybear.ai/api/v1/tools/credits.balance" \
  --header "Authorization: Bearer $GOOSY_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{}'
```

```json theme={null}
{ "ok": true, "balance": 0 }
```

A job that stops for want of credits is easier to prevent than to notice. See
[Recipes](/api/recipes) for the other calls worth wiring into an integration.
