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.
Authorization: Bearer {API_KEY} on every requestapi-staging.belugahealth.comapi.belugahealth.comconsentsSigned: true.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.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).selfReportedMeds, allergies, medicalConditions, never to custom Q&A.Q1/A1 ... Qn/An. Embed multiple-choice options in the question text ("... POSSIBLE ANSWERS: a; b; c") and join multi-answers with semicolons.patientVerified: true and a verificationId with each visit.consentsSigned + demographics + patientPreference[] + the named med fields + your Q&A pairs.masterId (a unique ID you generate per visit), pharmacyId, company, visitType, plus patientVerified and verificationId if you verify identity.testToTreat plus results[]: screeningDate, testName, testResult, testResultUnits, refRange, statusIndicator (H/L/N), reportDate, sampleSource. Include patientPreference only when testToTreat is true.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.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.mime to match the format: POST {visitId, images: [{mime, data}]}.{visitId, image: {mime: "application/pdf", data}}, one per request. Also the route for lab reports you hold as documents.visitId or the masterId from the visit response works as the key.200 means the request reached us; the body carries the result. HTTP 400 with no body is an authorization issue.scheduleLink, query parameters included: they identify the visit.BOOKING_CREATED with docName, scheduledDate (ISO 8601), location.BOOKING_RESCHEDULED (same fields), BOOKING_CANCELLED, NO_SHOW.DOCTOR_CHAT with content: display it to the patient in your chat UI.CS_MESSAGE with content: route it to your support team.{firstName, lastName, content, isMedia, masterId}. For images, send base64 as content with isMedia: true.{content, masterId}.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.{masterId} to cancel. Returns canceled, or resolved if the physician already acted; repeat calls return alreadyProcessed: true.PHARMACY_ORDER_IN_FULFILLMENT → PHARMACY_ORDER_SHIPPED (with carrier, tracking) → PHARMACY_ORDER_DELIVERED.PACKAGE_IN_TRANSIT, PACKAGE_OUT_FOR_DELIVERY, PACKAGE_DELIVERED, PACKAGE_DELIVERY_FAILED with trackerStatus, trackerId, trackingUrl, tracking, carrier, deliveredDate.orderId + masterId tie every event back to the visit.LAB_ORDER_SHIPPED_TO_PATIENT (with carrier, tracking) → LAB_ORDER_DELIVERED_TO_PATIENT → LAB_ORDER_SHIPPED_TO_LAB → LAB_ORDER_RECEIVED_BY_LAB.LAB_ORDER_REQUISITION_CREATED delivers the requisition as a base64 PDF (labReqPdf) for the patient to bring to the draw site.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.RX_WRITTEN: no downstream tracking events are available for those orders.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.{name, city, state, zip} returns the first 100 matches.VISIT_DATA_UPDATED before the physician resolves, NEW_RX_SENT after. Window is 7 days from the original rx; retries are capped.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.{medId, masterId} returns NEW_RX_SENT + orderId; 14-day spacing; must match the original rx exactly.resolvedStatus moves open → closed, with resolvedTimestamp once closed.updateTimestamp reflects the last state change: poll this, or rely on webhooks instead.status, error, info). An HTTP 400 with an empty body signals an authorization problem only: check your Bearer key and environment.status strings in the response body of an HTTP 200, per the rule above.