{"openapi":"3.1.0","info":{"title":"RxBridge API","version":"1.0.0","description":"RxBridge is a real-time e-prescribing bridge between provider (prescriber) organizations and pharmacy organizations. This document describes the public partner API under /api/v1 — the same routes documented at /docs/api. Internal dashboard routes (/api/internal/**) and platform-admin routes (/api/admin/**) are not part of this contract and are not included here.\n\n## Test mode\n\nScripts created with a test-mode API key are test scripts: they are never submitted to the pharmacy integration, generate no billing and send no patient or prescriber emails, and every webhook about them carries `livemode: false`. With a test-mode key, onboarding checks the NPI checksum only (no registry lookup), and it cannot modify a prescriber that has been verified against the NPI registry (409 `live_prescriber_protected`). A test-mode key also cannot update the status or shipment of a live script (403 `live_script_protected`); a live key may act on a test script.\n\n## Error codes\n\nError bodies are `{ error, code?, field? }`. `code` is a stable machine-readable identifier — branch on it, not on the message.\n\n| code | HTTP status | Meaning |\n|---|---|---|\n| `invalid_json` | 400 | POST /scripts, POST /onboarding/prescriber, PATCH /onboarding/licenses, POST /onboarding/sessions: the request body is not valid JSON (onboarding also requires a JSON object). |\n| `prescriber_required` | 400 | POST /scripts: the organization has more than one active prescriber and the request named none. Send prescriber_id or prescriber_npi; the response lists active_prescribers. |\n| `invalid_prescriber_npi` | 400 | POST /scripts: prescriber_npi is not a 10-digit NPI. |\n| `prescriber_selector_conflict` | 400 | POST /scripts: prescriber_id and prescriber_npi identify different prescribers. |\n| `prescriber_not_found` | 404 | The prescriber_id / prescriber_npi (POST /scripts) or prescriber_id (PATCH /onboarding/licenses) is not a prescriber in your organization. |\n| `no_license_for_state` | 422 | POST /scripts: the prescriber has no unexpired license in the patient's state, patientState is not a valid US state or territory code, or patientState is required because the prescriber holds unexpired licenses in several states. |\n| `no_prescriber` | 403 | POST /scripts: the organization has no prescriber that can sign scripts. |\n| `prescriber_pending_review` | 403 | POST /scripts: the prescriber was submitted but a platform admin has not approved it yet. |\n| `prescriber_rejected` | 403 | POST /scripts: the prescriber failed platform review. |\n| `prescriber_deactivated` | 403 | POST /scripts: the prescriber was approved but has since been deactivated (an admin action, or an expired DEA registration). |\n| `license_expired` | 403 / 400 | 403 on POST /scripts: all of the prescriber's licenses have expired. 400 on onboarding and license writes: a submitted license expiration_date is in the past. |\n| `prescriber_npi_unverified` | 403 | POST /scripts with a live key: the prescriber was activated in test mode without NPI registry verification, so it can only sign test scripts. Once its organization has a live key it cannot sign until its NPI is verified. |\n| `medication_unavailable` | 400 | POST /scripts: medicationId is not an available product at the script's pharmacyOrgId. |\n| `medication_mismatch` | 400 | POST /scripts: the strength sent does not match the strength of the catalog product named by medicationId. |\n| `live_prescriber_protected` | 409 | POST /onboarding/prescriber, PATCH /onboarding/licenses: a test-mode API key tried to modify a prescriber that has been verified against the NPI registry. |\n| `live_script_protected` | 403 | PATCH /scripts/{id}/status, POST /scripts/{id}/shipment: a test-mode API key tried to modify a live script. Test keys can only modify test scripts. |\n| `invalid_patient_state` | 400 | GET /pharmacies/recommend: patient_state is missing or is not a two-letter US state or territory code. |\n| `invalid_medication_id` | 400 | GET /pharmacies/recommend: medication_id is not a valid UUID. |\n| `provider_org_required` | 403 | The onboarding endpoint is only available to provider organizations. |\n| `full_legal_name_required` | 400 | full_legal_name is missing. |\n| `mobile_phone_required` | 400 | mobile_phone is missing. |\n| `invalid_mobile_phone` | 400 | mobile_phone is not a 10-digit US number. |\n| `professional_email_required` | 400 | professional_email is missing. |\n| `npi_required` | 400 | npi is missing. |\n| `invalid_npi` | 400 | npi (body or ?npi= filter) is not exactly 10 digits, or its checksum is invalid. |\n| `npi_not_found` | 400 | The NPI is not in the NPPES registry (live keys). |\n| `npi_inactive` | 400 | The NPI is deactivated in the NPPES registry (live keys). |\n| `npi_not_individual` | 400 | The NPI belongs to an organization (NPI-2); an individual (NPI-1) is required (live keys). |\n| `npi_lookup_failed` | 502 | The live NPPES registry lookup itself failed; safe to retry. |\n| `licenses_required` | 400 | licenses must be a non-empty array. |\n| `invalid_license` | 400 | A licenses[] entry is not an object. |\n| `license_number_required` | 400 | licenses[].license_number is missing. |\n| `invalid_license_state` | 400 | licenses[].state is not a 2-letter code for one of the 50 states, DC or PR. |\n| `invalid_license_type` | 400 | licenses[].license_type is not physician, aprn, pa or pharmd (or an accepted alias). |\n| `license_expiration_required` | 400 | licenses[].expiration_date is missing. |\n| `invalid_license_expiration` | 400 | licenses[].expiration_date is not a valid YYYY-MM-DD date. |\n| `duplicate_license_state` | 400 | Two licenses in the payload have the same state. |\n| `invalid_date_of_birth` | 400 | date_of_birth is not a valid date, or the prescriber is under 18. |\n| `invalid_dea_number` | 400 | The DEA number is malformed or fails the checksum / last-name check. |\n| `invalid_dea_expiration_date` | 400 | The DEA expiration date is not a valid YYYY-MM-DD date. |\n| `attestations_required` | 400 | The attestations object is missing. |\n| `attestation_required` | 400 | One of the required attestations is not true. |\n| `typed_signature_required` | 400 | typed_signature is missing. |\n| `prescriber_id_required` | 400 | PATCH /onboarding/licenses: prescriber_id is missing. |\n| `invalid_prescriber_id` | 400 | GET /onboarding/prescriber: the prescriber_id filter is not a UUID. |\n| `return_url_required` | 400 | POST /onboarding/sessions: return_url is missing. |\n| `invalid_return_url` | 400 | POST /onboarding/sessions: return_url is not an absolute http(s) URL, or carries credentials, a fragment, or one of the reserved query parameters session_id, state or status. |\n| `invalid_prefill` | 400 | POST /onboarding/sessions: a prefill value is malformed (wrong type, empty, or over-long) and has no more specific code. The field is named in `field`. |\n| `prefill_field_not_allowed` | 400 | POST /onboarding/sessions: prefill.attestations or prefill.typed_signature was sent. The prescriber must enter both personally on the hosted page. |\n| `return_url_not_allowed` | 403 | POST /onboarding/sessions: return_url's origin is not registered for your organization or its partner (an RxBridge admin registers return origins), or it is http / test-only for a live key. |\n| `invalid_expires_in` | 400 | POST /onboarding/sessions: expires_in is not an integer number of seconds in [300, 604800]. |\n| `external_ref_required` | 400 | POST /onboarding/sessions: external_ref is missing. |\n| `invalid_external_ref` | 400 | POST /onboarding/sessions: external_ref is not a non-empty string of at most 200 characters. |\n| `invalid_state` | 400 | POST /onboarding/sessions: state is not a string of at most 500 characters. |\n| `session_already_open` | 409 | POST /onboarding/sessions: an open session already exists for this external_ref. The body carries session_id and expires_at; DELETE it or pass replace: true. |\n| `invalid_session_id` | 400 | GET/DELETE /onboarding/sessions/{id}: the path id is not a UUID. |\n| `session_not_found` | 404 | GET/DELETE /onboarding/sessions/{id}: no such session for this organization (an unknown id and another organization's id are indistinguishable). |\n| `session_already_completed` | 409 | DELETE /onboarding/sessions/{id}: the session is already completed and cannot be canceled. |\n| `mode_mismatch` | 409 | POST /provision: the email's existing organization has API keys in the other mode (live vs test), or test_mode was requested for an organization with no keys that already has live data (a live script or an NPI-verified prescriber), so no key was created. Use a different email for the other mode. |\n| `org_not_owned_by_partner` | 403 | POST /provision: the email's existing organization was not created by the calling partner (matched by external_source), so no key was created. Only the partner that created an organization can re-provision it; its owner manages keys in Rx-Bridge. |\n| `source_mismatch` | 403 | POST /provision: the request names a partner (external_source) other than the one the provisioning key belongs to, so nothing was created. |\n| `partner_key_required` | 403 | POST /provision: external_org_id was sent with the shared provisioning key. It needs a provisioning key issued to your integration only, since the shared key cannot say which partner is calling. |\n| `partner_not_registered` | 409 | POST /provision: external_org_id was sent but the calling integration is not registered as a partner in Rx-Bridge, so no organization was created. |\n| `no_active_contract` | 403 | POST /scripts: the provider has no active contract with the pharmacy named in pharmacyOrgId, so nothing was created or sent. Request or accept a contract first. |\n| `onboarding_failed` | 500 | POST /onboarding/prescriber failed; nothing was saved. |\n| `update_failed` | 500 | PATCH /onboarding/licenses failed (licenses may be saved when a later DEA write fails; the retry is idempotent). |\n| `lookup_failed` | 500 | A read of prescribers or licenses failed. |"},"servers":[{"url":"https://rx-bridge.cofabri.com/api/v1","description":"Production"}],"tags":[{"name":"Scripts","description":"Prescription orders."},{"name":"Contracts","description":"Provider <-> pharmacy contracts."},{"name":"Pharmacies","description":"Pharmacy directory."},{"name":"Medications","description":"Medication catalog, scoped to accessible pharmacies."},{"name":"Onboarding","description":"Prescriber onboarding, licenses, and DEA registration, plus hosted onboarding sessions that let a prescriber complete onboarding on RxBridge."},{"name":"Pricing","description":"Fee and commission estimates."},{"name":"Webhooks","description":"Event subscriptions."},{"name":"Organization","description":"The API key's own organization."}],"components":{"securitySchemes":{"apiKeyBearer":{"type":"http","scheme":"bearer","description":"API key sent as `Authorization: Bearer <key>`. Keys are prefixed `rxb_`."},"apiKeyHeader":{"type":"apiKey","in":"header","name":"x-api-key","description":"API key sent as the `x-api-key` header, as an alternative to the Bearer form."}},"schemas":{"Script":{"type":"object","description":"A script (prescription order). Returned by POST /scripts, GET /scripts, and GET /scripts/{id}.","properties":{"id":{"type":"string","format":"uuid"},"providerOrgId":{"type":"string","format":"uuid"},"pharmacyOrgId":{"type":"string","format":"uuid"},"createdByUserId":{"type":"string","format":"uuid"},"patientName":{"type":"string"},"patientPhone":{"type":["string","null"]},"patientEmail":{"type":["string","null"]},"patientDateOfBirth":{"type":["string","null"],"description":"ISO date (YYYY-MM-DD)."},"patientAddressLine1":{"type":["string","null"]},"patientAddressLine2":{"type":["string","null"]},"patientCity":{"type":["string","null"]},"patientState":{"type":["string","null"]},"patientZip":{"type":["string","null"]},"patientGender":{"type":["string","null"]},"patientAllergies":{"type":["array","null"],"items":{"type":"string"}},"insuranceProvider":{"type":["string","null"]},"insurancePolicyId":{"type":["string","null"]},"insuranceGroupNumber":{"type":["string","null"]},"insuranceRxBin":{"type":["string","null"]},"insuranceRxPCN":{"type":["string","null"]},"insuranceCardholderName":{"type":["string","null"]},"insuranceRelationship":{"type":["string","null"]},"prescriberId":{"type":["string","null"],"format":"uuid"},"prescriberNpi":{"type":["string","null"]},"prescriberDeaNumber":{"type":["string","null"]},"prescriberLicenseNumber":{"type":["string","null"]},"prescriberLicenseState":{"type":["string","null"]},"medicationName":{"type":"string"},"ndc":{"type":["string","null"],"description":"10 or 11 digit NDC, hyphens stripped on save."},"strength":{"type":["string","null"]},"quantity":{"type":["string","null"]},"directions":{"type":["string","null"]},"refills":{"type":"integer","minimum":0},"prescriptionDate":{"type":["string","null"],"description":"ISO date (YYYY-MM-DD)."},"prescriptionExpirationDate":{"type":["string","null"],"description":"ISO date (YYYY-MM-DD). Server-computed; not settable on create."},"substitutionAllowed":{"type":"boolean"},"dawCode":{"type":["integer","null"],"description":"Dispense-As-Written code, 0-9."},"priorAuthRequired":{"type":"boolean"},"priorAuthNumber":{"type":["string","null"]},"controlledSubstanceSchedule":{"type":["string","null"]},"quantityWritten":{"type":["string","null"]},"daysSupply":{"type":["integer","null"]},"diagnosisCodes":{"type":["array","null"],"items":{"type":"string"},"description":"ICD-10-CM codes, e.g. \"A00.0\"."},"specialInstructions":{"type":["string","null"]},"pharmacyNotes":{"type":["string","null"]},"orderValue":{"type":["number","null"],"description":"Set by the pharmacy via PATCH /scripts/{id}/status when moving to \"completed\"."},"status":{"type":"string","enum":["draft","pending","processing","on_hold_pending_pa","verified","ready","completed","cancelled","rejected","awaiting_payment"]},"priorityProcessing":{"type":"boolean"},"deliveryTrackingEnabled":{"type":"boolean"},"autoRefillRemindersEnabled":{"type":"boolean"},"shippingCarrier":{"type":["string","null"]},"trackingNumber":{"type":["string","null"]},"shippingStatus":{"type":["string","null"]},"externalSystem":{"type":["string","null"],"description":"Caller's own system identifier, for cross-referencing."},"externalSystemScriptId":{"type":["string","null"],"description":"Caller's own script ID in their system."},"cancellationReason":{"type":["string","null"]},"cancellationNotes":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"approvalStatus":{"type":"string","enum":["not_required","pending","approved","rejected"],"description":"Derived: rejected if prescriberRejectedAt is set, else approved if prescriberApprovedAt is set, else pending if requiresPrescriberApproval was set on create, else not_required."},"prescriberRejectionReason":{"type":["string","null"]},"clinicalNote":{"type":["string","null"],"description":"Free-text clinical context sent by the submitting partner on create (max 2000 characters). PHI. Provider organization's keys only; always null for the pharmacy organization."},"submittedByName":{"type":["string","null"],"description":"Display name of the person who submitted the script, as sent by the partner on create."},"submittedByExternalId":{"type":["string","null"],"description":"The partner's own id for the submitter. Never resolved to an Rx-Bridge user."},"approvedAt":{"type":["string","null"],"format":"date-time","description":"When the prescriber approved the script in Rx-Bridge. Null until then."},"rejectedAt":{"type":["string","null"],"format":"date-time","description":"When the prescriber rejected the script in Rx-Bridge. Null unless rejected."},"portalUrl":{"type":"string","format":"uri","description":"Absolute URL of this script in the Rx-Bridge provider dashboard (requires a signed-in user of the provider organization)."}},"required":["id","providerOrgId","pharmacyOrgId","createdByUserId","patientName","medicationName","refills","substitutionAllowed","priorAuthRequired","status","priorityProcessing","deliveryTrackingEnabled","autoRefillRemindersEnabled","createdAt","updatedAt","approvalStatus","portalUrl"]},"ScriptEvent":{"type":"object","properties":{"id":{"type":"string","description":"Stable identifier of the event."},"occurredAt":{"type":"string","format":"date-time"},"type":{"type":"string","enum":["submitted","approval_requested","approved","rejected","status_changed","shipment_updated"]},"label":{"type":"string","description":"Display text in Title Case."},"source":{"type":"string","enum":["medoura","praxis","rxbridge","pharmacy"]},"actorName":{"type":["string","null"],"description":"The submitter or assigned prescriber where known; null otherwise (never pharmacy staff)."},"detail":{"type":["string","null"],"description":"For example the previous status, the rejection reason, or carrier and shipping status."}},"required":["id","occurredAt","type","label","source","actorName","detail"]},"Contract":{"type":"object","description":"A contract between a provider organization and a pharmacy organization.","properties":{"id":{"type":"string","format":"uuid"},"provider_org_name":{"type":["string","null"]},"pharmacy_org_name":{"type":["string","null"]},"status":{"type":"string","enum":["pending","active","terminated","rejected"]},"requested_by_org_name":{"type":["string","null"]},"start_date":{"type":["string","null"],"format":"date-time"},"end_date":{"type":["string","null"],"format":"date-time"},"notes":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","status","created_at","updated_at"]},"Pharmacy":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"The pharmacy organization's organization ID (organizations.id / pharmacies.organization_id), not the pharmacies table's own row id."},"name":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"servicesOffered":{"type":["array","null"],"items":{"type":"string"}},"phone":{"type":"string"},"email":{"type":"string"}},"required":["id","name","city","state","phone","email"]},"PharmacyRecommendationItem":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"The pharmacy's organization id: the value POST /scripts takes as pharmacyOrgId."},"name":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"servicesOffered":{"type":"array","items":{"type":"string"}}},"required":["id","name","city","state","servicesOffered"]},"Medication":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"genericName":{"type":["string","null"]},"ndc":{"type":["string","null"]},"strength":{"type":["string","null"]},"form":{"type":["string","null"]},"schedule":{"type":["string","null"]},"isControlled":{"type":"boolean"},"quantityOptions":{"type":"array","items":{"type":"string"},"description":"Orderable quantities (e.g. \"1 mL\"), from the pharmacy that stocks the product."},"daysSupplyOptions":{"type":"array","items":{"type":"integer"},"description":"Orderable days-supply values."},"maxRefills":{"type":["integer","null"],"description":"Maximum refills the pharmacy accepts."},"requiresPriorAuth":{"type":"boolean","description":"Whether the pharmacy requires prior authorization."},"shipsToStates":{"type":["array","null"],"items":{"type":"string"},"description":"Two-letter state codes the pharmacy ships to; null means all states."},"leadTimeDays":{"type":["integer","null"],"description":"Typical days from order to shipment."}},"required":["id","name","isControlled"]},"Webhook":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string","enum":["script.created","script.status_changed","script.shipment_updated","script.approval_requested","script.approved","script.rejected","contract.created","contract.accepted","contract.rejected","contract.terminated","prescriber.onboarded","prescriber.activated","prescriber.rejected","prescriber.deactivated","onboarding_session.completed","*"]}},"description":{"type":["string","null"]},"is_active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","url","events","is_active","created_at","updated_at"]},"WebhookCreated":{"type":"object","allOf":[{"$ref":"#/components/schemas/Webhook"},{"type":"object","properties":{"secret":{"type":"string","description":"HMAC-SHA256 signing secret (\"whsec_...\"). Only returned once, at creation time — store it. Used to verify the x-rxbridge-signature header on delivered webhook payloads."}},"required":["secret"]}]},"Organization":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"type":{"type":"string","enum":["provider","pharmacy"]},"slug":{"type":"string"}},"required":["id","name","type","slug"]},"Pagination":{"type":"object","properties":{"page":{"type":"integer","minimum":1},"limit":{"type":"integer","minimum":1,"maximum":100},"total":{"type":"integer"},"total_pages":{"type":"integer"},"has_more":{"type":"boolean"}},"required":["page","limit","total","total_pages","has_more"]},"Error":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string","description":"Stable machine-readable error code, present on the onboarding, license, script and test-mode failures. Branch on it, not on `error`. See \"Error codes\" in the API description for the full list."},"field":{"type":"string","description":"Request field the error refers to (onboarding endpoints), for example `licenses[0].state`."},"active_prescribers":{"type":"array","description":"Only on POST /scripts 400 `prescriber_required`: the organization's active prescribers to choose from.","items":{"type":"object","properties":{"prescriber_id":{"type":"string","format":"uuid"},"npi":{"type":"string"}},"required":["prescriber_id","npi"]}},"details":{"type":"object","description":"Field-level validation errors, keyed by field path. Only present on POST /scripts 400 responses (zod validation failures).","additionalProperties":{"type":"array","items":{"type":"string"}}}},"required":["error"]},"PrescriberEventData":{"type":"object","properties":{"prescriber_id":{"type":"string","format":"uuid"},"npi":{"type":"string"},"status":{"type":"string","enum":["pending_review","active","rejected","deactivated"]},"reason":{"type":"string","description":"Rejection or deactivation reason. Present on prescriber.rejected and prescriber.deactivated only."},"livemode":{"type":"boolean","description":"false for a prescriber created with a test-mode API key (its NPI was never verified against the registry)."}},"required":["prescriber_id","npi","status","livemode"]},"OnboardingSessionCompletedData":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"external_ref":{"type":"string","description":"The external_ref you passed to POST /onboarding/sessions."},"prescriber_id":{"type":"string","format":"uuid"},"npi":{"type":"string"},"status":{"type":"string","enum":["pending_review","active","rejected","deactivated"],"description":"The prescriber's status at completion: pending_review, never approval. Follow prescriber.activated / prescriber.rejected for the review outcome."},"livemode":{"type":"boolean","description":"false for a session created with a test-mode API key."}},"required":["session_id","external_ref","prescriber_id","npi","status","livemode"]},"OnboardingSession":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["created","opened","completed","expired","canceled"],"description":"Effective status: an open session past `expires_at` reads `expired`."},"livemode":{"type":"boolean"},"external_ref":{"type":"string"},"state":{"type":["string","null"]},"return_url":{"type":"string","format":"uri"},"prescriber_id":{"type":["string","null"],"format":"uuid"},"prescriber_status":{"type":["string","null"],"enum":["pending_review","active","rejected","deactivated",null]},"review_status":{"type":["string","null"],"enum":["pending","approved","rejected",null]},"licenses":{"type":"array","description":"License summary once completed (empty before). No license numbers.","items":{"type":"object","properties":{"state":{"type":"string"},"license_type":{"type":["string","null"]},"expiration_date":{"type":["string","null"]}}}},"created_at":{"type":"string","format":"date-time"},"expires_at":{"type":"string","format":"date-time"},"opened_at":{"type":["string","null"],"format":"date-time"},"completed_at":{"type":["string","null"],"format":"date-time"},"canceled_at":{"type":["string","null"],"format":"date-time"}},"required":["id","status","livemode","external_ref","return_url","licenses","created_at","expires_at"]},"ScriptApprovalEventData":{"type":"object","properties":{"script_id":{"type":"string","format":"uuid"},"approval_status":{"type":"string","enum":["pending","approved","rejected"]},"prescriber_id":{"type":"string","format":"uuid","description":"script.approval_requested only."},"submitted_by_name":{"type":["string","null"],"description":"script.approval_requested only."},"approved_at":{"type":"string","format":"date-time","description":"script.approved only: when the prescriber approved."},"rejected_at":{"type":"string","format":"date-time","description":"script.rejected only: when the prescriber rejected."},"rejection_reason":{"type":"string","description":"script.rejected only."}},"required":["script_id","approval_status"]},"WebhookPayload":{"type":"object","description":"Sent with headers X-RxBridge-Signature (`sha256=` + hex HMAC-SHA256 of the raw body, keyed with the endpoint secret), X-RxBridge-Event and X-RxBridge-Delivery.","properties":{"event":{"type":"string","enum":["script.created","script.status_changed","script.shipment_updated","script.approval_requested","script.approved","script.rejected","contract.created","contract.accepted","contract.rejected","contract.terminated","prescriber.onboarded","prescriber.activated","prescriber.rejected","prescriber.deactivated","onboarding_session.completed"]},"event_id":{"type":"string","format":"uuid","description":"Unique per event. Minted once and identical on every delivery retry (X-RxBridge-Delivery changes on every attempt), so dedupe on it."},"occurred_at":{"type":"string","format":"date-time","description":"When Rx-Bridge recorded the change (for changes detected by LifeFile polling, this is the poll time)."},"timestamp":{"type":"string","format":"date-time"},"livemode":{"type":"boolean","description":"false when the event was caused by a test-mode API key or concerns a test script; true otherwise."},"data":{"type":"object","description":"Event-specific object. prescriber.* events: see PrescriberEventData. onboarding_session.completed: see OnboardingSessionCompletedData. script.approval_requested, script.approved and script.rejected: see ScriptApprovalEventData."}},"required":["event","event_id","occurred_at","timestamp","livemode","data"]}}},"paths":{"/api/v1/contracts":{"post":{"summary":"Request a contract with a pharmacy","description":"Provider organizations only. Creates a pending contract request between the caller's provider org and the given pharmacy org.\n\nRequires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`. Callers must be a **provider** organization.\n\nRate limit: 100 requests / 60s per organization (tier: `standard`).","tags":["Contracts"],"operationId":"postContracts","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pharmacy_org_id":{"type":"string","format":"uuid","description":"Organization ID of the pharmacy to request a contract with."},"notes":{"type":"string"}},"required":["pharmacy_org_id"]}}}},"responses":{"201":{"description":"Contract created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contract"}}}},"400":{"description":"pharmacy_org_id missing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Caller is not a provider organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Failed to create contract.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List contracts for the caller's organization","description":"Returns contracts where the caller's organization is either the provider or the pharmacy party.\n\nRequires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`.\n\nRate limit: 100 requests / 60s per organization (tier: `standard`).","tags":["Contracts"],"operationId":"getContracts","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"status","in":"query","required":false,"description":"Filter to a single contract status.","schema":{"type":"string","enum":["pending","active","terminated","rejected"]}}],"responses":{"200":{"description":"Array of contracts (not a paginated envelope).","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Contract"}}}}}}}},"/api/v1/contracts/{id}":{"patch":{"summary":"Accept, reject, or terminate a contract","description":"action determines which lifecycle transition to perform; start_date/end_date/notes are only meaningful for the matching action.\n\nRequires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`.\n\nRate limit: 100 requests / 60s per organization (tier: `standard`).","tags":["Contracts"],"operationId":"patchContractsId","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Contract ID.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"type":"string","enum":["accept","reject","terminate"]},"start_date":{"type":"string","format":"date-time","description":"Used only when action is \"accept\"."},"end_date":{"type":"string","format":"date-time","description":"Used only when action is \"terminate\"."},"notes":{"type":"string"}},"required":["action"]}}}},"responses":{"200":{"description":"Updated contract.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contract"}}}},"400":{"description":"action missing or not one of accept/reject/terminate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Failed to update contract.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"Get a single contract","description":"Requires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`.\n\nRate limit: 100 requests / 60s per organization (tier: `standard`).","tags":["Contracts"],"operationId":"getContractsId","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Contract ID.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The contract.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contract"}}}},"404":{"description":"Contract not found (or not visible to the caller's organization).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/organization":{"get":{"summary":"Get the API key's own organization","description":"Returns the organization the calling API key belongs to.\n\nRequires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`.\n\nRate limit: 100 requests / 60s per organization (tier: `standard`).","tags":["Organization"],"operationId":"getMeOrganization","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"responses":{"200":{"description":"The organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Organization"}}}},"404":{"description":"Organization not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/medications":{"get":{"summary":"List medications available at accessible pharmacies","description":"For pharmacy organizations, returns medications stocked (pharmacy_medications.is_available = true) at their own pharmacy. For provider organizations, returns medications available across every pharmacy they have an active contract with. Results are deduplicated by medication ID; when several pharmacies stock the same product, the ordering limits (quantityOptions, daysSupplyOptions, maxRefills, requiresPriorAuth, shipsToStates, leadTimeDays) come from the first pharmacy returned. Pharmacy-internal fields (LifeFile product id, price, product code) are never exposed.\n\nRequires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`.\n\nRate limit: 100 requests / 60s per organization (tier: `standard`).","tags":["Medications"],"operationId":"getMedications","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"pharmacy_id","in":"query","required":false,"description":"Scope to a single pharmacy's organization ID. Must be a pharmacy the caller has access to, or a 403 is returned.","schema":{"type":"string","format":"uuid"}},{"name":"search","in":"query","required":false,"description":"Case-insensitive substring match against medication name or generic name.","schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"Default 1.","schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"description":"Default 25, max 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}],"responses":{"200":{"description":"Paginated list of medications.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Medication"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"]}}}},"403":{"description":"pharmacy_id given but not accessible to the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/onboarding/licenses":{"patch":{"summary":"Update an existing prescriber's licenses and/or DEA registration","description":"Provider organizations only. Upserts state license rows by (prescriber_id, state) — other states are kept — and upserts the single DEA registration row for the prescriber. Use for renewals or adding a new state license without re-running full onboarding. Every license is validated before anything is written (a bad entry fails the whole request instead of being skipped), and the license upsert is a single atomic statement. Licenses use the same rules as POST /onboarding/prescriber.\n\nRequires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`. Callers must be a **provider** organization.\n\nRate limit: 100 requests / 60s per organization (tier: `standard`).","tags":["Onboarding"],"operationId":"patchOnboardingLicenses","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"prescriber_id":{"type":"string","format":"uuid","description":"Must belong to the caller's organization."},"licenses":{"type":"array","items":{"type":"object","properties":{"state":{"type":"string","description":"2-letter code for one of the 50 states, DC or PR (case-insensitive)."},"license_number":{"type":"string"},"license_type":{"type":"string","enum":["physician","aprn","pa","pharmd"],"description":"Aliases accepted: MD, DO, NP, APRN, FNP, PA, PA-C, PharmD."},"expiration_date":{"type":"string","format":"date","description":"YYYY-MM-DD; required, not in the past."}},"required":["state","license_number","license_type","expiration_date"]},"description":"Optional. Each entry is validated; duplicate states are rejected."},"dea":{"type":"object","properties":{"dea_number":{"type":"string","description":"Validated against the prescriber's last name via validateDEANumber (DEA checksum algorithm)."},"registration_address":{"type":"object","properties":{"line1":{"type":"string"},"line2":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"zip":{"type":"string"}}},"expiration_date":{"type":"string"},"schedules":{"description":"Free-form; an array is stored comma-joined."}}}},"required":["prescriber_id"]}}}},"responses":{"200":{"description":"Update summary.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"licenses_updated":{"type":"integer"},"dea_updated":{"type":"boolean"}},"required":["success","licenses_updated","dea_updated"]}}}},"400":{"description":"Validation failed; the body carries a stable `code` and the `field`. Codes: invalid_json, prescriber_id_required, licenses_required, invalid_license, license_number_required, invalid_license_state, invalid_license_type, license_expiration_required, invalid_license_expiration, license_expired, duplicate_license_state, invalid_dea_number, invalid_dea_expiration_date.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Caller is not a provider organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"prescriber_id not found in the caller's organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"live_prescriber_protected: a test-mode API key tried to modify a prescriber that has been verified against the NPI registry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Update failed (code update_failed). Licenses are saved atomically; a DEA write failure is reported after licenses were saved, and a retry is idempotent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"Get a prescriber's licenses and DEA registration","description":"Requires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`. Callers must be a **provider** organization.\n\nRate limit: 100 requests / 60s per organization (tier: `standard`).","tags":["Onboarding"],"operationId":"getOnboardingLicenses","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"prescriber_id","in":"query","required":false,"description":"Filter to one prescriber. Omit to list every prescriber in the caller's organization.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Prescribers with their licenses and DEA registrations.","content":{"application/json":{"schema":{"type":"object","properties":{"prescribers":{"type":"array","items":{"type":"object","properties":{"prescriber_id":{"type":"string","format":"uuid"},"full_legal_name":{"type":"string"},"npi":{"type":"string"},"licenses":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"state":{"type":"string","description":"Uppercase 2-letter code (50 states, DC or PR)."},"license_number":{"type":"string"},"license_type":{"type":"string","nullable":true,"enum":["physician","aprn","pa","pharmd"]},"expiration_date":{"type":"string","nullable":true,"description":"ISO date (YYYY-MM-DD)."}}}},"dea_registrations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"dea_number":{"type":"string"},"expiration_date":{"type":"string","nullable":true},"schedules":{"nullable":true,"description":"Free-form; an array is stored comma-joined (not validated by this route)."}}}}}}}},"required":["prescribers"]}}}},"403":{"description":"Caller is not a provider organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Lookup failed (code lookup_failed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/onboarding/prescriber":{"post":{"summary":"Onboard (create or update) a prescriber in one call","description":"Provider organizations only. Upserts a prescriber identified by (organization, npi) with identity, licenses, optional DEA registration, and an attestation, in one atomic request: either everything is saved or the request fails. An organization may hold many prescribers. A new NPI creates an inactive prescriber with review_status \"pending\" (201); an existing NPI updates that prescriber (200). licenses is the prescriber's complete set: states not listed are removed. On an already-approved prescriber, a substantive change to full_legal_name resets it to pending review and deactivates it until a platform admin re-approves. The comparison ignores case, punctuation, titles and credentials (Dr, MD, DO, NP and similar) and tolerates added name parts such as a middle name or initial, so only dropping or replacing a name part of the stored name triggers re-review. A change in the name the NPI registry holds for the NPI (live keys) is compared the same way. Other field changes never affect activation. New prescribers require platform review before they can be used to create scripts. The NPI must belong to an individual (NPI-1) and is checked against the NPPES registry in live mode (sandbox/test-mode API keys only run the local checksum); a registry name that differs from full_legal_name does not reject the request but flags the prescriber for the reviewer. Test-mode API keys cannot modify a prescriber whose NPI has been verified (409 live_prescriber_protected). Identity verification (Stripe) is not required on this path.\n\nRequires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`. Callers must be a **provider** organization.\n\nRate limit: 100 requests / 60s per organization (tier: `standard`).","tags":["Onboarding"],"operationId":"postOnboardingPrescriber","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"full_legal_name":{"type":"string"},"date_of_birth":{"type":"string","format":"date","description":"Optional, YYYY-MM-DD. If given it must be a valid date and the prescriber at least 18."},"mobile_phone":{"type":"string","description":"A 10-digit US number; formatting characters and a leading country code 1 are ignored."},"professional_email":{"type":"string","format":"email"},"npi":{"type":"string","description":"Exactly 10 digits, no separators. Must be an individual NPI (NPI-1) that passes the NPPES lookup (live keys) or the checksum (test keys)."},"primary_specialty":{"type":"string"},"degree":{"type":"string"},"licenses":{"type":"array","minItems":1,"items":{"type":"object","properties":{"state":{"type":"string","description":"2-letter code for one of the 50 states, DC or PR (case-insensitive; stored uppercase)."},"license_number":{"type":"string"},"license_type":{"type":"string","enum":["physician","aprn","pa","pharmd"],"description":"Aliases accepted: MD, DO (physician); NP, APRN, FNP (aprn); PA, PA-C (pa); PharmD (pharmd). Case-insensitive; stored as the canonical value."},"expiration_date":{"type":"string","format":"date","description":"YYYY-MM-DD. Required; must not be in the past (a license expiring today is valid)."}},"required":["state","license_number","license_type","expiration_date"]},"description":"The complete license set (replace semantics). At least one; no duplicate states."},"will_prescribe_controlled_substances":{"type":"boolean","default":false},"dea_number":{"type":"string","description":"Validated (DEA checksum and last-name letter) only when will_prescribe_controlled_substances is true and dea_number is present."},"dea_registration_address":{"type":"object","properties":{"line1":{"type":"string"},"line2":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"zip":{"type":"string"}}},"dea_expiration_date":{"type":"string","format":"date","description":"YYYY-MM-DD."},"dea_schedules":{"description":"Free-form; an array is stored comma-joined."},"attestations":{"type":"object","properties":{"informationAccurate":{"type":"boolean"},"licensedAndInGoodStanding":{"type":"boolean"},"willComplyWithLaws":{"type":"boolean"},"willPrescribeWithinScope":{"type":"boolean"}},"required":["informationAccurate","licensedAndInGoodStanding","willComplyWithLaws","willPrescribeWithinScope"],"description":"All four keys must be true."},"typed_signature":{"type":"string"}},"required":["full_legal_name","mobile_phone","professional_email","npi","licenses","attestations","typed_signature"]}}}},"responses":{"200":{"description":"An existing prescriber (same NPI in this organization) was updated. status shows whether it is active, pending re-review, rejected or deactivated.","content":{"application/json":{"schema":{"type":"object","properties":{"prescriber_id":{"type":"string","format":"uuid"},"onboarding_completed":{"type":"boolean","const":true},"is_active":{"type":"boolean","description":"False until a platform admin reviews and activates the prescriber."},"status":{"type":"string","enum":["pending_review","active","rejected","deactivated"],"description":"Derived status: active if is_active; else rejected if review_status is rejected; else deactivated if review_status is approved; else pending_review."},"review_status":{"type":"string","enum":["pending","approved","rejected"]},"message":{"type":"string"}},"required":["prescriber_id","onboarding_completed","is_active","status","review_status","message"]}}}},"201":{"description":"A new prescriber was created (inactive, review_status \"pending\").","content":{"application/json":{"schema":{"type":"object","properties":{"prescriber_id":{"type":"string","format":"uuid"},"onboarding_completed":{"type":"boolean","const":true},"is_active":{"type":"boolean","description":"False until a platform admin reviews and activates the prescriber."},"status":{"type":"string","enum":["pending_review","active","rejected","deactivated"],"description":"Derived status: active if is_active; else rejected if review_status is rejected; else deactivated if review_status is approved; else pending_review."},"review_status":{"type":"string","enum":["pending","approved","rejected"]},"message":{"type":"string"}},"required":["prescriber_id","onboarding_completed","is_active","status","review_status","message"]}}}},"400":{"description":"Validation failed. The body carries a stable `code` and, where applicable, the `field`. Codes: invalid_json, full_legal_name_required, mobile_phone_required, invalid_mobile_phone, professional_email_required, npi_required, invalid_npi, npi_not_found, npi_inactive, npi_not_individual, licenses_required, invalid_license, license_number_required, invalid_license_state, invalid_license_type, license_expiration_required, invalid_license_expiration, license_expired, duplicate_license_state, invalid_date_of_birth, invalid_dea_number, invalid_dea_expiration_date, attestations_required, attestation_required, typed_signature_required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Caller is not a provider organization (code provider_org_required).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"A test-mode API key tried to modify a prescriber whose NPI has been verified (code live_prescriber_protected).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Onboarding failed and nothing was saved (code onboarding_failed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Live NPPES registry lookup itself failed (code npi_lookup_failed); safe to retry. Live keys only.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"Get the organization's prescribers and their onboarding status","description":"Requires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`. Callers must be a **provider** organization.\n\nRate limit: 100 requests / 60s per organization (tier: `standard`).","tags":["Onboarding"],"operationId":"getOnboardingPrescriber","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"npi","in":"query","required":false,"description":"Only the prescriber with this NPI (exactly 10 digits).","schema":{"type":"string"}},{"name":"prescriber_id","in":"query","required":false,"description":"Only the prescriber with this id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Every prescriber in the caller's organization (or the filtered subset; an unknown npi or prescriber_id yields an empty list), oldest first.","content":{"application/json":{"schema":{"type":"object","properties":{"organization_id":{"type":"string","format":"uuid"},"prescribers":{"type":"array","items":{"type":"object","properties":{"prescriber_id":{"type":"string","format":"uuid"},"full_legal_name":{"type":"string"},"npi":{"type":"string"},"npi_verified":{"type":"boolean"},"degree":{"type":"string","nullable":true},"primary_specialty":{"type":"string","nullable":true},"onboarding_completed":{"type":"boolean"},"is_active":{"type":"boolean"},"status":{"type":"string","enum":["pending_review","active","rejected","deactivated"]},"review_status":{"type":"string","enum":["pending","approved","rejected"]},"npi_name_mismatch":{"type":"boolean","description":"True when the NPPES registry name did not match full_legal_name at submission; shown to reviewers."},"licenses":{"type":"array","items":{"type":"object"}},"dea_registrations":{"type":"array","items":{"type":"object"}}}}}},"required":["organization_id","prescribers"]}}}},"400":{"description":"Malformed filter (codes invalid_npi, invalid_prescriber_id).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Caller is not a provider organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Lookup failed (code lookup_failed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/onboarding/sessions":{"post":{"summary":"Create a hosted onboarding session for a prescriber","description":"Provider organizations only. Returns a single-use URL for RxBridge's hosted onboarding page: hand it to the prescriber (RxBridge sends no email). The URL is returned once; treat it as a credential. The prescriber completes identity, NPI and licenses, DEA (if needed), the four attestations and a typed signature on RxBridge; they always enter the attestations and signature themselves, so those cannot be prefilled. `return_url` must match an origin an RxBridge admin registered for your organization or its partner (a partner that provisions organizations through the provisioning API has its origins registered once, and every organization it provisions inherits them); without a registered origin no session can be created. On completion the prescriber is redirected to `return_url` with only `session_id`, `state` and `status`; the authoritative result is the `onboarding_session.completed` webhook and `GET /onboarding/sessions/{id}`. Completion means the prescriber entered pending review, never approval. The session inherits test or live mode from the API key. Prefill values are validated strictly: `null` and empty strings for `npi`, `mobile_phone`, `date_of_birth` and `professional_email` are rejected with `invalid_prefill` (or the field's own code, such as `invalid_npi`); omit a key to skip it.\n\nRequires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`. Callers must be a **provider** organization.\n\nRate limit: 30 requests / 60s per organization (tier: `write`).","tags":["Onboarding"],"operationId":"postOnboardingSessions","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"return_url":{"type":"string","format":"uri","maxLength":2000,"description":"Absolute URL on a registered origin, at most 2000 characters. Must be https, except that http is accepted for loopback origins (localhost, 127.0.0.1, [::1]) registered test-only and only with a test-mode API key. No fragment (not even an empty `#`), credentials, or `session_id` / `state` / `status` query parameters."},"external_ref":{"type":"string","maxLength":200,"description":"Your identifier for this prescriber. At most one open session per external_ref."},"state":{"type":"string","maxLength":500,"description":"Opaque value echoed back on the redirect."},"expires_in":{"type":"integer","minimum":300,"maximum":604800,"default":1800,"description":"Seconds the link stays valid."},"replace":{"type":"boolean","default":false,"description":"Cancel an open session for the same external_ref and mint a new one."},"prefill":{"type":"object","description":"Advisory, editable prescriber data. Never verified, never placed in a URL. `attestations` and `typed_signature` are rejected.","properties":{"full_legal_name":{"type":"string"},"professional_email":{"type":"string"},"mobile_phone":{"type":"string"},"date_of_birth":{"type":"string","format":"date"},"npi":{"type":"string"},"degree":{"type":"string"},"primary_specialty":{"type":"string"},"licenses":{"type":"array","items":{"type":"object"},"description":"May be empty or partial; every entry present is validated in full."},"will_prescribe_controlled_substances":{"type":"boolean"},"dea_number":{"type":"string"},"dea_expiration_date":{"type":"string","format":"date"},"dea_registration_address":{"type":"object"},"dea_schedules":{"description":"Array or comma-separated string."}}}},"required":["return_url","external_ref"]}}}},"responses":{"201":{"description":"Session created. `url` is returned once and cannot be recovered.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"url":{"type":"string","format":"uri"},"expires_at":{"type":"string","format":"date-time"}},"required":["id","url","expires_at"]}}}},"400":{"description":"Validation error; see `code` and `field`. Session codes: `invalid_json`, `return_url_required`, `invalid_return_url`, `external_ref_required`, `invalid_external_ref`, `invalid_state`, `invalid_expires_in`, `prefill_field_not_allowed`, `invalid_prefill`; prefill values that fail prescriber validation return that validator's code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`provider_org_required` (not a provider organization), or `return_url_not_allowed` (the origin is not registered for your organization or its partner, or it is not allowed for this key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`session_already_open`: an open session exists for this external_ref (body has `session_id`, `expires_at`). DELETE it or pass `replace: true`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error; the body has only `error`. If a retry returns `session_already_open`, the first attempt created a session whose URL you never received: pass `replace: true`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/onboarding/sessions/{id}":{"get":{"summary":"Get a hosted onboarding session","description":"Returns the session state. `status` is evaluated against `expires_at` on every read, so an overdue open session reads `expired`. Once completed, `prescriber_id`, `prescriber_status`, `review_status` and a license summary (no license numbers) are included. Another organization's session id returns 404 like an unknown id.\n\nRequires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`. Callers must be a **provider** organization.\n\nRate limit: 100 requests / 60s per organization (tier: `standard`).","tags":["Onboarding"],"operationId":"getOnboardingSessionsId","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Session id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The session.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingSession"}}}},"400":{"description":"`invalid_session_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`provider_org_required`: not a provider organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`session_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error; the body has only `error`. Safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Cancel a hosted onboarding session","description":"Withdraws an open session (also releases its external_ref). Idempotent on an already-canceled session; an already-expired session is returned as `expired`. A completed session cannot be canceled.\n\nRequires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`. Callers must be a **provider** organization.\n\nRate limit: 30 requests / 60s per organization (tier: `write`).","tags":["Onboarding"],"operationId":"deleteOnboardingSessionsId","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Session id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The session after cancellation (`canceled`), or `expired` if it had already expired.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["canceled","expired"]}},"required":["id","status"]}}}},"400":{"description":"`invalid_session_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`provider_org_required`: not a provider organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`session_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`session_already_completed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error; the body has only `error`. Safe to retry: cancelling is idempotent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/pharmacies":{"get":{"summary":"Search pharmacies","description":"Returns every pharmacy on Rx-Bridge (not scoped to the caller's contracts), optionally filtered.\n\nRequires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`.\n\nRate limit: 100 requests / 60s per organization (tier: `standard`).","tags":["Pharmacies"],"operationId":"getPharmacies","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"city","in":"query","required":false,"description":"Case-insensitive substring match.","schema":{"type":"string"}},{"name":"state","in":"query","required":false,"description":"Case-insensitive exact match.","schema":{"type":"string"}},{"name":"services","in":"query","required":false,"description":"Comma-separated list; matches pharmacies offering at least one of the given services.","schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"Default 1.","schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"description":"Default 25, max 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}],"responses":{"200":{"description":"Paginated list of pharmacies.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Pharmacy"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"]}}}}}}},"/api/v1/pharmacies/recommend":{"get":{"summary":"Recommend a pharmacy for a patient","description":"Provider organizations only. Returns the best pharmacy to route a script to, with a short `reason`, plus up to four `alternatives`. Only active, onboarded pharmacies with an unexpired primary license that serve `patient_state` (states served, or an unexpired pharmacy license or DEA registration there) are considered; when `medication_id` is sent, the pharmacy must also list that medication as available. Ranking: an active contract with your organization first, then more services offered, then name. `id` is the pharmacy organization id, the value `POST /scripts` takes as `pharmacyOrgId`. `recommendation` is null (and `alternatives` empty) when no pharmacy qualifies.\n\nRequires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`. Callers must be a **provider** organization.\n\nRate limit: 100 requests / 60s per organization (tier: `standard`).","tags":["Pharmacies"],"operationId":"getPharmaciesRecommend","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"patient_state","in":"query","required":true,"description":"Two-letter US state or territory code of the patient (case-insensitive).","schema":{"type":"string","minLength":2,"maxLength":2}},{"name":"medication_id","in":"query","required":false,"description":"Optional medication ID (from GET /medications). When sent, only pharmacies stocking it are considered.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The recommendation and alternatives.","content":{"application/json":{"schema":{"type":"object","properties":{"recommendation":{"type":["object","null"],"properties":{"pharmacy":{"$ref":"#/components/schemas/PharmacyRecommendationItem"},"reason":{"type":"string"}},"required":["pharmacy","reason"]},"alternatives":{"type":"array","items":{"$ref":"#/components/schemas/PharmacyRecommendationItem"}}},"required":["recommendation","alternatives"]}}}},"400":{"description":"Error code `invalid_patient_state` (missing or not a two-letter state code) or `invalid_medication_id` (not a UUID).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Caller is not a provider organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/pricing/estimate":{"post":{"summary":"Estimate provider fee and/or pharmacy commission","description":"Provider organizations get providerFee (and providerFeeBreakdown, if includeBreakdown is true) based on their current-month script volume plus add-ons. Pharmacy organizations get pharmacyCommission when orderValue is given. Fields are populated based on the caller's organization type — a pharmacy org never gets providerFee, a provider org never gets pharmacyCommission.\n\nRequires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`.\n\nRate limit: 100 requests / 60s per organization (tier: `standard`).","tags":["Pricing"],"operationId":"postPricingEstimate","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"addOns":{"type":"object","description":"Provider organizations only. Note: as of this API version every add-on fee is $0 (ADD_ON_FEES in lib/pricing/pharmacy.ts) — pricing is reserved for future use but not yet active.","properties":{"priorityProcessing":{"type":"boolean"},"deliveryTrackingEnabled":{"type":"boolean"},"autoRefillRemindersEnabled":{"type":"boolean"}}},"orderValue":{"type":"number","description":"Pharmacy organizations only. Required to receive a pharmacyCommission estimate."},"includeBreakdown":{"type":"boolean","description":"Provider organizations only. When true, adds providerFeeBreakdown to the response."}}}}}},"responses":{"200":{"description":"Estimate. Any field may be absent depending on organization type and inputs.","content":{"application/json":{"schema":{"type":"object","properties":{"providerFee":{"type":"number","description":"Volume-tiered base fee plus add-on fees, rounded to 2 decimals. See lib/pricing/pharmacy.ts's VOLUME_TIERS."},"providerFeeBreakdown":{"type":"object","properties":{"baseFee":{"type":"number"},"addOnFee":{"type":"number"},"totalFee":{"type":"number"}},"description":"Only present when includeBreakdown is true."},"pharmacyCommission":{"type":"number","description":"6% of orderValue, clamped to [$1.00, $20.00], rounded to 2 decimals."}}}}}}}}},"/api/v1/scripts":{"post":{"summary":"Create a script","description":"Provider organizations only. Requires an active, onboarded prescriber: with exactly one it is used automatically; with several, name one with prescriber_id or prescriber_npi (otherwise 400 prescriber_required). The prescriber must hold an unexpired license in the patient's state (patientState); that license is stored on the script and sent on the pharmacy order (422 no_license_for_state). If patientState is omitted the prescriber's only unexpired license is used, and a prescriber licensed in several states must send patientState. Validates NCPDP rules (Schedule II refills/quantity, NDC format, DAW code, ICD-10 diagnosis codes) and that the pharmacy and prescriber licenses are current. On success, asynchronously submits to LifeFile when eligible and fires script.created webhooks (and script.approval_requested, if requiresPrescriberApproval was set) to the provider org and, if set, the pharmacy org. Scripts created with a test-mode API key are never submitted to the pharmacy integration and their webhooks carry livemode: false.\n\nRequires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`. Callers must be a **provider** organization.\n\nRate limit: 30 requests / 60s per organization (tier: `write`).","tags":["Scripts"],"operationId":"postScripts","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pharmacyOrgId":{"type":"string","format":"uuid"},"patientName":{"type":"string"},"patientPhone":{"type":"string","nullable":true},"patientEmail":{"type":"string","nullable":true},"patientDateOfBirth":{"type":"string","nullable":true,"description":"ISO date. Not required to create a script, but LifeFile order submission requires it — see lib/lifefile/order-mapping.ts."},"patientAddressLine1":{"type":"string","nullable":true},"patientAddressLine2":{"type":"string","nullable":true},"patientCity":{"type":"string","nullable":true},"patientState":{"type":"string","nullable":true},"patientZip":{"type":"string","nullable":true},"patientGender":{"type":"string","nullable":true},"patientAllergies":{"type":"array","items":{"type":"string"},"nullable":true},"insuranceProvider":{"type":"string","nullable":true},"insurancePolicyId":{"type":"string","nullable":true},"insuranceGroupNumber":{"type":"string","nullable":true},"insuranceRxBin":{"type":"string","nullable":true},"insuranceRxPCN":{"type":"string","nullable":true},"insuranceCardholderName":{"type":"string","nullable":true},"insuranceRelationship":{"type":"string","nullable":true},"medicationName":{"type":"string"},"medicationId":{"type":"string","format":"uuid","description":"Optional catalog product id (`id` from GET /v1/medications). Must be an available product at the script's pharmacy, else 400. When set, the pharmacy's LifeFile product id is sent with the order."},"ndc":{"type":"string","nullable":true,"description":"10 digits (4-4-2) or 11 digits (5-4-2); hyphens optional."},"strength":{"type":"string","nullable":true},"quantity":{"type":"string","nullable":true},"directions":{"type":"string","nullable":true},"refills":{"type":"integer","minimum":0,"default":0,"description":"Must be 0 for Schedule II (controlledSubstanceSchedule \"2\"/\"II\") — NCPDP rule."},"prescriptionDate":{"type":"string","nullable":true},"substitutionAllowed":{"type":"boolean","default":true},"dawCode":{"type":"number","nullable":true,"description":"0-9."},"priorAuthRequired":{"type":"boolean","default":false},"priorAuthNumber":{"type":"string","nullable":true},"controlledSubstanceSchedule":{"type":"string","nullable":true},"quantityWritten":{"type":"string","nullable":true,"description":"Required (positive number) for Schedule II — NCPDP rule."},"daysSupply":{"type":"number","nullable":true},"diagnosisCodes":{"type":"array","items":{"type":"string"},"nullable":true,"description":"ICD-10-CM codes, e.g. \"A00.0\"."},"specialInstructions":{"type":"string","nullable":true},"pharmacyNotes":{"type":"string","nullable":true},"priorityProcessing":{"type":"boolean","default":false},"deliveryTrackingEnabled":{"type":"boolean","default":false},"autoRefillRemindersEnabled":{"type":"boolean","default":false},"externalSystem":{"type":"string","nullable":true},"externalSystemScriptId":{"type":"string","nullable":true},"requiresPrescriberApproval":{"type":"boolean","default":false,"description":"Set true when the submitter is not the assigned prescriber, to route the script through prescriber sign-off before pharmacy fulfillment."},"submittedByName":{"type":"string","nullable":true,"description":"Display/audit-only."},"submittedByExternalId":{"type":"string","nullable":true,"description":"Caller's own opaque submitter id. Never resolved to an Rx-Bridge user."},"clinicalNote":{"type":"string","nullable":true,"maxLength":2000,"description":"Optional free-text clinical context from the submitter, intended for the approving prescriber. Trimmed; an empty value is stored as null; more than 2000 characters returns 400 with details.clinicalNote. Returned as `clinicalNote` on the script, to the provider organization's keys only (always null for the pharmacy organization)."},"prescriber_id":{"type":"string","nullable":true,"description":"Which of the organization's prescribers signs this script. Optional when the organization has exactly one active prescriber; required (this or prescriber_npi) when it has several. Send at most one of prescriber_id / prescriber_npi (both is allowed only if they name the same prescriber)."},"prescriber_npi":{"type":"string","nullable":true,"description":"10-digit NPI of the prescriber that signs this script. Alternative to prescriber_id."}},"required":["pharmacyOrgId","patientName","medicationName"]}}}},"responses":{"201":{"description":"Script created, status \"pending\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Script"}}}},"400":{"description":"Zod validation failure, an NCPDP rule violation (Schedule II refills/quantity, NDC format, DAW code, ICD-10 format), or a prescriber-selection error with a `code`: invalid_json, prescriber_required (several active prescribers and none named — the body lists active_prescribers), invalid_prescriber_npi, prescriber_selector_conflict.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Caller is not a provider org, or the prescriber/pharmacy validation failed. A 403 with \"code\": no_active_contract means the provider has no active contract with the named pharmacy, so nothing was created or sent. A prescriber-caused 403 may carry a \"code\": no_prescriber (the organization has no prescriber), prescriber_pending_review (a prescriber exists but awaits platform review; also returned for an active prescriber whose onboarding is incomplete), prescriber_rejected (the review was rejected), prescriber_deactivated (previously approved, now deactivated), license_expired (every state license of the prescriber has expired), or prescriber_npi_unverified (live key only: the prescriber was activated in test mode without NPI registry verification and can sign only test scripts). When several prescribers exist and none is usable, the reason follows the priority prescriber_pending_review, then prescriber_deactivated, then prescriber_rejected. Other 403 reasons (no state license on file, a missing or expired DEA registration for a controlled-substance script, an expired pharmacy license, and so on) carry only \"error\", so treat \"code\" as optional and fall back to the \"error\" text.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string","enum":["no_prescriber","prescriber_pending_review","prescriber_rejected","prescriber_deactivated","license_expired","prescriber_npi_unverified"]}},"required":["error"]}}}},"404":{"description":"prescriber_not_found: prescriber_id / prescriber_npi does not match a prescriber in the caller's organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"no_license_for_state: the selected prescriber has no unexpired license in the patient's state (or patientState is required because the prescriber is licensed in several states).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List scripts","description":"Provider organizations see scripts they created; pharmacy organizations see scripts routed to them.\n\nRequires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`.\n\nRate limit: 100 requests / 60s per organization (tier: `standard`).","tags":["Scripts"],"operationId":"getScripts","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"status","in":"query","required":false,"description":"Filter to a single script status.","schema":{"type":"string"}},{"name":"start_date","in":"query","required":false,"description":"ISO date/datetime. Filters createdAt >= start_date.","schema":{"type":"string","format":"date-time"}},{"name":"end_date","in":"query","required":false,"description":"ISO date/datetime. Filters createdAt <= end_date.","schema":{"type":"string","format":"date-time"}},{"name":"search","in":"query","required":false,"description":"Case-insensitive substring match against patient name or medication name.","schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"Default 1.","schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"description":"Default 25, max 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}],"responses":{"200":{"description":"Paginated list of scripts, newest first.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Script"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"]}}}}}}},"/api/v1/scripts/{id}":{"get":{"summary":"Get a single script","description":"Caller's organization must be either the providerOrgId or pharmacyOrgId on the script.\n\nRequires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`.\n\nRate limit: 100 requests / 60s per organization (tier: `standard`).","tags":["Scripts"],"operationId":"getScriptsId","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Script ID.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The script.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Script"}}}},"404":{"description":"Script not found, or not a script of the caller's organization. A test-mode key also gets 404 for live scripts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/scripts/{id}/events":{"get":{"summary":"List a script's timeline events","description":"Chronological history of a script, oldest first: submission, prescriber approval request, approval or rejection, status changes and shipment updates. Each event carries a `source` (`medoura` or `praxis` when the action came through that partner's API key, `pharmacy` when the routed pharmacy acted, otherwise `rxbridge`). `actorName` is only set for the submitter (submission, approval request) and the assigned prescriber (approval, rejection); it is null for pharmacy activity. Caller's organization must be either the providerOrgId or pharmacyOrgId on the script.\n\nRequires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`.\n\nRate limit: 100 requests / 60s per organization (tier: `standard`).","tags":["Scripts"],"operationId":"getScriptsIdEvents","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Script ID.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The events, oldest first.","content":{"application/json":{"schema":{"type":"object","properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/ScriptEvent"}}},"required":["events"]}}}},"404":{"description":"Script not found, including a malformed id or a script of another organization. A test-mode key also gets 404 for live scripts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/scripts/{id}/shipment":{"get":{"summary":"Get shipment/tracking info for a script","description":"Caller's organization must be either the providerOrgId or pharmacyOrgId on the script.\n\nRequires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`.\n\nRate limit: 100 requests / 60s per organization (tier: `standard`).","tags":["Scripts"],"operationId":"getScriptsIdShipment","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Script ID.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Shipment info.","content":{"application/json":{"schema":{"type":"object","properties":{"carrier":{"type":"string","nullable":true},"trackingNumber":{"type":"string","nullable":true},"shippingStatus":{"type":"string","nullable":true}},"required":["carrier","trackingNumber","shippingStatus"]}}}},"404":{"description":"Script not found, or not a script of the caller's organization. A test-mode key also gets 404 for live scripts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Update shipment/tracking info for a script","description":"Caller's organization must be either the providerOrgId or pharmacyOrgId on the script, same as GET. Any given field replaces the existing value; omitted fields (or empty strings) keep the script's current value. Fires script.shipment_updated webhooks to the provider and pharmacy orgs on the script.\n\nRequires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`.\n\nRate limit: 100 requests / 60s per organization (tier: `standard`).","tags":["Scripts"],"operationId":"postScriptsIdShipment","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Script ID.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"carrier":{"type":"string"},"trackingNumber":{"type":"string"},"shippingStatus":{"type":"string"}}}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true}},"required":["success"]}}}},"403":{"description":"A test-mode API key was used on a live script (code live_script_protected; nothing is changed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Script not found, or not a script of the caller's organization. A test-mode key also gets 404 for live scripts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/scripts/{id}/status":{"patch":{"summary":"Update a script's fulfillment status","description":"Pharmacy organizations only, and only for scripts routed to their own organization. Transitions must be legal per lib/workflow/script-status.ts's ALLOWED_TRANSITIONS (e.g. a script can't jump straight from \"pending\" to \"completed\"). Two controlled-substance-only compliance gates apply on the transition that first reaches the target status (not on same-status \"re-save\" calls): moving into \"verified\" requires a diversion red-flag review (redFlagReviewConfirmed + reviewedByUserId) when controlledSubstanceSchedule is set; moving into \"completed\" requires photo-ID verification (idVerificationType + idVerifiedByUserId, optionally idVerificationLast4) when controlledSubstanceSchedule is set. reviewedByUserId/idVerifiedByUserId must be a pharmacist or admin member of the caller's organization. Fires script.status_changed webhooks to the caller's org and the script's provider org.\n\nRequires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`. Callers must be a **pharmacy** organization.\n\nRate limit: 100 requests / 60s per organization (tier: `standard`).","tags":["Scripts"],"operationId":"patchScriptsIdStatus","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Script ID.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["pending","processing","on_hold_pending_pa","verified","ready","completed","cancelled","rejected"],"description":"Target status. Must be a legal transition from the script's current status."},"orderValue":{"type":"number","description":"Persisted as the script's orderValue on every call that includes it (used for pharmacy commission once the script reaches \"completed\")."},"redFlagReviewConfirmed":{"type":"boolean","description":"Required with reviewedByUserId when status is \"verified\" and the script is a controlled substance, on the transition into \"verified\"."},"reviewedByUserId":{"type":"string","format":"uuid","description":"Must be a pharmacist or admin member of the caller's organization."},"reviewNotes":{"type":"string"},"idVerificationType":{"type":"string","enum":["drivers_license","state_id","passport","military_id","other"],"description":"Required (with idVerifiedByUserId) when status is \"completed\" and the script is a controlled substance, on the transition into \"completed\"."},"idVerificationLast4":{"type":"string","pattern":"^[A-Za-z0-9]{1,4}$","description":"Last 4 characters only — never the full ID number."},"idVerifiedByUserId":{"type":"string","format":"uuid","description":"Must be a pharmacist or admin member of the caller's organization."}},"required":["status"]}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true}},"required":["success"]}}}},"400":{"description":"Invalid status value, illegal status transition, or a controlled-substance compliance gate (red-flag review / ID verification) not satisfied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Caller is not a pharmacy organization, or a test-mode API key was used on a live script (code live_script_protected; nothing is changed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Script not found, or not a script of the caller's organization. A test-mode key also gets 404 for live scripts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/webhooks":{"post":{"summary":"Register a webhook endpoint","description":"Generates an HMAC-SHA256 signing secret (\"whsec_...\"), returned only in this response — store it to verify the x-rxbridge-signature header on delivered payloads.\n\nRequires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`.\n\nRate limit: 100 requests / 60s per organization (tier: `standard`).","tags":["Webhooks"],"operationId":"postWebhooks","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string","enum":["script.created","script.status_changed","script.shipment_updated","script.approval_requested","script.approved","script.rejected","contract.created","contract.accepted","contract.rejected","contract.terminated","prescriber.onboarded","prescriber.activated","prescriber.rejected","prescriber.deactivated","onboarding_session.completed","*"]},"description":"Non-empty. \"*\" subscribes to every event. contract.created, contract.accepted, contract.rejected and contract.terminated are reserved: they can be subscribed to but are not emitted yet. prescriber.* events carry data { prescriber_id, npi, status, reason?, livemode } — reason is present only on prescriber.rejected and prescriber.deactivated. onboarding_session.completed (a prescriber finished a hosted onboarding session) carries data { session_id, external_ref, prescriber_id, npi, status, livemode }; status is the prescriber's status at completion (pending_review, never approval)."},"description":{"type":"string"}},"required":["url","events"]}}}},"responses":{"201":{"description":"Webhook registered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreated"}}}},"400":{"description":"url missing, not https, embedded credentials, or resolving to a private or internal address; or events missing/empty/containing an unrecognized event name.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List webhook endpoints","description":"Signing secrets are never included in list responses — only in the POST /webhooks response, at creation time.\n\nRequires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`.\n\nRate limit: 100 requests / 60s per organization (tier: `standard`).","tags":["Webhooks"],"operationId":"getWebhooks","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"responses":{"200":{"description":"Array of webhooks (not a paginated envelope).","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}}}}}}}},"/api/v1/webhooks/{id}":{"get":{"summary":"Get a single webhook endpoint","description":"Requires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`.\n\nRate limit: 100 requests / 60s per organization (tier: `standard`).","tags":["Webhooks"],"operationId":"getWebhooksId","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Webhook endpoint ID.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The webhook (no secret).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"}}}},"404":{"description":"Webhook not found (or not owned by the caller's organization).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a webhook endpoint","description":"Every field is optional; only fields present in the body are changed. The signing secret cannot be rotated via this endpoint.\n\nRequires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`.\n\nRate limit: 100 requests / 60s per organization (tier: `standard`).","tags":["Webhooks"],"operationId":"patchWebhooksId","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Webhook endpoint ID.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string","enum":["script.created","script.status_changed","script.shipment_updated","script.approval_requested","script.approved","script.rejected","contract.created","contract.accepted","contract.rejected","contract.terminated","prescriber.onboarded","prescriber.activated","prescriber.rejected","prescriber.deactivated","onboarding_session.completed","*"]},"description":"Non-empty if given."},"description":{"type":"string"},"is_active":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Updated webhook.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"}}}},"400":{"description":"url invalid, or events empty/containing an unrecognized event name.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Webhook not found (or not owned by the caller's organization).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a webhook endpoint","description":"Requires an organization API key via `Authorization: Bearer <key>` or `x-api-key: <key>`.\n\nRate limit: 100 requests / 60s per organization (tier: `standard`).","tags":["Webhooks"],"operationId":"deleteWebhooksId","security":[{"apiKeyBearer":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Webhook endpoint ID.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true}},"required":["success"]}}}},"404":{"description":"Webhook not found (or not owned by the caller's organization).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}