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

# Submit intake answers (you collect them)

> **Vita Clinic only.** Requires the `intakes:write` scope. Use this when you collect the
questionnaire yourself; to have the patient fill it in on our page, use
[Send a hosted intake link](/api-reference/intakes/send-a-hosted-intake-link) instead.
Referral partners use [Submit referral intake answers](/api-reference/intakes/submit-referral-intake-answers-you-collect-them).

**Required:** `category` (`async` or `hormone`), `responses` (the six required answers
below, plus the rest of the questionnaire) and the patient, as `patient_id` **or**
`external_patient_id`. `external_intake_id` is optional but recommended.

Identify the patient with either `patient_id` (UUID, or a
`sandbox_pat_…` id when using a test key) or `external_patient_id`.
A patient belonging to another organization resolves as
`404 patient_not_found`. Supplying `external_intake_id` makes the call
idempotent.

If the patient has an order that is waiting on a health intake, these answers
**complete that order's intake** (the response `id` is that intake and it includes the
`order_id`): the order moves to prescriber review and the request is sent to the doctor
network. Otherwise a standalone intake is created. A request that fails creates nothing,
and its `external_intake_id` can be sent again straight away.




## OpenAPI

````yaml /openapi.yaml post /intakes
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`.
  - name: Partners
    description: >-
      Consultant (partner) program. **Available only to organizations enrolled
      in the program; every other organization receives `403 not_available`.**
      Create your consultants, assign customers to them and read the commission
      credit tracked on their orders. Requires `partners:read` /
      `partners:write`. Commissions are tracked for your own payouts — VitaRelay
      does not pay consultants.
paths:
  /intakes:
    post:
      tags:
        - Intakes
      summary: Submit intake answers (you collect them)
      description: >
        **Vita Clinic only.** Requires the `intakes:write` scope. Use this when
        you collect the

        questionnaire yourself; to have the patient fill it in on our page, use

        [Send a hosted intake
        link](/api-reference/intakes/send-a-hosted-intake-link) instead.

        Referral partners use [Submit referral intake
        answers](/api-reference/intakes/submit-referral-intake-answers-you-collect-them).


        **Required:** `category` (`async` or `hormone`), `responses` (the six
        required answers

        below, plus the rest of the questionnaire) and the patient, as
        `patient_id` **or**

        `external_patient_id`. `external_intake_id` is optional but recommended.


        Identify the patient with either `patient_id` (UUID, or a

        `sandbox_pat_…` id when using a test key) or `external_patient_id`.

        A patient belonging to another organization resolves as

        `404 patient_not_found`. Supplying `external_intake_id` makes the call

        idempotent.


        If the patient has an order that is waiting on a health intake, these
        answers

        **complete that order's intake** (the response `id` is that intake and
        it includes the

        `order_id`): the order moves to prescriber review and the request is
        sent to the doctor

        network. Otherwise a standalone intake is created. A request that fails
        creates nothing,

        and its `external_intake_id` can be sent again straight away.
      operationId: createIntake
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IntakeCreate'
      responses:
        '200':
          description: Idempotent replay of an existing `external_intake_id`.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/IntakeCreated'
        '201':
          description: Intake created.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/XRateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/XRateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/XRateLimitReset'
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/IntakeCreated'
                  sandbox:
                    type: boolean
                    description: Present and `true` when created with a `vr_test_` key.
        '400':
          $ref: '#/components/responses/InvalidBody'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/PatientNotFound'
        '409':
          $ref: '#/components/responses/DuplicateRequest'
        '422':
          $ref: '#/components/responses/ValidationFailed'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
components:
  schemas:
    IntakeCreate:
      type: object
      required:
        - category
        - responses
      description: Exactly one of `patient_id` or `external_patient_id` is required.
      properties:
        patient_id:
          type: string
          description: Patient UUID, or a `sandbox_pat_…` id when using a test key.
        external_patient_id:
          type: string
        category:
          type: string
          enum:
            - async
            - hormone
          description: >-
            `hormone` for hormone-therapy intakes, `async` for everything else.
            Any other value returns `422 validation_failed` before anything is
            created.
        form_type:
          type: string
        responses:
          allOf:
            - $ref: '#/components/schemas/IntakeRequiredAnswers'
          description: >-
            The questionnaire answers. The six required fields must be present
            or the request is rejected `422`. Send the rest of the questionnaire
            too, using the keys in the [Intake answers
            reference](/getting-started/intake-responses).
        external_intake_id:
          type: string
          description: Your identifier for this intake. Acts as an idempotency key.
      example:
        external_patient_id: clinic-patient-001
        category: async
        form_type: glp1_screening
        responses:
          height_cm: 170
          weight_kg: 92
          goal: weight loss
        external_intake_id: clinic-intake-4471
    IntakeCreated:
      type: object
      properties:
        id:
          type: string
          description: Intake UUID (or a `sandbox_int_…` id with a test key).
        external_intake_id:
          type:
            - string
            - 'null'
        order_id:
          type:
            - string
            - 'null'
          format: uuid
          description: >-
            Present when these answers completed an order's waiting intake; the
            order they now belong to.
        warnings:
          type: object
          description: >-
            How the submitted `responses` compare with the intake form. Never
            blocks the request. A key that is not a question id is not matched,
            so that question shows "Not answered" on the prescriber's document.
            See [Intake answers reference](/getting-started/intake-responses).
          properties:
            complete:
              type: boolean
              description: True when there is nothing in the three lists below.
            unknown_keys:
              type: array
              items:
                type: string
              description: Keys that are not a question id on the form.
            missing_required:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                  label:
                    type: string
              description: >-
                Required questions that apply to this patient and have no
                answer.
            invalid_values:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                  expected:
                    type: string
              description: Answers whose value or format is not allowed for that question.
      example:
        id: sandbox_int_b3d8e7a1
        external_intake_id: clinic-intake-4471
        warnings:
          complete: false
          unknown_keys:
            - height_in
          missing_required:
            - id: nicotine
              label: Nicotine use
          invalid_values:
            - id: gender
              expected: 'one of: female, male, nonbinary, prefer_not_say'
    IntakeRequiredAnswers:
      type: object
      additionalProperties: true
      required:
        - treatment_need
        - treatment_reason
        - current_meds
        - has_allergies
        - attest_accurate
        - attest_telehealth
      properties:
        treatment_need:
          description: Primary treatment goal. An array of strings, or a non-empty string.
          oneOf:
            - type: array
              minItems: 1
              items:
                type: string
            - type: string
              minLength: 1
          example:
            - weight_loss
        treatment_reason:
          type: string
          minLength: 1
          description: Why the patient wants treatment. Non-empty.
        current_meds:
          description: >-
            Current medications. Required to be present; `""` or `"none"` are
            valid answers.
          example: none
        has_allergies:
          description: Allergies answer. Required to be present; `"no"` is a valid answer.
          example: 'no'
        attest_accurate:
          type: boolean
          enum:
            - true
          description: Must be the boolean `true`.
        attest_telehealth:
          type: boolean
          enum:
            - true
          description: Must be the boolean `true` (telehealth consent).
    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
  headers:
    XRateLimitLimit:
      description: Requests permitted in the current window.
      schema:
        type: integer
    XRateLimitRemaining:
      description: Requests remaining in the current window.
      schema:
        type: integer
    XRateLimitReset:
      description: Unix timestamp when the current window resets.
      schema:
        type: integer
    RetryAfter:
      description: Seconds to wait before retrying.
      schema:
        type: integer
  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'
    PatientNotFound:
      description: >-
        Patient not found or owned by another organization
        (`patient_not_found`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    DuplicateRequest:
      description: >-
        A conflicting request with the same external id is in flight
        (`duplicate_request`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    ValidationFailed:
      description: Body parsed but failed validation (`validation_failed`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    RateLimited:
      description: Per-key rate limit exceeded (`rate_limited`). Default 60 writes/minute.
      headers:
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        X-RateLimit-Limit:
          $ref: '#/components/headers/XRateLimitLimit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/XRateLimitRemaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/XRateLimitReset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    InternalError:
      description: Unexpected server error (`internal_error`).
      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.