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

# Create a buyer profile

> Every field is optional — `{}` is a valid profile — but the agent can only fill what you gave it. Values are stored and typed exactly as sent: no normalisation, no address validation, no correction.

One profile per end user. A profile is the identity that stored merchant sessions hang off, so reusing one across unrelated buyers would share their merchant logins.

Requires the `buyer-profiles.write` scope.

Set `subject` to say which of your end users this profile is for. A key bound to that subject can then reach it and no other end user's key can.



## OpenAPI

````yaml /openapi.json post /v1/buyer-profiles
openapi: 3.1.0
info:
  title: Farthing API
  version: 1.0.0
  summary: >-
    Agentic checkout. Give it a product URL and a plain-English request; it
    drives a real browser to a completed purchase and asks a human only when it
    must.
  description: >-
    The HTTP API is the product. Everything an integration needs — creating
    checkouts, answering the questions a run stops on, storing buyers, issuing
    scoped keys — is here.


    A checkout is asynchronous and pauses for humans. `POST /v1/checkouts`
    returns in milliseconds with a `queued` object; the run itself takes minutes
    and may park on a `pendingUserAction` for up to ten. Poll `GET
    /v1/checkouts/{id}` (unmetered) or register a webhook endpoint.


    Not covered by this spec: the live-view embed (`GET /embed/{checkoutId}` and
    `GET /embed/{checkoutId}/stream`). Those are consumed by a browser,
    authenticate with a short-lived JWT minted at `POST
    /v1/checkouts/{id}/embed-token` rather than with an API key, and return HTML
    and `text/event-stream`.


    Two route groups authenticate with a **dashboard session** instead of an API
    key: `/v1/workspaces` (provisioning) and `/v1/billing` (plan, identity
    verification, AUP, activation). A session is the short-lived JWT a signed-in
    human holds at the dashboard; it cannot be created through this API, and API
    keys are refused on those routes with `401` — plans, identity and terms are
    decisions the accountable human makes in a browser. Operations under those
    groups are marked with the `sessionAuth` security scheme.
  contact:
    name: Farthing
    url: https://docs.farthing.ai
servers:
  - url: https://api.farthing.ai
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Signup
    description: Self-serve account creation. The only unauthenticated write on the API.
  - name: Checkouts
    description: Create a run, poll it, cancel it, and answer what it asks.
  - name: Buyer profiles
    description: >-
      Reusable identity and shipping details, referenced by id when creating a
      checkout.
  - name: Buyer sessions
    description: >-
      Stored merchant logins captured by a `credentials` action. Read-only
      metadata and revocation.
  - name: Webhooks
    description: >-
      Endpoints that receive `checkout.status_changed` with an HMAC-SHA256
      signature.
  - name: API keys
    description: >-
      Self-serve key issuance and revocation. Keys carry scopes; a key can never
      mint one broader than itself.
  - name: Usage
    description: Completed checkouts by day, and gross merchandise volume by currency.
  - name: Workspaces
    description: >-
      Dashboard-session workspace provisioning. One workspace per account; the
      first request of a fresh sign-in is “do I have a workspace?” and the
      second is “make me one”.
  - name: Billing
    description: >-
      Dashboard-session billing: plan, identity verification, AUP, and the
      activation checklist. Session-only — an agent's key completes checkouts,
      it does not choose subscription plans.
  - name: Health
    description: Liveness.
paths:
  /v1/buyer-profiles:
    post:
      tags:
        - Buyer profiles
      summary: Create a buyer profile
      description: >-
        Every field is optional — `{}` is a valid profile — but the agent can
        only fill what you gave it. Values are stored and typed exactly as sent:
        no normalisation, no address validation, no correction.


        One profile per end user. A profile is the identity that stored merchant
        sessions hang off, so reusing one across unrelated buyers would share
        their merchant logins.


        Requires the `buyer-profiles.write` scope.


        Set `subject` to say which of your end users this profile is for. A key
        bound to that subject can then reach it and no other end user's key can.
      operationId: createBuyerProfile
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BuyerProfileCreate'
            example:
              label: ada-home
              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
      responses:
        '201':
          description: Created. Omitted sub-objects come back as `null`, not `{}`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BuyerProfile'
        '400':
          description: Type mismatch.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              example:
                error: true
                message: Invalid profile
                issues: []
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
components:
  schemas:
    BuyerProfileCreate:
      type: object
      title: BuyerProfileCreate
      description: >-
        Every field is optional — `{}` is a valid profile — but the agent can
        only fill what you gave it.


        The top level is strict: an unrecognised field is a `400` with an
        `unrecognized_keys` issue, not a field the server drops for you. The
        field most worth misspelling here is `subject`, and a `201` carrying an
        untagged row is an isolation control that failed open on a typo. The
        nested `name`, `contact` and `shipping` objects are not strict. `PATCH`
        is strict in the same way.
      additionalProperties: false
      properties:
        label:
          type: string
          description: Yours, for bookkeeping. Never sent to a merchant.
        subject:
          type: string
          minLength: 1
          maxLength: 200
          description: >-
            Your own opaque id for one of your end users. 1 to 200 characters
            after trimming; never parsed, never validated against anything, and
            never sent to a merchant. An empty or whitespace-only value is a
            `400` — a subject that means nothing reads as isolation you do not
            have.


            On a key bound to a subject this is filled in for you: omit it and
            the row is stamped with the key's subject, name a different one and
            the request is a `403`. On a tenant-wide key it is how a row gets
            tagged in the first place.


            **Create-only.** A profile cannot be reassigned to another end user
            afterwards: `PATCH` with `subject` is a `400`.
          examples:
            - user_a91f
        name:
          $ref: '#/components/schemas/BuyerName'
        contact:
          $ref: '#/components/schemas/BuyerContact'
        shipping:
          $ref: '#/components/schemas/BuyerShipping'
    BuyerProfile:
      type: object
      title: BuyerProfile
      required:
        - id
        - label
        - subject
        - name
        - contact
        - shipping
        - createdAt
      properties:
        id:
          type: string
          format: uuid
        label:
          type:
            - string
            - 'null'
        subject:
          type:
            - string
            - 'null'
          description: >-
            Your own id for the end user this profile belongs to, or `null` when
            it is not tagged to one. Always present — unlike a checkout, which
            omits the field entirely. Fixed at creation.
          examples:
            - user_a91f
        name:
          type:
            - object
            - 'null'
          additionalProperties: true
          description: '`null` when never supplied — not `{}`.'
        contact:
          type:
            - object
            - 'null'
          additionalProperties: true
        shipping:
          type:
            - object
            - 'null'
          additionalProperties: true
        createdAt:
          type: string
          format: date-time
    ValidationError:
      type: object
      title: ValidationError
      required:
        - error
        - message
      properties:
        error:
          type: boolean
          const: true
        message:
          type: string
        issues:
          type: array
          description: >-
            Raw Zod issues. `path` is the machine-readable part. Additional
            fields appear depending on `code` (`validation`, `expected`,
            `received`, …) — do not treat the list below as exhaustive.
          items:
            type: object
            additionalProperties: true
            properties:
              code:
                type: string
              message:
                type: string
              path:
                type: array
                items:
                  type:
                    - string
                    - integer
    BuyerName:
      type: object
      additionalProperties: false
      properties:
        first:
          type: string
        last:
          type: string
      description: >-
        Also used as the cardholder name at the payment step, which is why the
        card action never asks for one.
      title: BuyerName
    BuyerContact:
      type: object
      additionalProperties: false
      properties:
        email:
          type: string
          description: >-
            Where the merchant's order confirmation goes. Most checkouts refuse
            to proceed without it.
        phone:
          type: string
          description: Required by a fair number of merchants, particularly delivery.
      title: BuyerContact
    BuyerShipping:
      type: object
      additionalProperties: false
      properties:
        addressLines:
          type: array
          items:
            type: string
          description: >-
            Only the **first** line is filled on some platforms. Put an
            apartment number on line one if it matters.
        locality:
          type: string
          description: City / town.
        administrativeAreaCode:
          type: string
          description: >-
            State / province. Send the short code (`KY`, `ON`). Effectively
            required for US and CA addresses — Shopify offers no shipping method
            without it, and with no shipping method the order can never be
            placed.
        postalCode:
          type: string
        countryCode:
          type: string
          description: >-
            ISO 3166-1 alpha-2. Filled before the state, since changing country
            repopulates the province list.
      title: BuyerShipping
    Error:
      type: object
      title: Error
      description: >-
        Every error is this object. There are no error codes: match on the HTTP
        status, treat `message` as text for a human.
      required:
        - error
        - message
      properties:
        error:
          type: boolean
          const: true
        message:
          type: string
    ScopeError:
      type: object
      title: ScopeError
      description: An out-of-scope request. `requiredScope` saves you parsing the message.
      required:
        - error
        - message
        - requiredScope
      properties:
        error:
          type: boolean
          const: true
        message:
          type: string
        requiredScope:
          $ref: '#/components/schemas/Scope'
    Scope:
      type: string
      title: Scope
      description: >-
        `<resource>.read` or `<resource>.write`, or the wildcard `*`. `write`
        implies `read` on the same resource. Resources are exactly the route
        groups under `/v1`.
      enum:
        - '*'
        - checkouts.read
        - checkouts.write
        - buyer-profiles.read
        - buyer-profiles.write
        - buyer-sessions.read
        - buyer-sessions.write
        - webhooks.read
        - webhooks.write
        - api-keys.read
        - api-keys.write
        - usage.read
        - usage.write
  responses:
    Unauthorized:
      description: >-
        Missing, malformed, invalid or revoked bearer token. There is no
        distinction between "never existed" and "revoked" — that difference is
        only useful to someone probing.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            missing:
              value:
                error: true
                message: Missing bearer token
            invalid:
              value:
                error: true
                message: Invalid or revoked API key
    Forbidden:
      description: >-
        The key is missing the scope this route requires, the tenant is
        suspended, or the key is bound to one end user and this route answers an
        account-wide question.
      content:
        application/json:
          schema:
            anyOf:
              - $ref: '#/components/schemas/ScopeError'
              - $ref: '#/components/schemas/Error'
          examples:
            scope:
              value:
                error: true
                message: This API key is missing the 'buyer-profiles.read' scope.
                requiredScope: buyer-profiles.read
            suspended:
              value:
                error: true
                message: Tenant suspended
            subjectBound:
              summary: A subject-bound key reaching for a tenant-wide fact
              value:
                error: true
                message: A subject-bound key cannot read account usage.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        `Authorization: Bearer ck_test_…` or `ck_live_…`. There is no other auth
        scheme — no query-string keys, no request signing, no OAuth.

````