openapi: 3.1.0
info:
  title: Pillwise API
  version: 0.1.0
  description: >
    US medication Index plus optional member Cabinet. Index is catalog lookup with an API key.
    Cabinet is completed forms, isolated per tenant. The Index reads are also MCP tools (Streamable
    HTTP) at POST /mcp, with the same key; each tool call is one lookup. [Setup for Claude Code,
    Cursor and VS Code](https://pillwise.dev/docs/mcp).
  license:
    name: Proprietary
servers:
  - url: https://api.pillwise.dev
    description: Future public API
tags:
  - name: Index
  - name: Cabinet
  - name: Meta
  - name: Webhooks
    description: >
      Outbound POST to the HTTPS URL you registered, signed with the `whsec_…` secret shown once at
      create. `X-Pillwise-Signature` is `t=<unix seconds>,v1=<hex>`, where v1 is HMAC-SHA256 of
      `<t>.<raw body>` keyed with the whole secret (prefix included). Compare in constant time and
      refuse a `t` more than five minutes from your clock, so a captured delivery cannot be
      replayed. 2xx is success; anything else is retried.
security:
  - bearerAuth: []
paths:
  /v1/drugs/search:
    get:
      tags:
        - Index
      summary: Typeahead suggest (brand/ingredient groups). No packages on hits.
      description: >-
        Suggest hits are what a member picks, in order: the medicine named exactly, then its main
        forms ("Gabapentin capsule"), brands and other ingredients, combinations, salts and single
        products; within each, the most widely sold first. Label names that are only the generic
        name ("Sertraline HCl") are the medicine itself. Everyday names ("vitamin D3", "NPH
        insulin") and brands still said after leaving the market ("Zofran") find the medicine; such
        a brand hit shows the name typed with the ingredient as its subtitle.
      parameters:
        - in: query
          name: q
          required: true
          schema:
            type: string
            example: tylenol
        - in: query
          name: jurisdiction
          schema:
            type: string
            default: us
        - in: query
          name: limit
          schema:
            type: integer
            default: 10
            maximum: 50
        - in: query
          name: cabinet_only
          schema:
            type: boolean
            default: true
        - in: query
          name: intent
          schema:
            type: string
            enum:
              - suggest
              - sku
            default: suggest
        - $ref: '#/components/parameters/as_of'
        - $ref: '#/components/parameters/graph_version'
      responses:
        '200':
          description: Suggest hits. Empty hits is 200, not 404.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SearchResponse'
        '401':
          description: missing_key or bad_key
        '402':
          description: >-
            quota_exceeded (included and bought blocks used up, grace elapsed) or
            subscription_lapsed (the paid period ended and did not renew)
        '403':
          description: tenant_disabled or forbidden_scope
        '410':
          description: Requested snapshot is not retained (gone_graph)
        '429':
          description: rate_limited (process safety fuse)
  /v1/drugs/{id}:
    get:
      tags:
        - Index
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            example: pw_d_tylenol
        - $ref: '#/components/parameters/as_of'
        - $ref: '#/components/parameters/graph_version'
      responses:
        '200':
          description: >-
            Canonical drug. `drug.member` gives its form, strength and name in members' words (see
            MemberView).
          content:
            application/json:
              schema:
                type: object
                properties:
                  drug:
                    type: object
                    properties:
                      member:
                        $ref: '#/components/schemas/MemberView'
        '410':
          description: Requested snapshot is not retained (gone_graph)
  /v1/drugs/{id}/form-options:
    get:
      tags:
        - Index
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            example: pw_d_tylenol
        - in: query
          name: form
          description: >-
            Narrow to one form: a `value` from `form_options.forms` (for example `eye_drops`), or
            the `form` on a suggest hit.
          schema:
            type: string
            example: patch
        - $ref: '#/components/parameters/as_of'
        - $ref: '#/components/parameters/graph_version'
      responses:
        '200':
          description: Forms, strengths, routes for this id (no packages)
          content:
            application/json:
              schema:
                type: object
                properties:
                  form_options:
                    $ref: '#/components/schemas/FormOptions'
        '410':
          description: Requested snapshot is not retained (gone_graph)
  /v1/drugs/{id}/presentations:
    get:
      tags:
        - Index
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            example: pw_d_tylenol
        - in: query
          name: form
          description: >-
            Narrow to one form: a `value` from `form_options.forms` (for example `eye_drops`), or
            the `form` on a suggest hit.
          schema:
            type: string
            example: patch
        - $ref: '#/components/parameters/as_of'
        - $ref: '#/components/parameters/graph_version'
      responses:
        '200':
          description: Linked form+strength+route rows to bind a cabinet item
          content:
            application/json:
              schema:
                type: object
                properties:
                  presentations:
                    type: array
                    items:
                      $ref: '#/components/schemas/Presentation'
        '410':
          description: Requested snapshot is not retained (gone_graph)
  /v1/drugs/{id}/packages:
    get:
      tags:
        - Index
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            example: pw_d_tylenol
        - $ref: '#/components/parameters/as_of'
        - $ref: '#/components/parameters/graph_version'
      responses:
        '200':
          description: Eligible package NDCs (bottle path)
        '410':
          description: Requested snapshot is not retained (gone_graph)
  /v1/drugs/ndc/{ndc}:
    get:
      tags:
        - Index
      parameters:
        - in: path
          name: ndc
          required: true
          schema:
            type: string
            example: 50580-488-10
          description: 10, 11, 12 digit, hyphenated, or spaced
        - $ref: '#/components/parameters/as_of'
        - $ref: '#/components/parameters/graph_version'
      responses:
        '200':
          description: Package + clinical drug
        '409':
          description: Ambiguous NDC (ndc_ambiguous)
        '410':
          description: Requested snapshot is not retained (gone_graph)
  /v1/drugs/rxcui/{rxcui}:
    get:
      tags:
        - Index
      parameters:
        - in: path
          name: rxcui
          required: true
          schema:
            type: string
            example: '314076'
        - $ref: '#/components/parameters/as_of'
        - $ref: '#/components/parameters/graph_version'
      responses:
        '200':
          description: Clinical drug
        '410':
          description: Requested snapshot is not retained (gone_graph)
  /v1/recalls:
    get:
      tags:
        - Index
      parameters:
        - $ref: '#/components/parameters/as_of'
        - $ref: '#/components/parameters/graph_version'
        - in: query
          name: class
          schema:
            type: string
          description: I, II, III (also accepts Class I / 1)
        - in: query
          name: status
          schema:
            type: string
        - in: query
          name: since
          schema:
            type: string
          description: Report or initiation date YYYY-MM-DD / YYYYMMDD
        - in: query
          name: drug_id
          schema:
            type: string
        - in: query
          name: ndc
          schema:
            type: string
      responses:
        '200':
          description: First-class recall index
          content:
            application/json:
              schema:
                type: object
                properties:
                  recalls:
                    type: array
                    items:
                      $ref: '#/components/schemas/Recall'
                  graph_version:
                    type: string
                  as_of:
                    type: string
                    format: date-time
                  jurisdiction:
                    type: string
                  freshness:
                    type: string
        '410':
          description: Requested snapshot is not retained (gone_graph)
  /v1/shortages:
    get:
      tags:
        - Index
      parameters:
        - $ref: '#/components/parameters/as_of'
        - $ref: '#/components/parameters/graph_version'
        - in: query
          name: status
          schema:
            type: string
        - in: query
          name: drug_id
          schema:
            type: string
        - in: query
          name: ndc
          schema:
            type: string
      responses:
        '200':
          description: First-class shortage index
          content:
            application/json:
              schema:
                type: object
                properties:
                  shortages:
                    type: array
                    items:
                      $ref: '#/components/schemas/Shortage'
                  graph_version:
                    type: string
                  as_of:
                    type: string
                    format: date-time
                  jurisdiction:
                    type: string
                  freshness:
                    type: string
        '410':
          description: Requested snapshot is not retained (gone_graph)
  /v1/changes:
    get:
      tags:
        - Index
      parameters:
        - in: query
          name: since
          schema:
            type: string
      responses:
        '200':
          description: Changelog cursor
        '410':
          description: '`since` snapshot is not retained (gone_graph)'
  /v1/meta/freshness:
    get:
      tags:
        - Meta
      responses:
        '200':
          description: The catalog's as_of and freshness class
  /v1/members/{member_ref}:
    delete:
      tags:
        - Cabinet
      description: >-
        Needs `cabinet:write`. Deletes the member and everything held for them: items, code tags,
        idempotency records and the member token. The audit trail keeps a `member.delete` row with
        the member id only. Frees a slot in the tenant's member band.
      parameters:
        - in: path
          name: member_ref
          required: true
          schema:
            type: string
            example: their_member_id
          description: Caller member id or Pillwise member UUID
      responses:
        '204':
          description: Deleted
        '404':
          description: member_not_found
  /v1/members/{member_ref}/cabinet:
    get:
      tags:
        - Cabinet
      parameters:
        - in: path
          name: member_ref
          required: true
          schema:
            type: string
            example: their_member_id
          description: Caller member id or Pillwise member UUID
      responses:
        '200':
          description: Completed forms + codes + member_id
  /v1/members/{member_ref}/cabinet/items:
    put:
      tags:
        - Cabinet
      description: >-
        Send an Idempotency-Key to make retries safe. A repeat with the same key, member ref and
        body within 24 hours returns the first item with `Idempotent-Replayed: true`; the same key
        with a different request is a 422 `idempotency_key_reused`. Without the header every PUT
        adds an item.
      parameters:
        - in: path
          name: member_ref
          required: true
          schema:
            type: string
        - in: header
          name: Idempotency-Key
          required: false
          schema:
            type: string
            minLength: 1
            maxLength: 255
            pattern: ^[\x21-\x7e]+$
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CabinetItemWrite'
            example:
              answers:
                confirmed_name: lisinopril
                form: tablet
                strength: '10'
                strength_unit: mg
                route: oral
                frequency: daily
                prn: false
                start_on: '2026-01-15'
              codes:
                pillwise_id: pw_d_lisinopril
      responses:
        '200':
          description: Stored item and member_id
  /v1/members/{member_id}/cabinet/items/{item_id}:
    patch:
      tags:
        - Cabinet
      parameters:
        - in: path
          name: member_id
          required: true
          schema:
            type: string
            format: uuid
        - in: path
          name: item_id
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CabinetAnswers'
      responses:
        '200':
          description: Patched answers (codes stay pinned)
  /v1/members/{member_id}/cabinet/items/{item_id}/end:
    post:
      tags:
        - Cabinet
      parameters:
        - in: path
          name: member_id
          required: true
          schema:
            type: string
            format: uuid
        - in: path
          name: item_id
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Item marked ended
  /v1/members/{member_id}/cabinet/export:
    get:
      tags:
        - Cabinet
      parameters:
        - in: path
          name: member_id
          required: true
          schema:
            type: string
            format: uuid
        - in: query
          name: target
          schema:
            type: string
            enum:
              - generic
              - sunfire
            default: generic
      description: Needs the `cabinet:export` scope. Every export is audited (`cabinet.export`).
      responses:
        '200':
          description: Code bundle for downstream systems
        '400':
          description: bad_target (not one of the listed targets)
        '403':
          description: forbidden_scope (key lacks cabinet:export)
  /v1/webhooks:
    get:
      tags:
        - Cabinet
      summary: List this tenant's webhook subscriptions
      responses:
        '200':
          description: Hooks without secrets
    post:
      tags:
        - Cabinet
      summary: Register an HTTPS endpoint
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - url
              additionalProperties: false
              properties:
                url:
                  type: string
                  description: >-
                    HTTPS on a public address. Private, shared, link-local, metadata and internal
                    names are refused here and again at every delivery (the name is resolved then),
                    and redirects are not followed.
                events:
                  type: array
                  items:
                    type: string
                    enum:
                      - recall.issued
                      - cabinet.affected
                  default:
                    - cabinet.affected
      responses:
        '201':
          description: Subscription created; secret shown once
        '400':
          description: invalid_url, invalid_events, or webhook_limit
  /v1/webhooks/{id}:
    get:
      tags:
        - Cabinet
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Hook without secret
        '404':
          description: Unknown or other tenant
    delete:
      tags:
        - Cabinet
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Deleted
        '404':
          description: Unknown or other tenant
webhooks:
  cabinetAffected:
    post:
      tags:
        - Webhooks
      security: []
      summary: cabinet.affected
      description: >
        POST to your registered URL when a stored cabinet item is affected. Join `member_id` to your
        user locally. We never send your inbound member id. Optional: most tenants never subscribe.
      parameters:
        - $ref: '#/components/parameters/webhookSignature'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CabinetAffectedEvent'
            example:
              type: cabinet.affected
              tenant_id: acme
              member_id: 3fa85f64-5717-4562-b3fc-2c963f66afa6
              item_id: 7c9e6679-7425-40de-944b-e07fc1f90ae7
              codes:
                pillwise_id: pw_d_lisinopril
                rxcui: '314076'
                ndc11: 68180-0514-01
              reason: recall.issued
              recall_id: rec_example
              graph_version: '2026-01-15T12:00:00Z'
              observed_at: '2026-01-15T12:01:00Z'
      responses:
        '200':
          description: Received. Any 2xx is success.
  recallIssued:
    post:
      tags:
        - Webhooks
      security: []
      summary: recall.issued
      description: >
        POST to your registered URL for a catalog Class I recall. Optional. Subscribe only if you
        need catalog-level notices, not member lists.
      parameters:
        - $ref: '#/components/parameters/webhookSignature'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecallIssuedEvent'
            example:
              type: recall.issued
              classification: Class I
              recall_id: rec_example
              recall_number: F-1234-2026
              drug_ids:
                - pw_d_lisinopril
              codes:
                ndc11:
                  - 68180-0514-01
                rxcui:
                  - '314076'
              graph_version: '2026-01-15T12:00:00Z'
              observed_at: '2026-01-15T12:01:00Z'
      responses:
        '200':
          description: Received. Any 2xx is success.
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: pw_live_
      description: >
        Live keys start `pw_live_`; sandbox keys start `pw_test_` and share the same API with a
        500-lookup monthly cap. Minted from the account pages or by an operator (`pillwise key
        create`). Shown once. A Cabinet PUT that would add a member past the tenant's band returns
        402 member_cap_reached. Scopes: index:read, cabinet:read, cabinet:write, cabinet:export.
        Cabinet reads and exports are audited like writes. Successful Index responses may include
        X-Quota-Used, X-Quota-Limit, X-Quota-Remaining, X-Quota-Period, X-Quota-Overage-Until.
  parameters:
    as_of:
      in: query
      name: as_of
      required: false
      schema:
        type: string
      description: >
        Latest retained snapshot with built_at <= this instant. RFC3339 is an exact instant;
        YYYY-MM-DD includes that whole UTC day. Envelope freshness is `pinned`.
    graph_version:
      in: query
      name: graph_version
      required: false
      schema:
        type: string
      description: Exact snapshot pin. Unknown or pruned versions return 410 gone_graph.
    webhookSignature:
      in: header
      name: X-Pillwise-Signature
      required: true
      schema:
        type: string
        example: t=1767225600,v1=0123456789abcdef
      description: >
        `t=<unix seconds>,v1=<hex>`: v1 is HMAC-SHA256 of `<t>.<raw JSON body>` using the full
        `whsec_…` secret shown at webhook create (prefix included). Refuse a `t` more than five
        minutes old.
  schemas:
    CabinetAffectedEvent:
      type: object
      required:
        - type
        - tenant_id
        - member_id
        - item_id
        - codes
        - reason
        - recall_id
        - graph_version
        - observed_at
      properties:
        type:
          type: string
          const: cabinet.affected
        tenant_id:
          type: string
        member_id:
          type: string
          format: uuid
        item_id:
          type: string
          format: uuid
        codes:
          $ref: '#/components/schemas/CabinetItemWrite/properties/codes'
        reason:
          type: string
          const: recall.issued
        recall_id:
          type: string
        graph_version:
          type: string
        observed_at:
          type: string
          format: date-time
    RecallIssuedEvent:
      type: object
      required:
        - type
        - classification
        - recall_id
        - drug_ids
        - codes
        - graph_version
        - observed_at
      properties:
        type:
          type: string
          const: recall.issued
        classification:
          type: string
          example: Class I
        recall_id:
          type: string
        recall_number:
          type: string
        drug_ids:
          type: array
          items:
            type: string
        codes:
          type: object
          required:
            - ndc11
            - rxcui
          properties:
            ndc11:
              type: array
              items:
                type: string
            rxcui:
              type: array
              items:
                type: string
        graph_version:
          type: string
        observed_at:
          type: string
          format: date-time
    CabinetItemWrite:
      type: object
      required:
        - answers
      properties:
        answers:
          type: object
          required:
            - confirmed_name
          properties:
            confirmed_name:
              type: string
            form:
              type: string
            strength:
              type: string
            strength_unit:
              type: string
            route:
              type: string
            frequency:
              type: string
            prn:
              type: boolean
            quantity:
              type: string
            days_supply:
              type: integer
            start_on:
              type: string
              format: date
            end_on:
              type: string
              format: date
            instructions:
              type: string
            entered_by:
              type: string
              enum:
                - self
                - caregiver
                - clinician
                - claim
                - barcode
        codes:
          type: object
          properties:
            rxcui:
              type: string
            rxcui_ingredient:
              type: string
            ndc11:
              type: string
            ndc_basis:
              type: string
              enum:
                - asserted
                - representative
            ndc_candidates:
              type: array
              items:
                type: object
                properties:
                  ndc11:
                    type: string
                  ndc_display:
                    type: string
                  description:
                    type: string
            ndc_fda:
              type: string
            ndc_display:
              type: string
            unii:
              type: array
              items:
                type: string
            spl_set_id:
              type: string
            application_number:
              type: string
            te_code:
              type: string
            pillwise_id:
              type: string
        resolved:
          type: object
          properties:
            pick_level:
              type: string
            search_hit_id:
              type: string
            confidence:
              type: string
            jurisdiction:
              type: string
    CabinetAnswers:
      type: object
      properties:
        confirmed_name:
          type: string
        form:
          type: string
        strength:
          type: string
        frequency:
          type: string
        instructions:
          type: string
        status:
          type: string
    Freshness:
      type: object
      properties:
        class:
          type: string
          enum:
            - current
            - aging
            - stale
            - degraded
            - pinned
        as_of:
          type: string
          format: date-time
        graph_version:
          type: string
        jurisdiction:
          type: string
    Recall:
      type: object
      required:
        - id
        - classification
        - drug_ids
      properties:
        id:
          type: string
        classification:
          type: string
          enum:
            - I
            - II
            - III
        status:
          type: string
        recall_number:
          type: string
        reason:
          type: string
        recalling_firm:
          type: string
        report_date:
          type: string
        initiation_date:
          type: string
        product_description:
          type: string
        ndc11:
          type: array
          items:
            type: string
        product_ndc:
          type: array
          items:
            type: string
        drug_ids:
          type: array
          items:
            type: string
    Shortage:
      type: object
      required:
        - id
        - drug_ids
      properties:
        id:
          type: string
        status:
          type: string
        update_type:
          type: string
        generic_name:
          type: string
        strength:
          type: string
        presentation:
          type: string
        ndc11:
          type: array
          items:
            type: string
        application_numbers:
          type: array
          items:
            type: string
        drug_ids:
          type: array
          items:
            type: string
        conflicts:
          type: array
          items:
            type: object
    ChangeSet:
      type: object
      required:
        - since
        - to
        - added
        - removed
      properties:
        since:
          type: string
        to:
          type: string
        added:
          type: array
          items:
            type: string
        removed:
          type: array
          items:
            type: string
        changed:
          type: array
          items:
            type: object
            required:
              - id
              - reasons
            properties:
              id:
                type: string
              reasons:
                type: array
                items:
                  type: string
                  enum:
                    - recall_added
                    - recall_removed
                    - shortage_added
                    - shortage_removed
    SearchResponse:
      type: object
      required:
        - hits
        - coverage
        - graph_version
        - as_of
        - jurisdiction
        - freshness
      properties:
        hits:
          type: array
          items:
            $ref: '#/components/schemas/SearchHit'
        coverage:
          type: string
          enum:
            - cpc_current
        graph_version:
          type: string
        as_of:
          type: string
          format: date-time
        jurisdiction:
          type: string
        freshness:
          type: string
    SearchHit:
      type: object
      required:
        - id
        - display_name
        - matched_term
        - match_kind
        - cabinet_eligible
      properties:
        id:
          type: string
        display_name:
          type: string
        subtitle:
          type: string
        matched_term:
          type: string
        match_kind:
          type: string
          enum:
            - preferred
            - synonym
            - brand
            - proprietary
            - identifier
        pick_level:
          type: string
          enum:
            - brand
            - ingredient
            - clinical_drug
            - packaged_product
        presentation_count:
          type: integer
        tty:
          type: string
        rxcui:
          type: string
        form:
          type: string
          description: >-
            On a suggest hit, the form the member's words chose ("nicotine patch" -> `patch`): pass
            it as `?form=` to form-options or presentations. On `intent=sku` hits, the drug's form
            value.
        form_label:
          type: string
          example: Patch
        strength:
          type: string
        strength_label:
          type: string
          example: 14 mg per 24 hours
        route:
          type: string
        cabinet_eligible:
          type: boolean
        form_options:
          $ref: '#/components/schemas/FormOptions'
        codes:
          type: object
          properties:
            rxcui:
              type: string
            ndc11:
              type: array
              items:
                type: string
            unii:
              type: array
              items:
                type: string
            spl_set_id:
              type: string
            application_number:
              type: string
            te_code:
              type: string
    FormChoice:
      type: object
      required:
        - value
        - label
        - group
        - dose_unit
      properties:
        value:
          type: string
          example: eye_drops
        label:
          type: string
          example: Eye drops
        group:
          type: string
          description: What kind of thing it is, for tailoring the next questions.
          enum:
            - tablet
            - capsule
            - oral_liquid
            - oral_other
            - mouth
            - skin
            - eye
            - ear
            - nose
            - inhaled
            - patch
            - injection
            - rectal
            - vaginal
            - implant
            - clinic
            - pack
            - other
        dose_unit:
          type: string
          description: What one dose is counted in.
          example: drop
        route:
          type: string
          example: ophthalmic
    Choice:
      type: object
      required:
        - value
        - label
      properties:
        value:
          type: string
          example: inhaled
        label:
          type: string
          example: Inhaled
    StrengthChoice:
      type: object
      required:
        - value
        - label
      properties:
        value:
          type: string
          example: 0.05 MG/ML
        label:
          type: string
          example: 0.005%
        forms:
          type: array
          description: >-
            The form values this strength comes in, so a client holding every form's strengths can
            narrow them without another call.
          items:
            type: string
          example:
            - eye_drops
    Presentation:
      type: object
      required:
        - id
      description: >-
        One form + strength + route to bind. Send `id` as `codes.pillwise_id` with `form` and
        `strength` (values or labels).
      properties:
        id:
          type: string
        rxcui:
          type: string
        form:
          type: string
          example: inhaler
        form_label:
          type: string
          example: Inhaler
        form_group:
          type: string
          example: inhaled
        dose_unit:
          type: string
          example: puff
        strength:
          type: string
          example: 0.09 MG/ACTUAT
        strength_label:
          type: string
          example: 90 mcg per puff
        route:
          type: string
          example: inhaled
        route_label:
          type: string
          example: Inhaled
        brand:
          type: string
        generic:
          type: string
    MemberView:
      type: object
      required:
        - display_name
      description: The drug in members' words, worked out when the catalog is built.
      properties:
        display_name:
          type: string
          example: Albuterol inhaler, 90 mcg per puff
        form:
          type: string
        form_label:
          type: string
        form_group:
          type: string
        dose_unit:
          type: string
        dose_form:
          type: string
          description: The standard dose form the member-facing form came from.
          example: Metered Dose Inhaler
        strength:
          type: string
        strength_label:
          type: string
        route:
          type: string
        route_label:
          type: string
        volume_ml:
          type: number
          description: A syringe's fill; the same strength in another fill is another dose.
        labels_version:
          type: integer
          description: The wording rules this view was worked out with.
    FormOptions:
      type: object
      description: >-
        Each choice has a `value` to send back (`?form=`, Cabinet `answers.form` /
        `answers.strength`) and a `label` to show. Only products on the US market are listed (a
        package or a current label). Forms come most sold first, with vials, packs and clinic-only
        forms last; strengths follow their form, then read by amount, and name the forms they come
        in.
      properties:
        forms:
          type: array
          items:
            $ref: '#/components/schemas/FormChoice'
        strengths:
          type: array
          items:
            $ref: '#/components/schemas/StrengthChoice'
        routes:
          type: array
          items:
            $ref: '#/components/schemas/Choice'
        packages:
          type: array
          items:
            type: object
            properties:
              ndc_raw:
                type: string
              ndc11:
                type: string
              display:
                type: string
              description:
                type: string
