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

# Start a checkout session and get a token to collect payment

> Creates a checkout session for one offering and returns a single-use client token your storefront uses to collect the patient's card with Stripe's browser components. Send only the offering reference (from the enrollment link), the enrollment id, and an optional promo code; the server looks up the price, the itemized fees, and how the charge is routed on its own and rejects any request that tries to supply a price, fee, or routing choice. The brand is taken from your authenticated request context, never from the body. The card number never reaches Purple — the browser sends it straight to the payment provider using the returned token. Send an Idempotency-Key header; repeating the same request returns the same session, and reusing the key with a different body returns 409. The order is recorded before the charge is attempted, so a declined or failed payment still leaves a visible order. Poll the payment-status endpoint for the outcome. A promo code is refused for now while promotions are not yet available, so a patient is never quietly charged full price.



## OpenAPI

````yaml /openapi/public-openapi.json post /v1/payments/checkout-sessions
openapi: 3.1.0
info:
  title: Purple API
  version: 0.0.0
servers:
  - url: https://api.dev.purplelabelmd.com
security: []
tags:
  - description: Sign a patient in and out and manage the browser session.
    name: Sessions & authentication
  - description: Run the server-driven intake questionnaire question by question.
    name: Intake
  - description: Address autocomplete for the intake flow.
    name: Addresses
  - description: Read the public status of a patient's enrollment.
    name: Enrollment
  - description: Track a client's onboarding progress.
    name: Onboarding
  - description: Read back the platform configuration stored for your brand.
    name: Brand configuration
  - description: Enable offerings for your brand and set their display copy.
    name: Offering configuration
  - description: Start and track payments for an order.
    name: Payments
  - description: Subscribe to platform events and manage delivery endpoints.
    name: Webhooks
paths:
  /v1/payments/checkout-sessions:
    post:
      tags:
        - Payments
      summary: Start a checkout session and get a token to collect payment
      description: >-
        Creates a checkout session for one offering and returns a single-use
        client token your storefront uses to collect the patient's card with
        Stripe's browser components. Send only the offering reference (from the
        enrollment link), the enrollment id, and an optional promo code; the
        server looks up the price, the itemized fees, and how the charge is
        routed on its own and rejects any request that tries to supply a price,
        fee, or routing choice. The brand is taken from your authenticated
        request context, never from the body. The card number never reaches
        Purple — the browser sends it straight to the payment provider using the
        returned token. Send an Idempotency-Key header; repeating the same
        request returns the same session, and reusing the key with a different
        body returns 409. The order is recorded before the charge is attempted,
        so a declined or failed payment still leaves a visible order. Poll the
        payment-status endpoint for the outcome. A promo code is refused for now
        while promotions are not yet available, so a patient is never quietly
        charged full price.
      operationId: createCommerceCheckoutSession
      parameters:
        - $ref: '#/components/parameters/CommerceIdempotencyKey'
        - $ref: '#/components/parameters/CommerceBrandHeader'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CommerceCheckoutSessionCreate'
        required: true
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CommerceCheckoutSession'
          description: >-
            the session was created; use the returned client token to collect
            payment
        '400':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/CommerceProblem'
          description: >-
            the request was refused before checkout ran — the X-Brand-Id header
            is missing or malformed (the brand always comes from your
            authenticated request context, never from the body; a missing or
            too-short Idempotency-Key is refused as 422, not 400). Nothing was
            created and the Idempotency-Key was not consumed; resend with a
            valid X-Brand-Id header.
        '404':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/CommerceProblem'
          description: the offering could not be found for this brand
        '409':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/CommerceProblem'
          description: the Idempotency-Key was already used with a different request
        '422':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/CommerceProblem'
          description: >-
            the request tried to set a price, fee, or routing choice; carried
            card data; used a promo code that cannot be applied yet; or was
            otherwise malformed
        '502':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/CommerceProblem'
          description: >-
            the payment provider declined to start the payment; the order is
            left visible and failed
        '503':
          content:
            application/problem+json:
              example:
                status: 503
                title: >-
                  the pricing catalog is temporarily unavailable — checkout
                  cannot resolve the price (fail closed)
                type: commerce/checkout/catalog-unavailable
              schema:
                $ref: '#/components/schemas/CommerceProblem'
          description: >-
            checkout refused fail-closed and nothing was written — the
            Idempotency-Key was not consumed, and no payment was attempted. Two
            reasons carry this status, told apart by the problem `type`.
            `commerce/checkout/catalog-unavailable`: the live pricing catalog is
            temporarily unavailable, so checkout refuses rather than charging
            against a stale or missing price; this can also appear briefly while
            the catalog warms up after a restart or a quiet period, and once
            warm the same request returns 404 if the offering has no price for
            your brand. `commerce/checkout/account-binding-unresolved`: the
            payment account this item charges on could not be confirmed in time.
            That is NOT a statement that the item is unpurchasable or that the
            brand has no payment account — the lookup simply had not finished,
            it continues in the background and records its result, so the same
            request shortly after succeeds. Retry after a short wait
            (Retry-After is sent). `commerce/checkout/order-not-recorded`: your
            order could not be written to durable storage. Nothing was created
            and no payment was attempted — retry with the SAME Idempotency-Key,
            which resolves the same purchase and can never create a second one
            (Retry-After is sent).
      security:
        - apiKey: []
components:
  parameters:
    CommerceIdempotencyKey:
      description: >-
        mandatory for payments (api-style-guide) — replays return the original
        result
      in: header
      name: Idempotency-Key
      required: true
      schema:
        minLength: 8
        type: string
    CommerceBrandHeader:
      description: >-
        The brand this request is scoped to. It is validated against your
        authenticated account; an unknown or unauthorized brand is rejected. The
        brand is never taken from the request body.
      in: header
      name: X-Brand-Id
      required: true
      schema:
        pattern: ^brd_[A-Za-z0-9][A-Za-z0-9_-]*$
        type: string
  schemas:
    CommerceCheckoutSessionCreate:
      additionalProperties: false
      description: >-
        The complete input to start a checkout session. Send the offering
        reference, the enrollment id, and an optional promo code — nothing else.
        The server determines the price, the fees, and how the charge is routed;
        a request that adds a price, fee, or routing field is rejected, and
        there is deliberately no field for a card number or security code.
      properties:
        journey_id:
          description: the enrollment id this checkout belongs to
          pattern: ^jny_[A-Za-z0-9][A-Za-z0-9_-]*$
          type: string
        offering_ref:
          description: >-
            the offering to purchase, taken from the enrollment link. Each
            supply length (for example 1, 3, 6, or 12 months) is its own
            offering with its own reference; selecting a plan means sending a
            different offering reference, not a quantity or term field.
          type: string
        promo:
          description: >-
            an optional promo code. Promotions are not yet available, so sending
            one returns a 422 rather than quietly charging full price.
          type: string
      required:
        - offering_ref
        - journey_id
      type: object
    CommerceCheckoutSession:
      description: >-
        The created checkout session. Everything here is safe to show in the
        patient's browser. The client token is returned only once, in this
        response, to collect payment; it is never returned again by the status
        endpoint.
      properties:
        amount:
          $ref: '#/components/schemas/CommerceMoney'
          description: the amount the patient will be charged, as determined by the server
        client_secret:
          description: >-
            a single-use client token. The browser passes it to the payment
            provider's components to collect the card and complete payment; the
            card number never reaches Purple.
          type: string
        offering_ref:
          type: string
        order_ref:
          pattern: ^ord_[A-Za-z0-9][A-Za-z0-9_-]*$
          type: string
        payment_state:
          description: >-
            the payment state at creation; it is always awaiting payment until
            the patient completes it
          enum:
            - created
            - payment_authorized
            - paid
            - payment_failed
            - partially_refunded
            - refunded
            - disputed
            - cancelled
          type: string
        stripe_account:
          description: >-
            the payment account this session's charge was set up on. The browser
            must load the payment provider's components in THIS account's
            context for the client token above to work. PRESENT only when the
            brand's charges are processed on the client's own payment account;
            OMITTED entirely (never null, never empty) when they are processed
            on the Purple platform account, because there is no separate account
            to point the browser at. Treat the field being absent as the signal
            to use the ordinary platform setup.
          pattern: ^acct_[A-Za-z0-9][A-Za-z0-9_-]*$
          type: string
      required:
        - order_ref
        - offering_ref
        - payment_state
        - amount
        - client_secret
      type: object
    CommerceProblem:
      description: RFC 7807 problem document (api-style-guide)
      properties:
        detail:
          type: string
        status:
          type: integer
        title:
          type: string
        type:
          type: string
      required:
        - type
        - title
        - status
      type: object
    CommerceMoney:
      description: integer minor units + ISO 4217 (api-style-guide) — never floats
      properties:
        amount_minor:
          type: integer
        currency:
          pattern: ^[A-Z]{3}$
          type: string
      required:
        - amount_minor
        - currency
      type: object
  securitySchemes:
    apiKey:
      description: 'Per-client API key (M2M). Presented as `Authorization: Bearer <key>`.'
      scheme: bearer
      type: http

````