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

# Buyer profiles

> Reusable identity and shipping details. Create once, reference by id on every checkout.

A buyer profile is the answer to "who is this order for and where does it go". The agent
reads it when it reaches the merchant's contact and shipping form, so you supply it once
instead of re-typing an address into every checkout.

Pass `buyerProfileId` when creating a checkout. Without one the agent has no address, and on
most merchants that is where the run stops.

## Create

```bash theme={null}
curl -sX POST https://api.farthing.ai/v1/buyer-profiles \
  -H "authorization: Bearer $FARTHING_KEY" \
  -H 'content-type: application/json' \
  -d '{
    "label": "ada-home",
    "name": { "first": "Ada", "last": "Lovelace" },
    "contact": { "email": "ada@example.com", "phone": "+15555550123" },
    "shipping": {
      "addressLines": ["1 Analytical Way", "Apt 4"],
      "locality": "Louisville",
      "administrativeAreaCode": "KY",
      "postalCode": "40202",
      "countryCode": "US"
    }
  }'
```

```json Response (201) theme={null}
{
  "id": "b3a1c0de-2f77-45a0-9c8e-6f4b2a1d9e30",
  "label": "ada-home",
  "subject": null,
  "name": { "first": "Ada", "last": "Lovelace" },
  "contact": { "email": "ada@example.com", "phone": "+15555550123" },
  "shipping": {
    "addressLines": ["1 Analytical Way", "Apt 4"],
    "locality": "Louisville",
    "administrativeAreaCode": "KY",
    "postalCode": "40202",
    "countryCode": "US"
  },
  "createdAt": "2026-07-29T09:12:04.881Z"
}
```

## Fields

Every field is optional — the API will happily store `{}` — but the agent can only fill what
you gave it. A field it does not recognise is a different matter: the body is strict, so a typo is
a `400` rather than a stored profile missing the thing you thought you set.

<ResponseField name="label" type="string">
  Yours, for your own bookkeeping. Never sent to a merchant.
</ResponseField>

<ResponseField name="subject" type="string">
  Your own id for the end user this profile *is*. Optional, 1 to 200 characters, opaque to us,
  never sent to a merchant. Set it and an API key bound to that subject sees this profile while
  no other end user's key can — see [End-user isolation](/end-user-isolation).

  It comes back as `null` on an untagged profile, and it **cannot be changed after creation**:
  `PATCH` with `subject` is a `400`, not a silent no-op. Decide before you create. `""` is a `400`
  as well — send a real id or omit the field.
</ResponseField>

<ResponseField name="name" type="object">
  `{ first, last }`. Used for the merchant's name fields, and as the cardholder name at the
  payment step — which is why the card action never asks for one.
</ResponseField>

<ResponseField name="contact" type="object">
  `{ email, phone }`. `email` is what order confirmations go to; most checkouts refuse to
  proceed without it. `phone` is required by a fair number of merchants, particularly
  delivery.
</ResponseField>

<ResponseField name="shipping" type="object">
  `{ addressLines, locality, administrativeAreaCode, postalCode, countryCode }`.

  * `addressLines` — array of strings. **Only the first line is filled** on the Shopify
    tier; a second line is stored but will not be typed. If an apartment number matters, put
    it on line one.
  * `locality` — city / town.
  * `administrativeAreaCode` — state / province. Send the short code (`KY`, `ON`) — the code
    is tried first, then the full name as a visible label.
  * `postalCode`
  * `countryCode` — ISO 3166-1 alpha-2. Filled before the state, because changing country
    repopulates the province list and would wipe a state chosen first.
</ResponseField>

<Warning>
  For US and CA addresses, `administrativeAreaCode` is effectively required. Shopify will not
  offer a shipping method until the state or province is set, and with no shipping method the
  order can never be placed — the run fails with `Could not reach the payment step` and the
  real cause is three steps upstream.
</Warning>

Values are stored as sent. No normalisation, no address validation, no correction. If the
postal code does not match the city, you find out when the merchant rejects it.

## Read

<CodeGroup>
  ```bash Get one theme={null}
  curl -s https://api.farthing.ai/v1/buyer-profiles/$PROFILE_ID \
    -H "authorization: Bearer $FARTHING_KEY"
  ```

  ```bash List theme={null}
  curl -s "https://api.farthing.ai/v1/buyer-profiles?limit=20" \
    -H "authorization: Bearer $FARTHING_KEY"
  ```
</CodeGroup>

```json List → 200 theme={null}
{
  "data": [
    {
      "id": "b3a1c0de-2f77-45a0-9c8e-6f4b2a1d9e30",
      "label": "ada-home",
      "subject": null,
      "name": { "first": "Ada", "last": "Lovelace" },
      "contact": { "email": "ada@example.com", "phone": "+15555550123" },
      "shipping": { "addressLines": ["1 Analytical Way"], "locality": "Louisville", "administrativeAreaCode": "KY", "postalCode": "40202", "countryCode": "US" },
      "createdAt": "2026-07-29T09:12:04.881Z"
    }
  ],
  "nextCursor": "2026-07-29T09:12:04.881Z"
}
```

The list is cursor-paginated, newest first. Pass `nextCursor` back as `?cursor=` for the next
page; `nextCursor: null` means you have them all. `limit` defaults to 20 and caps at 100.

## Update

```bash theme={null}
curl -sX PATCH https://api.farthing.ai/v1/buyer-profiles/$PROFILE_ID \
  -H "authorization: Bearer $FARTHING_KEY" \
  -H 'content-type: application/json' \
  -d '{
    "shipping": {
      "addressLines": ["221B Baker Street"],
      "locality": "London",
      "postalCode": "NW1 6XE",
      "countryCode": "GB"
    }
  }'
```

The response is the full updated profile — the same shape as create.

### PATCH replaces a document wholesale

This is the one thing to get right. `name`, `contact` and `shipping` are each stored as a
single JSON document, and `PATCH` is partial **at the top level only**:

* Omit a key and that document is left exactly as it was.
* Supply a key and its document is **replaced entirely**. Fields you did not mention are gone,
  not inherited.

<Warning>
  There is no deep merge, deliberately. Patching half an address and inheriting the other half
  from the old one is how you ship a parcel to a street in the wrong city — a new `postalCode`
  silently keeping the old `locality` produces an address that looks complete and is wrong.

  Send a whole `shipping` object or none at all.
</Warning>

```bash theme={null}
# Ada moved. This replaces the whole address, and leaves name and contact untouched.
-d '{"shipping":{"addressLines":["221B Baker Street"],"locality":"London","postalCode":"NW1 6XE","countryCode":"GB"}}'

# WRONG. The old addressLines, locality and countryCode survive; only the postcode changes.
-d '{"shipping":{"postalCode":"NW1 6XE"}}'
```

Three smaller behaviours:

* An empty body is a `400 No fields to update`, not a no-op that quietly succeeds.
* `subject` is rejected, not ignored — a `400` with an `unrecognized_keys` issue. A profile
  cannot be reassigned to a different end user, and a caller attempting it should be told so
  rather than getting a `200` and a profile that has not moved. See
  [End-user isolation](/end-user-isolation#a-profile-cannot-be-reassigned).
* Send `"name": null` to **clear** a document outright — that is how you remove an address you
  no longer want us holding, short of deleting the whole profile. `"name": {}` stores an empty
  object instead, which is a different thing.

One consequence worth designing around: a checkout stores only `buyerProfileId`, never a copy
of the address. Resolve that id after an update and you get the *current* address, not the one
the parcel actually went to. If shipped-to addresses matter to you — and for anything with a
refund or dispute process they do — record the address yourself at creation time rather than
resolving the reference later.

## Delete

```bash theme={null}
curl -sX DELETE https://api.farthing.ai/v1/buyer-profiles/$PROFILE_ID \
  -H "authorization: Bearer $FARTHING_KEY"
```

```json Response (200) theme={null}
{ "deleted": true, "id": "b3a1c0de-2f77-45a0-9c8e-6f4b2a1d9e30" }
```

A real delete, not a tombstone. **This is the path for honouring a data-deletion request** —
"remove what you hold about me" has an answer, and it is this endpoint.

What survives and what goes is decided by the schema, not by a cleanup job that might not run:

<CardGroup cols={2}>
  <Card title="Checkouts survive" icon="receipt">
    A checkout that referenced the profile keeps existing, with its `buyerProfileId` reference
    set to null. Purchase history outlives the identity, so your books still balance and the
    deleted person is no longer in them.
  </Card>

  <Card title="Buyer sessions cascade" icon="trash">
    Stored merchant logins for that profile are deleted with it. That is the point of deleting,
    not a side effect: leaving a live merchant session behind for a buyer you just erased is
    the opposite of what was asked for.
  </Card>
</CardGroup>

Deleting is not reversible and there is no soft-delete flag to undo. `404 Profile not found`
covers both "no such id" and "another tenant's" — a second delete of the same profile is a
`404`, so treat that as success if you are retrying.

<Note>
  A run currently in flight against a deleted profile does not stop. It already loaded the
  address it needed; the reference simply nulls out. Cancel the checkout too if the point is that
  nothing more should be shipped.
</Note>

## Profiles and stored merchant sessions

A buyer profile is also the identity that [buyer sessions](/buyer-sessions) hang off. When a
run logs into a merchant with credentials you supplied, the resulting cookies are stored
against `(tenant, buyerProfileId, merchantDomain)` — so the *next* checkout for that profile
at that merchant starts logged in.

That means the profile id is not just an address book entry. Reusing one profile across
unrelated end users would share their merchant logins. One profile per end user.

If you serve many end users, say which one each profile belongs to: set `subject` at creation
and issue your agent a key bound to it. That turns "one profile per end user" from a convention
you maintain into a fence the API enforces — a key for one buyer cannot read, patch, delete or
ship to another's profile, and cannot list them either.

The fence holds from your admin key too, which is where it matters for stored logins: a checkout
tagged `subject: "user_a91f"` cannot be pointed at a profile tagged for someone else, so a run can
never write one person's merchant session onto another person's profile. Naming both is a `403`. See
[End-user isolation](/end-user-isolation#which-buyer-profile-a-checkout-may-use).
