Partner Integration Guide

How your platform connects to Beluga's nationwide physician network. You own the patient experience end to end; Beluga handles clinical review, prescribing, and compliance behind a simple API. This guide walks through exactly what you build, what you send, and what you receive at each step.

AuthorizationAuthorization: Bearer {API_KEY} on every request
Stagingapi-staging.belugahealth.com
Productionapi.belugahealth.com
ProtocolHTTPS only. Endpoint paths and keys are shared in onboarding.

How onboarding works

  1. 1
    Kick off in Slack. Your shared channel with Beluga's onboarding team runs the whole process, from credentials to go-live.
  2. 2
    Build intake, submit a test visit. Implement Steps 1 and 2 below against staging and send a test payload.
  3. 3
    Beluga verifies your integration. We confirm the payload, questionnaire wiring, and webhooks, then enable production.
  4. 4
    Add-ons switch on in sequence. Sync video, pharmacy fulfillment, labs, and AutoRx are enabled as your program needs them.
Implement the steps in order; each capability is confirmed before the next is switched on. That sequencing is what keeps the integration fast and drop-free.

Your integration journey

1

Build your patient intake

Runs entirely in your product. Identical architecture for every vertical you launch with us.
Core

You build

  • A questionnaire flow with a consent checkbox linking to Beluga's telemedicine consent and privacy practices. The checkbox gates your Next button; you will send consentsSigned: true.
  • Branching logic that ends in the patient's medication choice(s). Each becomes a patientPreference object: name, strength, quantity, refills, daysSupply, medId. Your Beluga customer dashboard is the source of truth for medIds: pull your program's catalog from there rather than hardcoding.
  • Demographics: first and last name (100 char max), DOB MM/DD/YYYY (verify 18+ client-side), phone (10 digits, valid US mobile, unique per patient), email, address, city, state (2-letter caps), zip (5 digits), sex (Male / Female / Other).
  • Dedicated inputs for current medications, allergies, and conditions. These map to the named fields selfReportedMeds, allergies, medicalConditions, never to custom Q&A.
  • Every other question captured as Q1/A1 ... Qn/An. Embed multiple-choice options in the question text ("... POSSIBLE ANSWERS: a; b; c") and join multi-answers with semicolons.

Choose your identity path (per program)

  • You verify IDs: include patientVerified: true and a verificationId with each visit.
  • Beluga verifies: collect a photo ID during intake and upload it in Step 3; our physicians review it manually.
Nothing hits Beluga's API in this step. Weight loss, hormones, ED, hair loss, dermatology, blood pressure programs: the payload structure below is the same for all of them, so you build this once.
2

Submit the visit

One POST creates the visit and queues it for a physician licensed in the patient's state.
Core

You send

  • formObj: consentsSigned + demographics + patientPreference[] + the named med fields + your Q&A pairs.
  • Root metadata: masterId (a unique ID you generate per visit), pharmacyId, company, visitType, plus patientVerified and verificationId if you verify identity.
  • Rule: never send empty, null, or undefined fields.

Submitting lab results? A dedicated endpoint

  • For programs where you run the testing (test-to-treat and similar), visit creation goes through a separate lab-results endpoint so structured results ride along with the visit and the physician can review and treat in one pass.
  • Add testToTreat plus results[]: screeningDate, testName, testResult, testResultUnits, refRange, statusIndicator (H/L/N), reportDate, sampleSource. Include patientPreference only when testToTreat is true.
  • Have the report as a document instead? Lab reports can also be attached as a PDF using the upload in Step 3.

You receive

  • A success body with masterId; the photo-ID flow also returns a visitId. Persist masterId: every later call, response, and webhook joins through it.
  • scheduleLink for sync-video states is being finalized (flagged TO BE IMPLEMENTED in the docs); your onboarding team confirms it when your program uses video.
  • Body errors: specific field error, Missing metadata, State not valid, Patient not eligible for new visit (same visitType via a different client within 24 hours), and for the lab variant: Patient preference not allowed / Payload contains bad fields. Full guide in Reference below.
How to read responses: any request that reaches Beluga's system returns HTTP 200. The actual outcome lives in the response body (status, error, info fields), so parse the body rather than relying on the HTTP code. An HTTP 400 with an empty body means an authorization problem only: check your Bearer key and environment.
3

Upload photos & PDFs

Photo IDs when Beluga verifies identity, plus any supporting images or documents.
If applicable

You send

  • Images: accepted formats are JPEG, PNG, GIF, BMP, TIFF, WebP, HEIC, and HEIF. Compress to 1000px wide (or under 3MB), base64-encode without the MIME prefix, and set each item's mime to match the format: POST {visitId, images: [{mime, data}]}.
  • PDFs: POST {visitId, image: {mime: "application/pdf", data}}, one per request. Also the route for lab reports you hold as documents.
  • Either the visitId or the masterId from the visit response works as the key.

You receive

  • Success body: "Successfully received images / image".
  • Body errors: "Visit does not exist" (unknown ID) or "Error accepting image(s)".
  • Same rule as Step 2: HTTP 200 means the request reached us; the body carries the result. HTTP 400 with no body is an authorization issue.
4

Video visit booking

Most states allow chat-based care. Where live video is required, the visit response includes a booking link.
Add-on

You build

  • Surface the full scheduleLink, query parameters included: they identify the visit.
  • An iframe keeps the flow seamless; also keep the link reachable in case the patient exits and returns later.

Webhooks you receive

  • BOOKING_CREATED with docName, scheduledDate (ISO 8601), location.
  • BOOKING_RESCHEDULED (same fields), BOOKING_CANCELLED, NO_SHOW.
5

Chat & support messaging

The physician reviews the intake and may ask clarifying questions before deciding.
Core

Webhooks you receive

  • DOCTOR_CHAT with content: display it to the patient in your chat UI.
  • CS_MESSAGE with content: route it to your support team.

You send

  • Patient replies: POST {firstName, lastName, content, isMedia, masterId}. For images, send base64 as content with isMedia: true.
  • Your CS team to Beluga admin: POST {content, masterId}.
6

The clinical outcome

The physician prescribes or refers, writes the note, messages the patient, and resolves the visit.
Core

Webhooks you receive, in order

  • RX_WRITTEN: fires when the clinical note is saved. Carries docName and medsPrescribed[]: name, strength, refills, quantity, medId, rxId, pharmacyNotes.
  • CONSULT_CONCLUDED: fires on resolution with visitOutcome: "prescribed" | "referred". This is your signal the visit is complete.
  • CONSULT_CANCELED: the patient canceled before the physician concluded.

Optional: cancel from your side

  • POST {masterId} to cancel. Returns canceled, or resolved if the physician already acted; repeat calls return alreadyProcessed: true.
7

Fulfillment tracking

Order and shipping events stream to your webhook endpoint so you can power status pages and notifications.
Add-on

Pharmacy orders (Beluga pharmacy partners)

  • Order status: PHARMACY_ORDER_IN_FULFILLMENTPHARMACY_ORDER_SHIPPED (with carrier, tracking) → PHARMACY_ORDER_DELIVERED.
  • Package tracking: PACKAGE_IN_TRANSIT, PACKAGE_OUT_FOR_DELIVERY, PACKAGE_DELIVERED, PACKAGE_DELIVERY_FAILED with trackerStatus, trackerId, trackingUrl, tracking, carrier, deliveredDate.
  • Depending on the pharmacy, you may receive all or only some of these webhooks. Any event we receive from the pharmacy is always forwarded through one of these standard webhooks.
  • orderId + masterId tie every event back to the visit.

Lab orders

  • At-home kits: LAB_ORDER_SHIPPED_TO_PATIENT (with carrier, tracking) → LAB_ORDER_DELIVERED_TO_PATIENTLAB_ORDER_SHIPPED_TO_LABLAB_ORDER_RECEIVED_BY_LAB.
  • In-person draws: LAB_ORDER_REQUISITION_CREATED delivers the requisition as a base64 PDF (labReqPdf) for the patient to bring to the draw site.
  • Both paths finish with LAB_ORDER_RESULTS (with bookingLink). Results also populate labResults[] on the GET visit endpoint. Running your own testing instead? Submit results through the lab-results endpoint in Step 2.
External pharmacies (manufacturer-direct and similar) end at RX_WRITTEN: no downstream tracking events are available for those orders.
8

Ongoing operations

Read, update, and automate after launch. All synchronous request / response.
Ongoing

Read any time

  • GET /visit/externalFetch/{masterId}: visitStatus, resolvedStatus + timestamps, labResults[], the full formObj + intakeResults, rxHistory[], and AutoRx fields (currentDose, nextDose, checkinResult).
  • GET /patient/externalFetch/{phone}: demographics plus every masterId on file for the patient.
  • Pharmacy search: POST {name, city, state, zip} returns the first 100 matches.

Manage

  • Update visit / resend rx: VISIT_DATA_UPDATED before the physician resolves, NEW_RX_SENT after. Window is 7 days from the original rx; retries are capped.
  • Patient name update: allowed until the name and photo ID are approved.

AutoRx program (weight-loss)

  • Titration rx with exactly one preference returns NEW_RX_SENT. Windows: 15-60 days after a 1-month rx, 60-120 after a 3-month rx (staging compresses to 1 minute-4 days for testing).
  • weightlossCheckin visit carries titration (Increase / Decrease / Stay the same), BMI (must exceed 20), and titrationLevel 1-3 for 3-month authorizations.
  • A check-in is required once a visit is resolved more than 90 days (taken 60+ days after resolution); 180 days is a hard stop, then a new visit is needed. A "Yes" on safety questions returns success plus "Patient needs to submit a followup visit".
  • Refill trigger (Beluga pharmacy): POST {medId, masterId} returns NEW_RX_SENT + orderId; 14-day spacing; must match the original rx exactly.

Reference

  • masterId: generated by you, unique per visit. The join key on every later API call, response, and webhook. Store it at creation and the whole lifecycle links up.
  • visitId: returned when Beluga verifies identity. Preferred key for image/PDF uploads (masterId is also accepted there).
  • phone: 10 digits, unique per patient. Lookup key for patient data and cross-visit history.
  • company + visitType: route each submission to the right program configuration and clinical protocol.
  • medId: identifies each medication option; your Beluga customer dashboard is the source of truth for your program's medIds.
  • orderId: appears on pharmacy and lab events so fulfillment status ties back to the visit.
  • active: in the physician queue awaiting review.
  • pending: waiting on something before review, such as ID photos or a video booking.
  • admin: with Beluga's admin/support team.
  • holding: temporarily parked outside the active queue.
  • resolved: physician concluded the visit (prescribed or referred). resolvedStatus moves open → closed, with resolvedTimestamp once closed.
  • canceled: ended before clinical resolution.
  • updateTimestamp reflects the last state change: poll this, or rely on webhooks instead.
  • Read the body, not the HTTP code: any request that reaches Beluga returns HTTP 200, with the outcome in the response body (status, error, info). An HTTP 400 with an empty body signals an authorization problem only: check your Bearer key and environment.
  • Specific field error: a required formObj field is missing or malformed on visit creation; correct and resubmit.
  • Missing metadata: masterId, pharmacyId, company, or visitType absent from the request root.
  • State not valid: the patient's state isn't on the allowable list for the program.
  • Patient not eligible for new visit: same patient + same visitType submitted by a different client within 24 hours (duplicate protection).
  • Patient preference not allowed / bad fields: lab submissions: omit patientPreference when testToTreat is false, and send only documented fields.
  • Visit does not exist: a media, chat, CS, or name-change call referenced an unknown visitId/masterId.
  • Name change no longer allowed: physicians already approved the name against the photo ID.
  • How to read them: these arrive as status strings in the response body of an HTTP 200, per the rule above.
  • VISIT_DATA_UPDATED: visit not yet resolved; preferences saved, no new rx.
  • NEW_RX_SENT: prescription issued; includes medsPrescribed[] (+ orderId on refills).
  • NO_VISIT / VISIT_WAS_REFERRED: unknown masterId, or the visit ended in a referral and can't be re-prescribed.
  • TOO_LONG_AGO: resend attempted more than 7 days after the original rx, or AutoRx more than 180 days after resolution.
  • RX_TIME_OUT_OF_RANGE: outside AutoRx windows (15-60d for 1-month, 60-120d for 3-month) or under the 14-day refill spacing; staging compresses this to 1 minute-4 days for testing.
  • NOT_RESOLVED / VISIT_MISMATCH / TOO_MANY_ITEMS: AutoRx: visit not yet resolved, masterId isn't the most recent resolved weightloss visit for your company, or more than one preference was sent.
  • RX_MISMATCH / INCORRECT_PHARMACY_INTEGRATION: refill triggers: preference must exactly match the original rx and be enabled for Beluga direct pharmacy integration.
  • NEEDS_CHECKIN / FOLLOWUP_REQUIRED: qualifying weightlossCheckin missing (needed once resolved >90d, taken 60d+ after), or check-in answers require a physician followup.
  • CATEGORY / DURATION / TITRATION / PHARMACY _MISMATCH: the incoming preference must match the original rx's product type, duration, titration track, and pharmacy.
  • MAX_MONTHS_REACHED / NO_MORE_REFILLS / TOO_MANY_RETRIES: program, refill, or retry ceilings hit.
  • RX_ERROR / GENERIC: e-prescribing or system error; RX_ERROR is safe to retry.
Questions during the build? Post in your shared Slack channel and Beluga's onboarding team will guide you step by step. This guide describes the data interface only: clinical review, prescribing, and the physician EMR are handled entirely inside Beluga. Payloads reflect Beluga's skeleton API, webhook, and GET documentation shared in your channel.