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

> ## Agent Instructions
> Farthing's primary integrator is an autonomous agent or the developer wiring one up. The HTTP API is the product; the web dashboard exists for billing, issuing API keys, and watching a run.
> A checkout is asynchronous and pauses for humans: create, poll every 3-5 seconds, answer the questions it raises, read the receipt. A run takes 2-3 minutes and can park for up to 10 minutes on one question. Never present it as a synchronous request/response call.
> Branch on pendingUserAction.key. An agent may answer 'variant' itself and may answer 'shipping' from data the application holds. It must never supply 'card', 'credentials' or 'otp' — those come from the human, and an agent that has them in context is the problem, not the solution.
> Base URL is https://api.farthing.ai and auth is Authorization: Bearer ck_test_... or ck_live_.... API keys carry scopes; give an agent the narrowest set that does its job. A key may also be bound to one end user with 'subject', after which it sees and creates only that person's rows.
> POST /v1/checkouts takes an Idempotency-Key header. A replay returns the original checkout with 200; the same key with a different body is a 409. Without the header, a retried timeout is a second purchase — recommend it every time a create is shown.
> Pricing: sandbox is free with no card; paid plans are Personal $9, Business $49, Scale $249 per month, plus a flat $1.00 per COMPLETED checkout on every tier. Failed and cancelled checkouts are never billed. Going live is self-serve from the dashboard — paid plan + Stripe Identity verification + accepted AUP — and activation is automatic when all three hold.
> Every example in these docs is a shape the API actually produces. Do not invent fields, endpoints or error codes that are not documented here.

# User actions

> Submit or decline a pending user action.

One endpoint. It resolves the checkout's currently pending action and resumes the run.

For the concepts — action keys, schema rendering, TTLs, what happens to the values you send —
see [User actions](/user-actions).

## Resolve an action

```http theme={null}
POST /v1/checkouts/{id}/actions/{actionId}
```

Success: `200`.

`actionId` is `pendingUserAction.id` from the checkout object. It must belong to that
checkout and still be `pending`.

### Body

A discriminated union on `action`. Nothing else validates.

<Tabs>
  <Tab title="submit">
    ```json theme={null}
    { "action": "submit", "values": { "Size": "M", "Color": "Black" } }
    ```

    <ParamField body="action" type="&#x22;submit&#x22;" required />

    <ParamField body="values" type="object" required>
      The object described by `pendingUserAction.responseSchema`. Send exactly the keys in
      `properties` — the schema is `additionalProperties: false`, so extra keys are
      meaningless.

      Values should be strings. The API accepts any JSON here, but the engines read them as
      strings.
    </ParamField>

    ```bash theme={null}
    curl -sX POST \
      https://api.farthing.ai/v1/checkouts/$FARTHING_ID/actions/$ACTION_ID \
      -H "authorization: Bearer $FARTHING_KEY" \
      -H 'content-type: application/json' \
      -d '{
        "action": "submit",
        "values": {
          "card_number": "4111111111111111",
          "card_expiry": "12 / 30",
          "card_cvc": "123"
        }
      }'
    ```
  </Tab>

  <Tab title="decline">
    ```json theme={null}
    { "action": "decline", "reason": "buyer changed their mind" }
    ```

    <ParamField body="action" type="&#x22;decline&#x22;" required />

    <ParamField body="reason" type="string">
      Optional free text, stored on the action for your own audit. Not sent to the merchant.
    </ParamField>

    Declining fails the checkout with `failure.reason: "user_cancelled"` and tears down the
    browser.

    ```bash theme={null}
    curl -sX POST \
      https://api.farthing.ai/v1/checkouts/$FARTHING_ID/actions/$ACTION_ID \
      -H "authorization: Bearer $FARTHING_KEY" \
      -H 'content-type: application/json' \
      -d '{ "action": "decline", "reason": "no operator available" }'
    ```
  </Tab>
</Tabs>

### Response

`200` with the **full checkout object** — the same shape as `GET /v1/checkouts/{id}`, taken
after the update. `pendingUserAction` is gone.

The status returns to `running` on a `submit` — the run has resumed. On a `decline` the
checkout goes terminal with `failure.reason: "user_cancelled"`.

```json theme={null}
{
  "id": "1f2e3d4c-5b6a-4790-8123-abcdef012345",
  "status": "awaiting_user_action",
  "target": {
    "kind": "direct_url",
    "url": "https://example-store.com/products/widget",
    "request": "buy this in medium, cheapest delivery"
  },
  "constraints": { "maxCost": { "amount": "60.00", "currency": "USD" } },
  "buyerProfileId": "b3a1c0de-2f77-45a0-9c8e-6f4b2a1d9e30",
  "metadata": { "orderRef": "acme-4412" },
  "browser": { "embedTokenUrl": "/v1/checkouts/9a2f1c84-6b70-4d3e-8f21-5c0ab7e91d55/embed-token", "permissions": ["read"] },
  "progressItems": [
    { "kind": "intent_started", "label": "Started checkout", "data": {}, "at": "2026-07-29T09:14:31.203Z", "sequence": 1 },
    { "kind": "step", "label": "Browser session started", "data": {}, "at": "2026-07-29T09:14:39.502Z", "sequence": 2 },
    { "kind": "step", "label": "Checkout phase: product", "data": { "url": "https://example-store.com/products/widget" }, "at": "2026-07-29T09:14:44.117Z", "sequence": 3 },
    { "kind": "user_action", "label": "This product comes in multiple options — pick one to continue.", "data": { "key": "variant" }, "at": "2026-07-29T09:14:45.630Z", "sequence": 4 }
  ],
  "createdAt": "2026-07-29T09:14:31.190Z",
  "updatedAt": "2026-07-29T09:14:45.622Z"
}
```

### Errors

| Status | Message                                             | Cause                                                  |
| ------ | --------------------------------------------------- | ------------------------------------------------------ |
| `400`  | `Invalid action body` (with `issues`)               | Body matched neither the submit nor the decline shape. |
| `404`  | `Checkout not found`                                | Unknown checkout, or another tenant's.                 |
| `404`  | `Action not found`                                  | No action with that id **on this checkout**.           |
| `409`  | `Action already submitted` / `declined` / `expired` | Already resolved.                                      |

<Tip>
  The `409` is your idempotency signal. If your own submit retries and comes back
  `Action already submitted`, the first attempt landed — poll the checkout instead of treating
  it as a failure.
</Tip>

## What happens next

<Steps>
  <Step title="A redacted copy is written to the action record">
    Card-shaped field names — `card`, `cc-num`, `pan`, `cvc`, `cvv`, `security code`,
    `expir` — become `"[redacted]"`. Everything else is stored as sent.
  </Step>

  <Step title="The raw values go to a transient, delete-on-read store">
    Keyed by action id, with a short TTL. The only place raw values exist.
  </Step>

  <Step title="The resume event carries the action id and the outcome only">
    Never the values. The event bus persists payloads durably; values must not be in them.
  </Step>

  <Step title="The run resumes and consumes the values">
    Read inside the one step that types them, deleted once that step completes.
  </Step>
</Steps>

Submitted values are not returned by any endpoint. Full detail in
[What happens to submitted values](/user-actions#what-happens-to-submitted-values).
