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

# Read one offering's price and whether your brand can sell it

> Everything a storefront page needs before it renders a price: the current price and terms for one of your offerings, and your brand's readiness to sell it, in one request. The two travel together on purpose — a price on its own does not tell you whether a purchase would go through, and a page that shows one without the other can offer a customer something the checkout will refuse. The price is the same price the checkout charges, from the same source; this endpoint never computes a second one. Read price_status before the amount: pending means the terms are not fully set up yet, and no amount is shown rather than a guessed one. Read the readiness block before you render a buy control. You may ask about your own brands only; any other brand is not found. This read changes nothing. CALLING IT FROM A BROWSER: a web page may call this read directly. Present a publishable brand key — sent as `Authorization: Bearer pub_live_…`, or `pub_test_…` outside production — which is minted for a single brand, is read-only, and is meant to be shipped in page source. The key reaches only the operations its allowlist names: today that is this read and that brand's readiness read, and nothing else — every other operation, and every write, is refused. Because it is minted for one brand it may only ask about that brand — any other brand, a sibling brand on your own account included, answers the same not found as a brand that does not exist, and the refusal never tells you which of those it was. A cross-origin page must also have its exact origin registered on that brand's browser-origin allowlist, so publishing the key does not by itself open the read to any site. The server path is unchanged — call this endpoint from your own server with your secret API key exactly as before, and never put that secret key in page source.



## OpenAPI

````yaml /openapi/public-openapi.json get /v1/account/brands/{brand_id}/storefront
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/account/brands/{brand_id}/storefront:
    get:
      tags:
        - Onboarding
      summary: Read one offering's price and whether your brand can sell it
      description: >-
        Everything a storefront page needs before it renders a price: the
        current price and terms for one of your offerings, and your brand's
        readiness to sell it, in one request. The two travel together on purpose
        — a price on its own does not tell you whether a purchase would go
        through, and a page that shows one without the other can offer a
        customer something the checkout will refuse. The price is the same price
        the checkout charges, from the same source; this endpoint never computes
        a second one. Read price_status before the amount: pending means the
        terms are not fully set up yet, and no amount is shown rather than a
        guessed one. Read the readiness block before you render a buy control.
        You may ask about your own brands only; any other brand is not found.
        This read changes nothing. CALLING IT FROM A BROWSER: a web page may
        call this read directly. Present a publishable brand key — sent as
        `Authorization: Bearer pub_live_…`, or `pub_test_…` outside production —
        which is minted for a single brand, is read-only, and is meant to be
        shipped in page source. The key reaches only the operations its
        allowlist names: today that is this read and that brand's readiness
        read, and nothing else — every other operation, and every write, is
        refused. Because it is minted for one brand it may only ask about that
        brand — any other brand, a sibling brand on your own account included,
        answers the same not found as a brand that does not exist, and the
        refusal never tells you which of those it was. A cross-origin page must
        also have its exact origin registered on that brand's browser-origin
        allowlist, so publishing the key does not by itself open the read to any
        site. The server path is unchanged — call this endpoint from your own
        server with your secret API key exactly as before, and never put that
        secret key in page source.
      operationId: getCommerceAccountBrandStorefront
      parameters:
        - description: >-
            the brand to read. Must be one of your own brands; any other brand
            is not found (404, never 403 — existence is never revealed across
            accounts).
          in: path
          name: brand_id
          required: true
          schema:
            type: string
        - description: the offering to price. Required.
          in: query
          name: offering_ref
          required: true
          schema:
            minLength: 1
            type: string
        - description: the resolved account, set by the gateway.
          in: header
          name: X-Client-Id
          required: true
          schema:
            minLength: 1
            type: string
        - description: the brands the resolved account owns, set by the gateway.
          in: header
          name: X-Client-Brand-Ids
          required: true
          schema:
            minLength: 1
            type: string
        - description: which kind of key was presented, set by the gateway.
          in: header
          name: X-Client-Key-Class
          required: false
          schema:
            enum:
              - publishable
              - secret
            type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  data:
                    $ref: '#/components/schemas/CommerceBrandStorefront'
                required:
                  - data
                type: object
          description: the offering's price and the brand's readiness to sell it
        '400':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/CommerceProblem'
          description: >-
            malformed owned-brand header, or a missing offering_ref on a brand
            you DO own — RFC 7807
        '401':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/CommerceProblem'
          description: >-
            Unauthenticated — an unknown or revoked API key resolves nothing and
            returns a single opaque error body, with no way to probe whether a
            key is valid — RFC 7807
        '403':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/CommerceProblem'
          description: >-
            the key you presented is a publishable (read-only) key and this
            request is not one of the reads it may make — RFC 7807, type
            commerce/auth/publishable-credential-read-only
        '404':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/CommerceProblem'
          description: >-
            not one of your brands — an unknown brand, a brand belonging to
            another account, and a malformed brand id are ONE indistinguishable
            refusal (404, never 403); OR, on a brand you DO own, no price is
            configured for that offering (type
            commerce/merchandising/not-configured) — RFC 7807
        '405':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/CommerceProblem'
          description: method not allowed — this endpoint is read-only — RFC 7807
      security:
        - apiKey: []
        - publishableKey: []
components:
  schemas:
    CommerceBrandStorefront:
      additionalProperties: false
      description: >-
        One offering's price and the brand's readiness to sell it. Read
        price_status inside pricing before the amount, and read readiness before
        you render a buy control — a price that resolves on a brand that is not
        ready to sell is a purchase the checkout will refuse.
      properties:
        brand_id:
          pattern: ^brd_[A-Za-z0-9][A-Za-z0-9_-]*$
          type: string
        offering_ref:
          description: >-
            the offering that was priced — the offering_ref you asked for,
            echoed back.
          type: string
        pricing:
          allOf:
            - $ref: '#/components/schemas/CommerceMerchandising'
          description: >-
            the price and terms, in exactly the shape the merchandising read
            returns. When price_status is pending the amount and the terms block
            are omitted rather than guessed.
        readiness:
          allOf:
            - $ref: '#/components/schemas/CommerceBrandReadiness'
          description: >-
            whether the brand can actually sell, and what is outstanding if it
            cannot — the same report the brand readiness endpoint returns.
      required:
        - brand_id
        - offering_ref
        - pricing
        - readiness
      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
    CommerceMerchandising:
      description: >-
        The price and terms for one offering on one of your brands — everything
        a plan card needs before it can quote a number. When the offering is
        fully set up you get `list_price`, the amount a patient will be charged,
        and `offering`, how often that amount bills and how often the product
        ships. The amount here is the amount the checkout charges: both resolve
        the same price from the same source, so a page cannot show one figure
        and bill another. When the offering is not fully set up — its terms are
        unset, or the price could not be resolved at the moment you asked — the
        request still succeeds, but with `price_status: pending`, and
        `list_price` and `offering` are omitted rather than guessed or served
        from an old copy. Check `price_status` before you read the amount, and
        render the plan without a buy control while it is pending: display
        degrades, money does not, and a checkout started against a pending
        offering is refused. `prices` is a display-only overlay of compare-at
        and introductory anchors; it is always present, may be empty, and never
        sets the amount charged.
      properties:
        list_price:
          $ref: '#/components/schemas/CommerceMoney'
        offering:
          $ref: '#/components/schemas/CommerceOfferingMerchandising'
        price_status:
          description: >-
            whether this offering can be quoted right now. `available`, or the
            field being absent, means it is fully set up: `list_price` and
            `offering` are both present and the amount equals what a checkout
            will charge. `pending` means it is not — either its billing and
            shipping terms have not been set yet, or the price could not be
            resolved at the moment you asked — and in both cases `list_price`
            and `offering` are omitted rather than guessed or served from an old
            copy. Read this field before you read the amount. While it is
            `pending`, show the plan without a price and without a buy control:
            a checkout for an offering whose price does not resolve is refused,
            so a buy control here can only produce a failed purchase.
          enum:
            - available
            - pending
          type: string
        prices:
          items:
            $ref: '#/components/schemas/CommercePriceMerchandising'
          type: array
      required:
        - prices
      type: object
    CommerceBrandReadiness:
      additionalProperties: false
      description: >-
        Whether one of your brands is ready to sell, and what is outstanding if
        it is not. checks covers the brand as a whole; offerings covers each
        offering you have enabled. Read source_readable first: false means the
        platform could not read your offering setup just now, so an empty rows
        list means "we could not look", never "nothing is outstanding".
      properties:
        brand_id:
          pattern: ^brd_[A-Za-z0-9][A-Za-z0-9_-]*$
          type: string
        checks:
          description: >-
            brand-wide readiness rows — your payment account and your checkout
            consent documents.
          items:
            $ref: '#/components/schemas/CommerceReadinessCheckRow'
          type: array
        offerings:
          additionalProperties: false
          properties:
            rows:
              items:
                $ref: '#/components/schemas/CommerceReadinessOffering'
              type: array
            source_readable:
              description: >-
                whether the platform could read your offering setup for this
                request. When false, rows is empty because the read did not
                answer — not because nothing is outstanding.
              type: boolean
          required:
            - source_readable
            - rows
          type: object
      required:
        - brand_id
        - checks
        - offerings
      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
    CommerceOfferingMerchandising:
      description: >-
        How one offering bills and ships, plus the display copy published for
        it. Billing and shipping run on separate schedules on purpose: a plan
        can bill quarterly and ship monthly, so read `billing_frequency` and
        `shipping_frequency` independently rather than inferring one from the
        other. The two always arrive together — a plan is never described to a
        patient with half its terms — and this whole block is omitted when they
        have not been set, which is the state `price_status: pending` reports on
        the enclosing response. `display_name`, `description` and `includes` are
        the published copy for the plan; each is optional, and when one is
        absent show your own wording rather than `offering_ref`, which is an
        internal identifier and is never patient-facing text. Missing copy never
        suppresses the terms: a plan with no published name still returns its
        schedules, so you can render it and price it.
      properties:
        billing_frequency:
          enum:
            - one_time
            - monthly
            - quarterly
            - semiannual
            - annual
          type: string
        brand_id:
          type: string
        description:
          description: >-
            the published paragraph describing the plan. Omitted when none has
            been published.
          type: string
        display_name:
          description: >-
            the plan's published name, as your operations contact set it — the
            heading to show on a plan card. Omitted when no name has been
            published; fall back to your own wording, never to `offering_ref`.
          type: string
        includes:
          description: >-
            the published "what's included" lines for the plan, in the order
            they should be shown. Omitted when none have been published. An
            empty list is not the same as omitted — it means the plan was
            published with no bullet lines on purpose, so render no list rather
            than falling back to your own.
          items:
            type: string
          type: array
        offering_ref:
          type: string
        shipping_frequency:
          enum:
            - none
            - weekly
            - biweekly
            - monthly
            - quarterly
          type: string
        version:
          description: >-
            the revision number of the terms, present only on plans configured
            through the older record that carried one. Absent otherwise; do not
            depend on it to detect a change.
          minimum: 1
          type: integer
      required:
        - brand_id
        - offering_ref
      type: object
    CommercePriceMerchandising:
      description: >-
        Display price anchors for one `sku` on one of your brands — the numbers
        you may show *beside* a price, never the price itself.
        `compare_price_minor` is the struck-through "was" figure.
        `introductory_price_minor` is the first-purchase figure intended for a
        patient who has not bought this product before. Neither field is the
        amount a patient is charged: the charged amount is `list_price` on the
        enclosing response. Show these beside it as anchors and never compute a
        charge from them — what a checkout will actually collect is stated by
        the checkout endpoint, not here. Amounts are whole numbers in the
        smallest unit of `currency` (cents for USD), matching every other amount
        in this API.
      properties:
        brand_id:
          type: string
        compare_price_minor:
          type: integer
        currency:
          pattern: ^[A-Z]{3}$
          type: string
        introductory_price_minor:
          type: integer
        sku:
          type: string
        version:
          minimum: 1
          type: integer
      required:
        - brand_id
        - sku
        - currency
        - version
      type: object
    CommerceReadinessCheckRow:
      additionalProperties: false
      description: >-
        One readiness check. kind tells you how much the platform actually
        knows: performed means the check ran against real data and result is its
        answer; unverifiable means the check could not be run here, so it is
        neither a pass nor a fail and must not be read as either; named_pending
        means the check exists but its rules are not settled yet, and blocking
        says whether it holds the offering back today.
      properties:
        blocking:
          description: present only when kind is named_pending.
          type: boolean
        check:
          description: the check's stable identifier.
          type: string
        detail:
          description: >-
            plain-language explanation. When something is outstanding this names
            what has to happen and who does it, so a step nobody on your side
            can perform is never presented as yours.
          type: string
        kind:
          description: performed, unverifiable, or named_pending.
          type: string
        result:
          description: pass or fail — present only when kind is performed.
          type: string
      required:
        - check
        - kind
        - detail
      type: object
    CommerceReadinessOffering:
      additionalProperties: false
      description: >-
        One of your brand's enabled offerings, with the platform's live verdict
        on whether it can be sold and the per-check report behind that verdict.
      properties:
        checks:
          items:
            $ref: '#/components/schemas/CommerceReadinessCheckRow'
          type: array
        offering_ref:
          description: the offering's code.
          type: string
        status:
          description: >-
            the platform's live verdict for this offering — active means it can
            be sold now; anything else means it cannot yet, and the checks below
            say why.
          type: string
      required:
        - offering_ref
        - status
        - checks
      type: object
  securitySchemes:
    apiKey:
      description: 'Per-client API key (M2M). Presented as `Authorization: Bearer <key>`.'
      scheme: bearer
      type: http
    publishableKey:
      description: >-
        Brand-scoped, read-only publishable key, safe to ship in a website.
        Presented as `Authorization: Bearer <key>` with the
        `pub_live_`/`pub_test_` prefix. It is minted for ONE brand and reaches
        only the two brand reads that declare it; every other operation, and
        every write, is refused. Cross-origin browser use additionally requires
        the page's exact origin on the brand's browser-origin allowlist.
      scheme: bearer
      type: http

````