> ## 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 an API key

> The plaintext key is returned exactly once, here. Only `sha256(key)` is stored — no endpoint, support process or database query can recover it.

**Scopes.** Omit `scopes` and the key gets `["*"]`, full access, which is what a key has always meant here. Supply an array to narrow it. A key can never mint a key with scopes it does not itself hold, so `api-keys.write` is not a back door to `*`.

**Mode.** Two independent `403`s guard a `live` key: a sandbox-only account cannot create one, and a `ck_test_` key cannot issue one however activated the account is. Authority passes down, never sideways — otherwise the sandbox confinement a test key exists to provide would be one `POST` deep. Mode is fixed at creation; there is no promotion path. A consequence worth planning for: a self-serve signup only ever holds `ck_test_` keys, so the first `ck_live_` key on a newly activated account cannot be minted through this endpoint and has to be issued during activation.

An empty body is valid and produces a full-access `test` key.

Requires the `api-keys.write` scope.



## OpenAPI

````yaml /openapi.json post /v1/api-keys
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/api-keys:
    post:
      tags:
        - API keys
      summary: Create an API key
      description: >-
        The plaintext key is returned exactly once, here. Only `sha256(key)` is
        stored — no endpoint, support process or database query can recover it.


        **Scopes.** Omit `scopes` and the key gets `["*"]`, full access, which
        is what a key has always meant here. Supply an array to narrow it. A key
        can never mint a key with scopes it does not itself hold, so
        `api-keys.write` is not a back door to `*`.


        **Mode.** Two independent `403`s guard a `live` key: a sandbox-only
        account cannot create one, and a `ck_test_` key cannot issue one however
        activated the account is. Authority passes down, never sideways —
        otherwise the sandbox confinement a test key exists to provide would be
        one `POST` deep. Mode is fixed at creation; there is no promotion path.
        A consequence worth planning for: a self-serve signup only ever holds
        `ck_test_` keys, so the first `ck_live_` key on a newly activated
        account cannot be minted through this endpoint and has to be issued
        during activation.


        An empty body is valid and produces a full-access `test` key.


        Requires the `api-keys.write` scope.
      operationId: createApiKey
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                mode:
                  type: string
                  enum:
                    - test
                    - live
                  default: test
                  description: >-
                    `test` keys are confined to sandbox merchants no matter how
                    the account is configured.
                label:
                  type: string
                  maxLength: 200
                scopes:
                  type: array
                  minItems: 1
                  maxItems: 32
                  items:
                    $ref: '#/components/schemas/Scope'
                  description: Omit for full access (`["*"]`).
                subject:
                  type: string
                  minLength: 1
                  maxLength: 200
                  description: >-
                    Bind the key to one of your end users — your own opaque id,
                    1 to 200 characters after trimming. **Omit for a tenant-wide
                    key**, which is what every key issued before this existed
                    already is. `""` is a `400`, not a tenant-wide key by
                    another name.


                    A bound key sees and creates only that subject's checkouts
                    and buyer profiles, and is refused outright on the
                    account-wide endpoints (`GET`/`DELETE /v1/api-keys`,
                    `/v1/usage`, `/v1/webhooks`).


                    Minting from a bound key **inherits** the binding: omitting
                    `subject` gives another key for the same end user, not a
                    tenant-wide one, and naming a different subject is a `403`.
                    Fixed at creation — there is no rebinding and no unbinding.
                  examples:
                    - user_a91f
              description: >-
                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.
            examples:
              fullAccess:
                summary: Full access (scopes omitted)
                value:
                  mode: live
                  label: prod-worker
              narrow:
                summary: An agent that may only run checkouts
                value:
                  mode: live
                  label: shopping-agent
                  scopes:
                    - checkouts.write
              boundToOneEndUser:
                summary: A key that can only ever act for one end user
                value:
                  mode: live
                  label: agent for user_a91f
                  scopes:
                    - checkouts.write
                    - buyer-profiles.read
                  subject: user_a91f
      responses:
        '201':
          description: Created. `key` appears here and nowhere else, ever.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiKeyCreated'
              example:
                id: 2a44c7f0-1b23-4a95-9d0e-77e1b6c4a812
                mode: live
                scopes:
                  - checkouts.write
                keyPrefix: ck_live_9Hf2QzT
                key: ck_live_9Hf2QzT0mHq9pP2rVwXk1tLb
        '400':
          description: Bad `mode`, or a scope string that is not in the valid set.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnknownScopeError'
              example:
                error: true
                message: 'Unknown scope(s): checkouts.admin'
                validScopes:
                  - '*'
                  - 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
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: >-
            A sandbox-only account asking for a live key, a test key asking for
            a live key, or an attempt to escalate scopes or subject. Authority
            passes down, never sideways.
          content:
            application/json:
              schema:
                anyOf:
                  - $ref: '#/components/schemas/EscalationError'
                  - $ref: '#/components/schemas/ScopeError'
                  - $ref: '#/components/schemas/Error'
              examples:
                sandboxOnly:
                  value:
                    error: true
                    message: >-
                      This account is sandbox-only. Live keys require
                      activation.
                escalation:
                  summary: A narrow key trying to mint a broader one
                  value:
                    error: true
                    message: A key cannot grant scopes it does not itself hold.
                    heldScopes:
                      - checkouts.write
                modeEscalation:
                  summary: A test key trying to mint a live key
                  value:
                    error: true
                    message: >-
                      A test key cannot issue a live key. Use a live key, or
                      activate the account.
                subjectEscalation:
                  summary: A subject-bound key trying to issue a key for someone else
                  value:
                    error: true
                    message: >-
                      This API key is bound to subject 'user_a91f' and cannot
                      issue keys for another.
components:
  schemas:
    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
    ApiKeyCreated:
      type: object
      title: ApiKeyCreated
      required:
        - id
        - mode
        - scopes
        - keyPrefix
        - key
      properties:
        id:
          type: string
          format: uuid
        mode:
          type: string
          enum:
            - test
            - live
        scopes:
          type: array
          items:
            $ref: '#/components/schemas/Scope'
        subject:
          type: string
          description: >-
            The end user this key acts for. **Present only when the key is
            bound** — a tenant-wide key's creation response has no `subject`
            field at all.
          examples:
            - user_a91f
        keyPrefix:
          type: string
          examples:
            - ck_live_9Hf2QzT
        key:
          type: string
          description: The plaintext key. Returned here and nowhere else, ever.
    UnknownScopeError:
      type: object
      title: UnknownScopeError
      required:
        - error
        - message
      properties:
        error:
          type: boolean
          const: true
        message:
          type: string
        validScopes:
          type: array
          items:
            type: string
          description: The complete valid set, so you never have to guess.
    EscalationError:
      type: object
      title: EscalationError
      description: Returned when a key tries to mint a key with scopes it does not hold.
      required:
        - error
        - message
      properties:
        error:
          type: boolean
          const: true
        message:
          type: string
        heldScopes:
          type: array
          items:
            $ref: '#/components/schemas/Scope'
          description: What the authenticating key actually holds.
    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'
    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
  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
  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.

````