> ## 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 or update a provider

> Scope `customers:write`. Creates the provider (201) with its portal invite, or updates it (200) when `external_id`
is already known. The provider is validated like one added by hand: a valid address, phone and contact email, and
prescribers (NPI, state license, DEA for controlled substances) when it has any. Without prescribers it is a tele
clinic that uses the doctor network. Set `send_invite` to `false` to create it without emailing the invite.
The response `id` is the VitaRelay id to store in your CRM.




## OpenAPI

````yaml /pharmacy-api/customers-openapi.yaml post /customers
openapi: 3.1.0
info:
  title: Pharmacy Customers API
  version: 1.0.0
  description: >
    Connect your CRM (GoHighLevel and similar) to your pharmacy's customers:
    send a provider when your CRM marks it as converted,

    get webhooks when its orders ship or deliver and when it needs a reorder,
    has lapsed or (for a partner rep) has gone

    dormant, and export your providers.


    **Authentication.** `Authorization: Bearer vr_live_...`. A key belongs to
    your pharmacy and carries `customers:read`

    and/or `customers:write` (create it under **Settings → API keys**). Test
    keys (`vr_test_...`) can read; their writes are

    acknowledged with `sandbox: true` and change nothing. Your pharmacy must
    have customers enabled.


    **Ids.** A customer's `id` is its VitaRelay organization id. `external_id`
    is your CRM's own id for the provider (the

    GoHighLevel contact id); sending the same `external_id` again updates the
    provider and never creates a second one.


    **Webhooks.** Subscribe with `POST /customers/webhooks`. Each request is
    signed:

    `X-Webhook-Signature: sha256=<hex HMAC-SHA256 of the exact request body,
    keyed with your endpoint secret>`, with

    `X-Webhook-Event`, `X-Webhook-Delivery` and `X-Webhook-Timestamp` headers.
    Delivery is at least once with exponential

    backoff for about a day; deduplicate on `X-Webhook-Delivery`. Events:
    `order.shipped`, `order.delivered`,

    `order.cancelled`, `customer.reorder_due`, `customer.lapsed`,
    `partner.dormant`. Use `POST /customers/webhooks/ping`

    to send yourself a test event.


    **Lifecycle windows** are days since a customer's last order (reorder due
    30, lapsed 60, partner dormant 90 by default;

    change them with `PUT /customers/settings`). Each event fires once per last
    order. Connecting a CRM does not send events

    for customers that are already past a window: read their `lifecycle_status`
    from `GET /customers`.
servers:
  - url: https://vitarelay.com/api/public/v1
    description: Production
security:
  - bearer: []
paths:
  /customers:
    post:
      summary: Create or update a provider
      description: >
        Scope `customers:write`. Creates the provider (201) with its portal
        invite, or updates it (200) when `external_id`

        is already known. The provider is validated like one added by hand: a
        valid address, phone and contact email, and

        prescribers (NPI, state license, DEA for controlled substances) when it
        has any. Without prescribers it is a tele

        clinic that uses the doctor network. Set `send_invite` to `false` to
        create it without emailing the invite.

        The response `id` is the VitaRelay id to store in your CRM.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerBody'
            example:
              external_id: ghl-contact-4471
              name: Jason Meds
              contact:
                first_name: Jason
                last_name: Tests
                email: owner@jasonmeds.com
                phone: (229) 719-4222
              address:
                line1: 12 Main St
                city: Albany
                state: GA
                zip: '31701'
              consent:
                sms:
                  granted: true
                  at: '2026-10-19T14:02:00Z'
                  source: ghl-form
                email:
                  granted: true
              attribution:
                source: facebook
                campaign: oct-launch
              booking_ref: bk_90812
      responses:
        '200':
          description: Updated (the external_id was already known)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerResult'
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerResult'
        '409':
          description: >-
            The contact email already has an account or a pending invitation
            (`duplicate_request`)
        '422':
          description: A field failed validation (`validation_failed`)
components:
  schemas:
    CustomerBody:
      type: object
      required:
        - external_id
        - name
        - contact
        - address
      properties:
        external_id:
          type: string
          description: Your CRM's id for the provider (the GoHighLevel contact id).
        name:
          type: string
          description: The clinic or practice name.
        contact:
          type: object
          required:
            - email
          properties:
            first_name:
              type: string
            last_name:
              type: string
            email:
              type: string
              format: email
            phone:
              type: string
        address:
          type: object
          required:
            - line1
            - city
            - state
            - zip
          properties:
            line1:
              type: string
            line2:
              type: string
            city:
              type: string
            state:
              type: string
              description: Two-letter state.
            zip:
              type: string
        phone:
          type: string
          description: The clinic's phone. Defaults to the contact's phone.
        prescribers:
          type: array
          items:
            type: object
            required:
              - full_name
              - specialty
              - npi_number
              - license_state
              - license_number
            properties:
              full_name:
                type: string
              specialty:
                type: string
              npi_number:
                type: string
              license_state:
                type: string
              license_number:
                type: string
              can_prescribe_controlled:
                type: boolean
              dea_number:
                type: string
        consent:
          $ref: '#/components/schemas/Consent'
        attribution:
          type: object
          additionalProperties:
            type: string
          description: Free-form source details
          up to 20 fields.: null
        booking_ref:
          type: string
        send_invite:
          type: boolean
          default: true
    CustomerResult:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/Customer'
        created:
          type: boolean
        invite_emailed:
          type: boolean
    Consent:
      type: object
      properties:
        sms:
          $ref: '#/components/schemas/ConsentItem'
        email:
          $ref: '#/components/schemas/ConsentItem'
    Customer:
      type: object
      properties:
        id:
          type: string
          format: uuid
        external_id:
          type: string
          nullable: true
        name:
          type: string
        kind:
          type: string
          enum:
            - practice
            - tele_clinic
        status:
          type: string
          enum:
            - invited
            - active
            - archived
        signup:
          type: object
          properties:
            status:
              type: string
              enum:
                - pending
                - accepted
            invite_emailed:
              type: boolean
        contact:
          type: object
        address:
          type: object
        prescribers:
          type: integer
        consent:
          type: object
        attribution:
          type: object
          nullable: true
        booking_ref:
          type: string
          nullable: true
        orders:
          type: object
          description: >-
            count, last_order_id, last_order_number, last_order_at,
            last_order_total_cents
        days_since_last_order:
          type: integer
          nullable: true
        lifecycle_status:
          type: string
          enum:
            - never_ordered
            - active
            - reorder_due
            - lapsed
        created_at:
          type: string
          format: date-time
    ConsentItem:
      type: object
      required:
        - granted
      properties:
        granted:
          type: boolean
        at:
          type: string
          format: date-time
          description: When consent was given. Defaults to now.
        source:
          type: string
  securitySchemes:
    bearer:
      type: http
      scheme: bearer

````

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