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

# Create a consult-first request

> **Vita Clinic only.** Requires the `orders:write` scope, and the clinic must have consult-first checkout switched on (otherwise `409 consult_first_not_available`).

**Consult first, medication after.** The patient pays only the doctor consult now and is shown an *estimate* for the medication and shipping. The doctor
network then reviews the request. When the doctor approves, one medication order is built from the script the doctor actually signed:

- If it is the same medication (any dose), the total is within the estimate, the request has `authorize_auto_charge: true`, and the patient has a saved card,
  the card is charged automatically and the order is placed.
- Otherwise nothing is charged: the request moves to `awaiting_patient_checkout` with a `proposal`, and you call
  `POST /consult-requests/{id}/approve` once the patient agrees.
- If the doctor declines, the request ends as `declined`, no medication order is ever created, and nothing more is charged.
  **The consult fee is not refundable.**

A proposal never expires. Prices come from the clinic's patient shop, never from the body. Prescription items only: order anything else with `POST /orders`.

**Who pays the consult** (`billing_mode`):

- `patient_checkout` (default): the response carries a `checkout` object. Tokenize the patient's card with Accept.js and send it to
  `POST /orders/{consult.order_id}/pay`. The card is always saved to the patient, because the medication is charged to it later.
- `card_on_file`: the clinic's card on file pays the consult immediately. The medication is still the patient's to pay (a saved card, or a new one at approval).

Status changes are delivered as `consult_request.updated` webhooks. Send `external_request_id` to make the call idempotent: a repeat returns the same request.




## OpenAPI

````yaml /openapi.yaml post /consult-requests
openapi: 3.1.0
info:
  title: VitaRelay API
  version: 2.0.0
  description: |
    The VitaRelay REST API.

    **Base URL:** `https://vitarelay.com/api/public/v1`

    ## Authentication
    All requests require a Bearer API key:

    ```text
    Authorization: Bearer vr_live_xxxxxxxxxxxxxxxx
    ```

    Keys are issued per organization. `vr_live_` keys act on production data.
    `vr_test_` keys operate a fully-isolated **sandbox** (see below).

    ## Pagination
    List endpoints are cursor-based. Pass `limit` (default 50, max 100) and
    `cursor` (the `next_cursor` from the previous page). When `next_cursor`
    is `null` there are no further pages. All resource ids are UUIDs.

    ## Idempotency
    Write endpoints accept your own external identifiers —
    `external_patient_id`, `external_intake_id`, `external_order_id`. These act
    as idempotency keys: replaying a request with the same external id returns
    the original resource (HTTP `200` instead of `201`) rather than creating a
    duplicate.

    ## Rate limiting
    Write endpoints are rate limited per API key — default **60 requests per
    minute** (fixed window). Exceeding the limit returns `429 rate_limited`
    with `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining` and
    `X-RateLimit-Reset` headers. Read endpoints are not rate limited in v1.

    ## Sandbox
    With a `vr_test_` key, writes never create real patients, intakes or orders
    and never charge a card. They return synthetic ids (`sandbox_pat_…`,
    `sandbox_int_…`, `sandbox_ord_…`) and include `"sandbox": true` in the
    response. Sandbox objects advance through a simulated lifecycle that fires
    real webhook deliveries to your endpoint (`intake.reviewed`, `order.paid` →
    `order.shipped` → `order.delivered`) with `"sandbox": true` in the payload.
    In sandbox, reference a patient by `external_patient_id` or by the returned
    `sandbox_pat_…` id.

    ## Webhooks
    Outbound webhooks are configured in the VitaRelay admin UI (there is no
    webhook management API). Events: `order.paid`, `order.shipped`,
    `order.delivered`, `order.exception`, `intake.reviewed`. Each delivery is
    signed with HMAC-SHA256 over the raw body and carries the headers
    `X-VitaRelay-Signature: sha256=<hex>`, `X-VitaRelay-Event`,
    `X-VitaRelay-Delivery` (event id) and `X-VitaRelay-Timestamp`. Failed
    deliveries are retried with exponential backoff and dead-lettered after the
    maximum attempts.
  contact:
    name: VitaRelay Support
    email: info@vitarelay.com
servers:
  - url: https://vitarelay.com/api/public/v1
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Patients
    description: Patient records scoped to your organization.
  - name: Intakes
    description: >-
      The health questionnaire a prescriber reviews. There are two ways to
      complete one, and each has a Vita Clinic endpoint and a referral (Rx)
      endpoint. **Patient fills it in on our page:** `POST
      /patients/{patientId}/intake/send-link` (Vita Clinic) or `POST
      /rx/patients/{patientId}/intake` (referral). **You collect the answers and
      send them to us:** `POST /intakes` (Vita Clinic) or `POST
      /rx/patients/{patientId}/intake/submit` (referral). Check progress with
      `GET /patients/{patientId}/intake/status` and the `intake.submitted` /
      `intake.reviewed` webhooks. See [Intakes and prescriber
      review](/getting-started/intakes-and-prescriptions) and the [Intake
      answers reference](/getting-started/intake-responses).
  - name: Orders
    description: >-
      Orders and fulfillment status. Two billing modes: `card_on_file` (default)
      charges the clinic's stored card synchronously, and `patient_checkout`
      creates the order unpaid and returns a checkout block so the patient can
      pay on your own page via Authorize.Net Accept.js (see POST
      /orders/{id}/pay).
  - name: Products
    description: Catalog products available to your organization.
  - name: Prescriptions
    description: >-
      Prescriptions written by your organization (read only). Refer patients
      into the Internal Doctor Network (IDN). You refer; an IDN doctor evaluates
      and prescribes. Requires IDN activation and rx scopes.
  - name: Storefront
    description: >-
      Build and manage your clinic's patient storefront — settings, curated
      product listings and bundles. Requires the `shop:read` / `shop:write`
      scopes.
  - name: Subscriptions
    description: >-
      Patient-shop subscription lifecycle. Requires `subscriptions:read` or
      `subscriptions:write`.
paths:
  /consult-requests:
    post:
      tags:
        - Orders
      summary: Create a consult-first request
      description: >
        **Vita Clinic only.** Requires the `orders:write` scope, and the clinic
        must have consult-first checkout switched on (otherwise `409
        consult_first_not_available`).


        **Consult first, medication after.** The patient pays only the doctor
        consult now and is shown an *estimate* for the medication and shipping.
        The doctor

        network then reviews the request. When the doctor approves, one
        medication order is built from the script the doctor actually signed:


        - If it is the same medication (any dose), the total is within the
        estimate, the request has `authorize_auto_charge: true`, and the patient
        has a saved card,
          the card is charged automatically and the order is placed.
        - Otherwise nothing is charged: the request moves to
        `awaiting_patient_checkout` with a `proposal`, and you call
          `POST /consult-requests/{id}/approve` once the patient agrees.
        - If the doctor declines, the request ends as `declined`, no medication
        order is ever created, and nothing more is charged.
          **The consult fee is not refundable.**

        A proposal never expires. Prices come from the clinic's patient shop,
        never from the body. Prescription items only: order anything else with
        `POST /orders`.


        **Who pays the consult** (`billing_mode`):


        - `patient_checkout` (default): the response carries a `checkout`
        object. Tokenize the patient's card with Accept.js and send it to
          `POST /orders/{consult.order_id}/pay`. The card is always saved to the patient, because the medication is charged to it later.
        - `card_on_file`: the clinic's card on file pays the consult
        immediately. The medication is still the patient's to pay (a saved card,
        or a new one at approval).


        Status changes are delivered as `consult_request.updated` webhooks. Send
        `external_request_id` to make the call idempotent: a repeat returns the
        same request.
      operationId: createConsultRequest
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - items
                - shipping
              properties:
                patient_id:
                  type: string
                  format: uuid
                external_patient_id:
                  type: string
                  description: >-
                    The id you used when creating the patient. One of
                    `patient_id` or `external_patient_id` is required.
                external_request_id:
                  type: string
                  maxLength: 128
                items:
                  type: array
                  minItems: 1
                  items:
                    type: object
                    required:
                      - product_id
                      - quantity
                    properties:
                      product_id:
                        type: string
                        format: uuid
                      quantity:
                        type: integer
                        minimum: 1
                shipping:
                  type: object
                  required:
                    - line1
                    - city
                    - state
                    - postal_code
                  properties:
                    line1:
                      type: string
                    line2:
                      type: string
                    city:
                      type: string
                    state:
                      type: string
                    postal_code:
                      type: string
                    country:
                      type: string
                      description: Two-letter country code (default `US`).
                shipping_method:
                  type: string
                  description: >-
                    The delivery speed the patient picked (`ground` or
                    `overnight`).
                billing_mode:
                  type: string
                  enum:
                    - patient_checkout
                    - card_on_file
                authorize_auto_charge:
                  type: boolean
                  description: >-
                    The patient agreed to have their saved card charged for the
                    medication and shipping, up to the estimate, once the doctor
                    approves. Show the returned `authorization_text` where they
                    decide. Default `false`.
      responses:
        '200':
          description: A repeat of an earlier call with the same `external_request_id`.
        '201':
          description: The request was created.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    allOf:
                      - $ref: '#/components/schemas/ConsultRequest'
                      - type: object
                        properties:
                          consult:
                            type: object
                            properties:
                              order_id:
                                type: string
                                format: uuid
                              amount_cents:
                                type: integer
                              paid:
                                type: boolean
                          authorization_text:
                            type: string
                          checkout:
                            type: object
                            description: >-
                              Present for `patient_checkout`. The same Accept.js
                              object as `POST /orders`.
        '400':
          $ref: '#/components/responses/InvalidBody'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          description: >-
            The clinic's card on file is missing or was declined (`card_on_file`
            only).
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Consult-first checkout is not turned on for this clinic.
        '422':
          description: >-
            Validation failed, the patient's intake is not complete, a product
            is not priced, or the request cannot be placed.
components:
  schemas:
    ConsultRequest:
      type: object
      properties:
        id:
          type: string
          format: uuid
        external_request_id:
          type:
            - string
            - 'null'
        patient_id:
          type: string
          format: uuid
        status:
          $ref: '#/components/schemas/ConsultRequestStatus'
        billing_mode:
          type: string
          enum:
            - patient
            - clinic_card
        estimate:
          type: object
          properties:
            medication_cents:
              type: integer
            shipping_cents:
              type: integer
            total_cents:
              type: integer
        auto_charge_authorized:
          type: boolean
        consult_order_id:
          type:
            - string
            - 'null'
          format: uuid
        medication_order_id:
          type:
            - string
            - 'null'
          format: uuid
        proposal:
          type:
            - object
            - 'null'
          description: What the doctor's script produced, once there is one.
          properties:
            lines:
              type: array
              items:
                type: object
                properties:
                  product_id:
                    type: string
                    format: uuid
                  name:
                    type: string
                  quantity:
                    type: integer
                  unit_price_cents:
                    type: integer
            shipping_cents:
              type: integer
            total_cents:
              type: integer
            needs_approval_reasons:
              type: array
              items:
                type: string
                enum:
                  - not_authorized
                  - different_medication
                  - over_estimate
                  - no_saved_card
                  - nothing_prescribed
        failure_reason:
          type:
            - string
            - 'null'
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    ConsultRequestStatus:
      type: string
      enum:
        - awaiting_consult_payment
        - awaiting_doctor
        - declined
        - proposal_ready
        - charging
        - awaiting_patient_checkout
        - completed
        - charge_failed
        - cancelled
      description: >
        `awaiting_consult_payment` the consult is not paid yet;
        `awaiting_doctor` the doctor network is reviewing; `declined` no
        prescription (final);

        `awaiting_patient_checkout` the doctor approved and the patient must
        approve the medication order; `charging` the card is being charged;

        `charge_failed` the card failed, the patient can try again; `completed`
        the medication order was placed and paid (final); `cancelled` (final).
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: |
                One of `invalid_body`, `validation_failed`, `unauthorized`,
                `insufficient_scope`, `not_vita_clinic`, `patient_not_found`,
                `product_not_found`, `product_not_allowed`, `duplicate_request`,
                `rate_limited`, `card_required`, `payment_declined`,
                `internal_error`, `not_found`.
            message:
              type: string
      example:
        error:
          code: validation_failed
          message: One or more fields are invalid
  responses:
    InvalidBody:
      description: >-
        Malformed request or invalid path parameter (`invalid_body`,
        `invalid_id`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: Missing, invalid or revoked API key (`unauthorized`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Forbidden:
      description: |
        The key lacks the required scope (`insufficient_scope`) or its
        organization is not an active Vita Clinic (`not_vita_clinic`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    NotFound:
      description: Resource not found or not visible to your organization (`not_found`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: |
        API key issued by VitaRelay. Send as `Authorization: Bearer vr_live_…`
        (production) or `Authorization: Bearer vr_test_…` (sandbox).

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.