> ## 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.

# Live view

> Mint a short-lived embed token and iframe the running browser. Read-only, ten-minute expiry, one checkout per token.

You can watch the browser work. `progressItems` tells you what happened; the live view shows
you the page it happened on — which is the difference between "the agent failed at the
payment step" and "oh, there is a full-screen coupon modal over the pay button".

It is a live JPEG stream of the running browser, delivered over Server-Sent Events, rendered
into a self-contained page you can drop in an `<iframe>`.

## Mint a token

```bash theme={null}
curl -sX POST https://api.farthing.ai/v1/checkouts/$FARTHING_ID/embed-token \
  -H "authorization: Bearer $FARTHING_KEY"
```

```json Response (200) theme={null}
{
  "token": "eyJhbGciOiJIUzI1NiJ9.eyJjaGVja291dElkIjoiMWYyZTNkNGMtNWI2YS00NzkwLTgxMjMtYWJjZGVmMDEyMzQ1IiwiZXhwIjoxNzg1MzE4Mjg1fQ.Qy8xVnZ0S2c",
  "url": "https://api.farthing.ai/embed/1f2e3d4c-5b6a-4790-8123-abcdef012345?token=eyJhbGciOiJIUzI1NiJ9...",
  "expiresAt": "2026-07-29T09:24:45.000Z"
}
```

Then embed `url` directly:

```html theme={null}
<iframe
  src="https://api.farthing.ai/embed/1f2e3d4c-...?token=eyJhbGciOiJIUzI1NiJ9..."
  style="width: 100%; aspect-ratio: 16 / 10; border: 0; border-radius: 8px"
  title="Live checkout"
></iframe>
```

That is the whole integration. The page inside the frame handles the stream, renders frames,
and shows a small status chip (`connecting…`, `live`, `ended: succeeded`, `disconnected`).

## The token is the access control

The checkout UUID alone grants nothing. The token is a signed JWT pinned to one
`checkoutId` claim and checked on every request to the embed routes — so a token minted for
one run cannot watch another, and knowing a checkout id is not enough to watch it.

This is not incidental. A live browser stream that authorises on an id you might have logged,
copied into a ticket, or leaked in a URL is a hole; requiring a separately minted,
short-lived, single-checkout credential closes it.

<ResponseField name="TTL" type="10 minutes">
  Fixed, not configurable. `expiresAt` is authoritative. After it passes both embed routes
  return `401 Invalid or expired embed token` as plain text.
</ResponseField>

<ResponseField name="Scope" type="one checkout">
  A token whose `checkoutId` claim does not match the path segment is rejected, even if it is
  otherwise valid and unexpired.
</ResponseField>

<ResponseField name="Renewal" type="mint a new one">
  There is no refresh. A run outlasting the window needs a fresh
  `POST /v1/checkouts/{id}/embed-token` and a reload of the iframe `src`. Minting is cheap
  and unlimited — for a long run, re-mint on a timer at roughly half the TTL.
</ResponseField>

<Warning>
  The token appears in the iframe URL's query string, so it lands in browser history and any
  `Referer` your page sends. That is survivable because the token is short-lived, read-only,
  and scoped to one checkout — but do not treat it as harmless, do not log it, and do not
  mint tokens ahead of when you need them.
</Warning>

## It is read-only

The stream carries frames out. Nothing goes in.

`permissions` is `["read"]` on the checkout's `browser` object because that is the only mode
that exists. There is no click-through, no keyboard forwarding, no takeover. A viewer cannot
touch the merchant's page, and a leaked token cannot be used to drive a purchase.

If you want to influence the run, that is what [user actions](/user-actions) are for. The
live view is a window, not a remote desktop.

## Stream lifecycle

The embed page consumes `GET /embed/{checkoutId}/stream?token=…`, an SSE endpoint with four
event types:

| Event    | Data                                               | Meaning                                                                |
| -------- | -------------------------------------------------- | ---------------------------------------------------------------------- |
| `status` | the current status                                 | Sent once, immediately on connect.                                     |
| `frame`  | base64 JPEG                                        | One frame of the live view. Render as `data:image/jpeg;base64,<data>`. |
| `ping`   | empty                                              | Heartbeat, roughly every 5 seconds.                                    |
| `ended`  | terminal status, or `browser_unavailable` / `gone` | The stream is over; the client closes.                                 |

Connecting to a checkout that is already terminal, or that has no browser session, emits
`status` then `ended` immediately rather than hanging. The viewer's own connection uses a
separate observation channel, so watching never disturbs the run driving the page.

## Gotchas

<AccordionGroup>
  <Accordion title="browser.embedTokenUrl is a pointer, not a viewable URL">
    `GET /v1/checkouts/{id}` includes:

    ```json theme={null}
    "browser": { "embedTokenUrl": "/v1/checkouts/{id}/embed-token", "permissions": ["read"] }
    ```

    Its presence means a browser session exists and there is something to watch. It is not
    itself viewable — `POST` to it to mint a token, and use the `url` that comes back.
  </Accordion>

  <Accordion title="There is nothing to watch before the browser starts">
    A `queued` checkout has no browser session. The token mints fine, but the stream ends
    immediately. Wait for `status: "running"` — or for `browser` to appear on the checkout —
    before showing the frame.
  </Accordion>

  <Accordion title="The stream ends when the checkout does">
    On a terminal status the stream sends `ended` and closes. There is no replay: the live
    view is live only. For after-the-fact review, `progressItems` and
    `receipt.evidence.url` are what you have.
  </Accordion>

  <Accordion title="Frames are JPEG, not video">
    Bandwidth scales with how much the page is changing. It is fine for an operator console
    watching one or two runs; it is not designed for a wall of fifty concurrent streams.
  </Accordion>
</AccordionGroup>
