Overview
The LegUpRx API exposes the same data you see in the partner portal: leads, orders, payouts, sub-accounts, medical intake links, DNA testing links, Care Service links, and Care Services bulk coupon codes. It also publishes webhook events when those records change, and a separate public catalog API for product browsing on telehealthintakeforms.com.
Base URL: /
Format: JSON in, JSON out. All authenticated responses are wrapped in an envelope - typically { data: [...] } with an optional { page: { nextCursor, limit } } for paginated endpoints.
Authentication: authenticated endpoints require a per-user API token created in Settings - API Settings. Public catalog endpoints under https://www.telehealthintakeforms.com/api are unauthenticated and use a partner_id query parameter on the intake link itself for attribution.
Authentication
API tokens are created and managed in Settings - API Settings. Each token is tied to a single portal user. Tokens can be rotated or revoked from that page at any time; revoked tokens stop working immediately.
Send the token in the Authorization header on every request:
Authorization: Bearer <token>
Tokens are scoped to your Clerk user account and start with lrx_live_. The full token is shown only once when you create or rotate it, so save it somewhere secure.
Auth-related errors
| Status | Code | Description |
|---|---|---|
| 401 | missing_bearer | No Authorization: Bearer header was sent. |
| 401 | invalid_token | Token is malformed, revoked, expired, or not user-scoped. |
| 401 | unknown_user | Token verifies but no portal user matches its subject. |
| 503 | api_keys_unavailable | Reserved for environments where API token verification cannot run. |
Authenticated request
GET /api/v1/leads HTTP/1.1 Host: app.leguprx.com Authorization: Bearer $LEGUPRX_API_TOKEN Accept: application/json
List leads
Returns leads collected for your partner account, ordered newest first. Paginated using a cursor; pass the nextCursor value from the previous response in the cursor query parameter to fetch the next page.
Self-hosted elective medication intakes create a lead progressively: account registration creates the row, and later intake steps can add phone and internal product/tier context to the same intake session. These rows use origin: "headless_intake" and location contains the configured product category. A row may therefore contain fewer contact fields on an early request and more when fetched again later. Product/tier context is not part of this public response.
**Elective-medication test runs are excluded by default.** Pass includeTest=true (see Query parameters) to include them. DNA, Care Services, and other lead sources are unaffected. See "Test and simulated data" for the two kinds of test run.
Query parameters
| Name | Type | Description |
|---|---|---|
limit | integer (1-200) | Page size. Defaults to 50. |
cursor | string | Opaque pagination cursor from the previous response. |
includeTest | boolean | When `true`, also return leads from elective-medication test and simulated intake runs. Omit it (or pass anything else) for real leads only — that is what a CRM sync should use. Test rows always carry `isTest`, so including them never makes them look like real patients. Only elective medication intakes produce test rows; DNA, Care Services, and other lead sources are unaffected. See "Test and simulated data". |
Response
Envelope: { data: LeadRow[], page: { nextCursor, limit } }. If a lead has not consented to email or SMS, the matching email or phone field is returned as No Consent.
Two fields mark a test run from an elective medication intake:
- isTest: true when the lead came from a test or simulated run rather than from a person. Always present; false on every real lead and on every lead recorded before test tagging existed.
- testSource: why it is a test — "provider_test_mode" or "simulation" — and null when isTest is false.
Nothing else about a test lead is altered. origin still reads headless_intake, and the name and contact details are whatever was entered, because a mangled row is a row no later reader can trust. The flag is the only marker, so filter on it rather than on a name pattern.
{
"data": [
{
"id": "6f1a2c0e-4f0d-4c0a-9b41-7c9c2c0c0001",
"partnerId": "RFMLPVN1",
"subId": "12",
"firstName": "Jane",
"lastName": "Doe",
"email": "jane.doe@example.com",
"phone": "+15555550123",
"origin": "headless_intake",
"location": "weight-loss",
"questionId": null,
"createdAt": "2026-05-09T17:42:11.214Z",
"updatedAt": "2026-05-09T17:42:11.214Z",
"isTest": false,
"testSource": null
}
],
"page": {
"nextCursor": "MjAyNi0wNS0wOVQxNzo0MjoxMS4yMTRafDZmMWEyYzBlLTRmMGQtNGMwYS05YjQxLTdjOWMyYzBjMDAwMQ==",
"limit": 50
}
}Errors
| Status | Code | Description |
|---|---|---|
| 400 | invalid_request | `limit` is not a positive integer. |
| 401 | missing_bearer | Authorization header missing. |
| 401 | invalid_token | Token is invalid, revoked, or expired. |
| 429 | rate_limited | Too many requests for this token. |
GET /api/v1/leads
GET /api/v1/leads?limit=50 HTTP/1.1 Host: app.leguprx.com Authorization: Bearer $LEGUPRX_API_TOKEN Accept: application/json
List DNA testing links
Returns DNA test intake links available for your account. Requires DNA access; otherwise returns 403.
Response
Envelope: { data: DnaProductLink[] }.
{
"data": [
{
"recordId": "09a889e4-88cc-4b4f-8f37-7f93654d1ec1",
"email": "partner@example.com",
"partnerId": "RFMLPVN1",
"productName": "MTHFR Methylation Panel",
"description": "At-home DNA collection for methylation pathway insights.",
"intakeUrl": "https://medical.leguprecovery.com/start-online-visit/dna-mthfr?partner_id=RFMLPVN1",
"productImageUrl": "https://www.telehealthintakeforms.com/api/files/7c5271fd-b347-46d7-8e44-c0d3762ab000",
"minimumPrice": 199,
"recommendedPrice": 249,
"maximumPrice": 349,
"userSetPrice": 249,
"parentProduct": [
"MTHFR Methylation Panel"
],
"available": true
}
]
}Errors
| Status | Code | Description |
|---|---|---|
| 401 | missing_bearer | Authorization header missing. |
| 403 | forbidden | DNA access is disabled for this account. |
GET /api/v1/dna-links
GET /api/v1/dna-links HTTP/1.1 Host: app.leguprx.com Authorization: Bearer $LEGUPRX_API_TOKEN Accept: application/json
List medical intake links
Returns medical intake product links assigned to your account. Pass sub to rewrite each link's partner_id to attribute orders to the given sub-account (1-250).
**Self-hosted intake forms.** Products configured for our self-hosted intake return a url in the form https:// instead of the older provider-hosted link, and report urlStyle: "headless". The previous link is still returned as legacyUrl, and both remain valid, so any URL you have already embedded keeps working. Products not yet configured, or where the override is switched off, return urlStyle: "legacy" with url and legacyUrl identical.
Headless links accept m=1|3|6 to preselect an available tier and preserve documented UTM/click attribution. Unsupported, duplicate, or invalid parameters are removed. Never append a patient name, email, phone, date of birth, questionnaire answer, token, or other patient data to an intake URL: the initial request can be recorded by hosting infrastructure before the form canonicalizes it. Headless forms can be embedded in an iframe; use your verified partner embedding domain where configured.
Query parameters
| Name | Type | Description |
|---|---|---|
sub | integer (1-250) | Sub-account number. The matching `subId` must already exist on your account. |
lang | string (`en` or `es`) | Optional language for supported self-hosted intake links. Unrecognized or unavailable values fall back to the partner default. |
Response
Envelope: { data: PartnerProductLink[], sub: string | null }.
Each link includes:
- url: the link to share. Headless when configured, otherwise the legacy provider-hosted link.
- legacyUrl: the provider-hosted link, always present so you can compare during the transition.
- urlStyle: "headless" or "legacy".
{
"data": [
{
"recordId": "7a56b350-5a59-4df1-806c-108b39439180",
"email": "partner@example.com",
"partnerId": "RFMLPVN1",
"productName": "Injectable Semaglutide",
"details": "Available Nationwide",
"url": "https://intake.leguprx.com/sema-personalized?partner_id=RFMLPVN1",
"legacyUrl": "https://medical.leguprecovery.com/start-online-visit/sema-personalized-intake-vialsrx?partner_id=RFMLPVN1",
"urlStyle": "headless",
"imageUrl": "https://www.telehealthintakeforms.com/api/files/9c9585f5-4783-4c2b-8776-93e51f547409",
"category": [
"weight-loss"
],
"suggestedPrices": "$349 / 1 month",
"tiers": [
{
"key": "1-month",
"label": "1 month",
"minPrice": 299,
"maxPrice": 599,
"userPrice": 349,
"commission": 75
}
]
}
],
"sub": null
}Errors
| Status | Code | Description |
|---|---|---|
| 400 | invalid_request | `sub` is out of range or unknown for this account. |
| 401 | missing_bearer | Authorization header missing. |
GET /api/v1/medical-intake-links
GET /api/v1/medical-intake-links HTTP/1.1 Host: app.leguprx.com Authorization: Bearer $LEGUPRX_API_TOKEN Accept: application/json
List Care Service links
Returns Care Service links available to your account. Results are filtered by active subscription/manual override, yearly-plan bundle access, individual service access, and category feature flags. URLs in the response are generated from the admin-configured Care Services base URLs; the examples below use placeholder domains.
Each result includes a default url (no duration pre-selected), a redeemUrl for Bulk Code Redemption, and a tiers array. Each tier contains a tier-specific url that already includes the matching m= query parameter, so you can hand the link directly to a patient without rebuilding it yourself.
The URLs are the same patient-facing checkout links rendered in the partner portal under Partner Links - Care Services. Patient-facing URL parameters appended to each link are documented in the "URL parameters" table below.
Query parameters
| Name | Type | Description |
|---|---|---|
service | string | Optional service key: primary-care, urgent-care, mental-health, dermatology, or veterinary-care. |
sub | integer (1-250) | Optional sub-account number. The matching `subId` must already exist on your account. When provided, every returned URL has `sub=<id>` appended for attribution. |
duration | integer (1, 3, 6, 12) | Optional duration. When provided, the top-level `url` includes `m=<duration>` and the `tiers` array is filtered to that duration only. |
Response
Envelope: { data: CareServiceLink[], sub, service, duration }. Admin-only catalog fields and pricing caps are not returned.
**Patient-facing URL parameters** (already appended to every url/redeemUrl we return - documented so you understand how to interpret or, if needed, regenerate them):
- partner_id (always present): your partner attribution code. Required for revenue and order attribution.
- m (optional, present on tier urls and when duration is requested): term in months. One of 1, 3, 6, 12. Pre-selects the matching tier on the checkout page.
- sub (optional, present when sub was passed): sub-account number 1-250 for downstream attribution.
- redeem=true (only on redeemUrl): switches the destination from the card-checkout flow to the Bulk Code Redemption screen. Patients enter a 12-character coupon code (no card collected) and we provision the membership directly.
- lang (optional): accepted values are en and es. Returned URLs already carry the partner default when it is an enabled non-English language. An unrecognized or disabled value falls back to the partner default.
{
"data": [
{
"recordId": "2d2a6e76-41b5-4b64-a5fb-c1d9de8f0001",
"service": "primary-care",
"productName": "Primary Care Visit",
"description": "Virtual primary care access for eligible patients.",
"imageUrl": "https://example-care-services.com/assets/primary-care.png",
"url": "https://example-care-services.com/start?partner_id=RFMLPVN1",
"redeemUrl": "https://example-care-services.com/start?partner_id=RFMLPVN1&redeem=true",
"tiers": [
{
"key": "1-month",
"label": "1 month",
"userPrice": 99,
"commission": 30,
"url": "https://example-care-services.com/start?partner_id=RFMLPVN1&m=1"
}
]
}
],
"sub": null,
"service": null,
"duration": null
}Errors
| Status | Code | Description |
|---|---|---|
| 400 | invalid_request | `sub`, `service`, or `duration` is invalid. |
| 401 | missing_bearer | Authorization header missing. |
| 404 | not_found | Care Services API is disabled (CARE_SERVICES_API_ENABLED is off). |
| 429 | rate_limited | Too many requests for this token. |
GET /api/v1/care-service-links
GET /api/v1/care-service-links?service=primary-care&duration=1 HTTP/1.1 Host: app.leguprx.com Authorization: Bearer $LEGUPRX_API_TOKEN Accept: application/json
List Care Services coupons
Download Care Services bulk coupon codes owned by the authenticated partner. Each coupon is single-use, locked to a productSlug + months combination, and either redeemed by a patient via your Bulk Code Redemption URL or marked void/expired. Admin tokens may filter by any partner_id; non-admin tokens are automatically scoped to their own partner.
Use this endpoint to programmatically pull every code you have purchased (including ones still in active status that have not yet been redeemed) so you can distribute them, store them in your own system, or reconcile redemptions.
Returns JSON by default. Pass format=csv to download a spreadsheet (the same columns as the "Download all CSV" button in the partner portal Bulk Orders view) instead.
Query parameters
| Name | Type | Description |
|---|---|---|
status | string (csv) | Optional comma-separated subset of `active,redeemed,void,expired`. |
partner_id | string | Admin only. Filter to a specific partner. |
product_slug | string | Optional product slug filter. Valid slugs: `urgent-care`, `urgent-plus`, `primary`, `primary-plus`, `vet-care`, `behavior`, `primary-family-plan`, `primary-family-plan-and-discount-program`. The two `primary-family-plan*` slugs are Primary Care family plans that cover a household under one membership; they map to the `primary-care` service category. |
format | string | Response format: `json` (default) or `csv`. When `csv`, the response is a `text/csv` attachment named `care-services-coupons-<YYYY-MM-DD>.csv` and the total row count is returned in the `X-Total-Count` header. |
limit | integer (1-500) | Page size. Defaults to 100 for JSON, 500 for CSV. |
offset | integer (>=0) | Page offset. Defaults to 0. For full CSV exports of large coupon batches, paginate by incrementing `offset` and concatenating files. |
Response
JSON envelope: { data: CareServiceCoupon[], total, limit, offset }. Each coupon includes code (the raw 12-character A-Z 0-9 string), productSlug, months, status, expiresAt, redeemedAt, redeemedEmail, and redeemedLyricPrimaryExternalId so you can reconcile who used what.
CSV columns (with format=csv): code, product_slug, months, status, partner_id, sub_account_id, unit_price_cents, bulk_order_id, purchaser_email, purchaser_source, created_at, expires_at, redeemed_at, redeemed_email, redeemed_lyric_primary_external_id, notes. Values are quoted; embedded quotes are escaped by doubling.
{
"data": [
{
"id": "7e1cf7c0-bf56-4e90-b5be-9ec6e7d5a001",
"code": "A1B2C3D4E5F6",
"partnerId": "RFMLPVN1",
"productSlug": "primary",
"months": 1,
"status": "active",
"expiresAt": "2027-08-15T23:59:59Z",
"redeemedAt": null,
"redeemedEmail": null,
"redeemedLyricPrimaryExternalId": null,
"bulkOrderId": "8b27a12d-6a3f-4f33-9f6f-07ad04aa4e02",
"purchaserEmail": "partner@example.com",
"purchaserSource": "member_purchase",
"unitPriceCents": 9900,
"notes": null,
"createdAt": "2026-05-17T18:00:00Z"
}
],
"total": 1,
"limit": 100,
"offset": 0
}Errors
| Status | Code | Description |
|---|---|---|
| 400 | invalid_request | `product_slug`, `limit`, or `offset` is invalid. |
| 401 | missing_bearer | Authorization header missing. |
| 404 | not_found | Care Services API is disabled (CARE_SERVICES_API_ENABLED is off). |
| 429 | rate_limited | Too many requests for this token. |
GET /api/v1/care-services-coupons
# JSON (default) GET /api/v1/care-services-coupons?status=active HTTP/1.1 Host: app.leguprx.com Authorization: Bearer $LEGUPRX_API_TOKEN Accept: application/json # CSV download GET /api/v1/care-services-coupons?format=csv&status=active,redeemed HTTP/1.1 Host: app.leguprx.com Authorization: Bearer $LEGUPRX_API_TOKEN Accept: text/csv
List orders
Returns orders attributed to your partner account, including commission and shipping status. Sensitive fields are filtered for non-admin tokens.
**Orders from elective-medication test and simulated runs are included and flagged**, rather than withheld: an operator who ran one expects to see it. Every row carries isTest, and no row with isTest: true is ever paid — such rows are excluded from every payout and commission figure. Exclude them from your own revenue reporting the same way. DNA and Care Services orders are not produced by these test modes. See "Test and simulated data".
Response
Envelope: { data: Order[] }. The order object matches the Orders UI fields: orderId, createdDate, subAccount, product, amount, commission, status, and isTest.
isTest is true for a test or simulated run and false for every real order, including every order recorded before test tagging existed. A simulated order additionally reports status: "Simulated" and an orderId prefixed with TEST-SIM-; it is synthesized from the run's own analytics events, reports zero commission, is never payable, and has no stored order behind it at all. The orderId of a real order is never decorated, because the payment and shipping updates for that order are matched against the stored id.
{
"data": [
{
"orderId": "order_102938",
"createdDate": "2026-05-01T14:32:10.000Z",
"subAccount": "Austin clinic",
"product": "Injectable Semaglutide",
"amount": 349,
"commission": 75,
"status": "shipped",
"isTest": false
}
]
}Errors
| Status | Code | Description |
|---|---|---|
| 401 | missing_bearer | Authorization header missing. |
| 429 | rate_limited | Too many requests for this token. |
GET /api/v1/orders
GET /api/v1/orders HTTP/1.1 Host: app.leguprx.com Authorization: Bearer $LEGUPRX_API_TOKEN Accept: application/json
Test and simulated data
Test and simulation apply **only to elective medication intakes** — the self-hosted medical intake forms returned by GET /medical-intake-links with origin: "headless_intake" on the lead. DNA testing, Care Services, managed websites, and other lead sources do not have a product test mode or a ?test=true simulation path, and they never produce isTest: true rows through those mechanisms.
How to recognise elective-med activity that came from a test rather than from a patient, and how to keep it out of your reporting.
Test activity is **tagged, not hidden**. It appears in leads, orders, and analytics with a flag on the row, and it is excluded from every money figure we calculate. Suppressing it entirely would mean an operator who ran a test could not confirm it worked; leaving it unmarked would mean a fake patient reaching your CRM and a rehearsed sale reaching your revenue report. So the row is always there and always says what it is.
There are two kinds, and they are genuinely different things:
- **Test mode on the product.** An operator turns test mode on for a single elective medication product. The intake runs for real end to end and the treatment is created, but flagged so that it is never dispatched, prescribed, or fulfilled. The patient-facing form says so while it is on. Rows from these runs report
testSource: "provider_test_mode". - **Simulation.** Anyone opening an elective medication intake link with
?test=truewalks the whole questionnaire without any of it being submitted anywhere. It is used to check a questionnaire change or rehearse a demo. No account is created, no card is collected, and the payment step is replaced by a "Simulate payment" button that issues no payment credentials of any kind. Rows from these runs reporttestSource: "simulation".
The distinction matters when you are reconciling: a provider_test_mode run has a real, non-fulfilled record behind it, while a simulation run has nothing behind it anywhere. Neither is a sale.
**What this means per endpoint:**
GET /leadsexcludes test rows by default. Pass theincludeTest=truequery parameter (documented on that endpoint) to see them. Every row carriesisTestandtestSourceeither way.GET /ordersincludes them, flagged withisTest. A simulated order also readsstatus: "Simulated"and itsorderIdbeginsTEST-SIM-.- Payouts and commission are never affected. No row with
isTest: trueis included in any payout or commission total, so a test run cannot inflate what you are owed or what you are shown as earning.
**If you sync either endpoint on a schedule, filter on isTest rather than on testSource, a name pattern, or an id prefix.** isTest is the field that answers "should this be counted"; testSource only explains why not, and an id prefix exists on simulated orders alone.
Response
Both fields are on the lead row itself, so no extra request is needed to tell test activity apart:
isTest(boolean, always present):truefor a test or simulated elective-medication run.falsefor real activity, andfalseon every row recorded before test tagging existed, which is correct — those paths only ever produced real rows.testSource(string or null):"provider_test_mode"or"simulation", andnullwheneverisTestisfalse.
On orders, isTest is present and carries the same meaning; the reason is not exposed as a separate field there.
{
"data": [
{
"id": "6f1a2c0e-4f0d-4c0a-9b41-7c9c2c0c0002",
"partnerId": "RFMLPVN1",
"subId": "12",
"firstName": "Jane",
"lastName": "Doe",
"email": "jane.doe@example.com",
"phone": "+15555550123",
"origin": "headless_intake",
"location": "weight-loss",
"questionId": null,
"createdAt": "2026-05-09T17:42:11.214Z",
"updatedAt": "2026-05-09T17:42:11.214Z",
"isTest": true,
"testSource": "simulation"
}
],
"page": {
"nextCursor": null,
"limit": 50
}
}Test and simulated data
# Real leads only (the default) GET /api/v1/leads HTTP/1.1 Host: app.leguprx.com Authorization: Bearer $LEGUPRX_API_TOKEN Accept: application/json # Include test and simulated runs GET /api/v1/leads?includeTest=true HTTP/1.1 Host: app.leguprx.com Authorization: Bearer $LEGUPRX_API_TOKEN Accept: application/json
List payouts
Returns payout records for the authenticated partner.
Response
Envelope: { data: Payout[] }. Admin-only receipt and note fields are not included.
{
"data": [
{
"recordId": "5e9036aa-7772-40b6-9efa-38b5d782fb73",
"email": "partner@example.com",
"createdAt": "2026-05-01",
"payoutDate": "2026-05-01",
"payoutAmount": 1234.56,
"status": "paid"
}
]
}Errors
| Status | Code | Description |
|---|---|---|
| 401 | missing_bearer | Authorization header missing. |
GET /api/v1/payouts
GET /api/v1/payouts HTTP/1.1 Host: app.leguprx.com Authorization: Bearer $LEGUPRX_API_TOKEN Accept: application/json
Sub-accounts
List, create, and delete sub-accounts. Sub-accounts let you attribute orders, leads, and links to a specific staffer or affiliate using the subId field.
Response
GET envelope: { data: PortalSubAccount[] }. POST returns 201 with { data: PortalSubAccount }. DELETE returns { data: { ok: true } }.
{
"data": [
{
"id": "52c4a0eb-d109-4b44-90df-f0f0864c0195",
"email": "partner@example.com",
"subId": "12",
"nickname": "Austin clinic",
"createdAt": "2026-04-01T10:00:00.000Z",
"updatedAt": "2026-04-01T10:00:00.000Z"
}
]
}Errors
| Status | Code | Description |
|---|---|---|
| 400 | invalid_request | Missing or invalid `subId`/`nickname`/`recordId`. |
| 401 | missing_bearer | Authorization header missing. |
| 404 | not_found | Sub account not found (DELETE). |
| 409 | invalid_request | A sub-account with that `subId` already exists. |
GET /api/v1/sub-accounts
GET /api/v1/sub-accounts HTTP/1.1
Host: app.leguprx.com
Authorization: Bearer $LEGUPRX_API_TOKEN
POST /api/v1/sub-accounts HTTP/1.1
Host: app.leguprx.com
Authorization: Bearer $LEGUPRX_API_TOKEN
Content-Type: application/json
{"subId":"12","nickname":"Austin clinic"}
DELETE /api/v1/sub-accounts HTTP/1.1
Host: app.leguprx.com
Authorization: Bearer $LEGUPRX_API_TOKEN
Content-Type: application/json
{"recordId":"52c4a0eb-d109-4b44-90df-f0f0864c0195"}Webhooks
Webhooks let you react in real time to leads, orders, and payouts. Configure endpoints in Settings - API Settings - Webhooks. Each account can have up to 10 endpoints, and each endpoint receives the event types you select.
- HTTPS only. Plain
http://URLs are rejected at save time. - Deliveries retry up to 6 times with exponential backoff (a few seconds, then minutes, then hours) until your endpoint returns a 2xx status.
- Each delivery is signed with HMAC-SHA256. Verifying the signature is optional, but strongly recommended - see Signature verification.
- Webhook secrets are optional. If you do not set one, deliveries still work, but you should treat signature verification as disabled for that endpoint.
lead.createddeliveries can optionally be delayed by 1 minute, 5 minutes, or 1 hour to give the lead time to enter their phone number on the next intake step before you receive the webhook.- You can attach a single optional custom header (for example a shared bearer token) per endpoint, which we send on every delivery.
Signature verification
Signature verification is optional, but strongly recommended. Every delivery includes an X-LegUpRx-Signature header in the form:
X-LegUpRx-Signature: t=<unix_timestamp>,v1=<sha256_hex>
The signed string is t + '.' + body, where body is the exact raw HTTP request body (do not re-serialize the JSON). Compute HMAC-SHA256 with your endpoint secret as the key and compare in constant time. Reject deliveries whose t is more than 5 minutes old to prevent replays.
We also send X-LegUpRx-Event (the event name) and X-LegUpRx-Delivery (a unique delivery id, useful for idempotent processing).
Verify webhook signature
POST /your-webhook-endpoint HTTP/1.1
Host: yourdomain.com
Content-Type: application/json
X-LegUpRx-Signature: t=1715300000,v1=4f0b8f3bb12c2f70d4e3fb28c85dd438df9ad1186a2c9e5d13afd7b0ac54e8c9
X-LegUpRx-Event: lead.created
X-LegUpRx-Delivery: 5c570818-e918-489e-bebb-fbd9a5f6a47d
{"id":"5c570818-e918-489e-bebb-fbd9a5f6a47d","event":"lead.created","created_at":"2026-05-09T17:42:11.214Z","data":{"lead":{"id":"6f1a2c0e-4f0d-4c0a-9b41-7c9c2c0c0001"}}}lead.created
Fires when a new lead is captured for your partner account through a webhook-enabled lead source, including managed websites, public links, and sub-account collection forms. Self-hosted medical-intake leads are available through `GET /leads` with `origin: "headless_intake"`, but their progressive internal upsert does not currently dispatch this webhook.
Example payload
{
"id": "0d8a0fe2-b19c-4f61-bf65-7a16c0371176",
"event": "lead.created",
"created_at": "2026-05-09T17:42:11.214Z",
"data": {
"lead": {
"id": "6f1a2c0e-4f0d-4c0a-9b41-7c9c2c0c0001",
"partnerId": "RFMLPVN1",
"subId": "12",
"firstName": "Jane",
"lastName": "Doe",
"email": "jane.doe@example.com",
"phone": "+15555550123",
"origin": "headless_intake",
"location": "weight-loss",
"questionId": null,
"createdAt": "2026-05-09T17:42:11.214Z",
"updatedAt": "2026-05-09T17:42:11.214Z",
"isTest": false,
"testSource": null
}
}
}lead.enriched
Fires when a previously captured lead on a webhook-enabled source has its phone number filled in or updated after the initial capture. Self-hosted medical-intake rows can be progressively enriched and observed through `GET /leads`, but that internal enrichment does not currently dispatch this webhook.
Example payload
{
"id": "ff34b08c-58ef-4edc-81c9-b12f94e57a11",
"event": "lead.enriched",
"created_at": "2026-05-09T18:11:02.005Z",
"data": {
"lead": {
"id": "6f1a2c0e-4f0d-4c0a-9b41-7c9c2c0c0001",
"partnerId": "RFMLPVN1",
"subId": "12",
"firstName": "Jane",
"lastName": "Doe",
"email": "jane.doe@example.com",
"phone": "+15555550123",
"origin": "headless_intake",
"location": "weight-loss",
"questionId": null,
"createdAt": "2026-05-09T17:42:11.214Z",
"updatedAt": "2026-05-09T17:42:11.214Z",
"isTest": false,
"testSource": null
},
"previousPhone": "",
"currentPhone": "+15555550123"
}
}order.created
Fires when a new order is attributed to your partner account, including its commission breakdown and shipping status.
Example payload
{
"id": "736b6ed2-1e5e-4c13-9faa-31a3d42f0909",
"event": "order.created",
"created_at": "2026-05-09T19:01:00.000Z",
"data": {
"source": "admin",
"order": {
"orderId": "order_102938",
"createdDate": "2026-05-01T14:32:10.000Z",
"subAccount": "Austin clinic",
"product": "Injectable Semaglutide",
"amount": 349,
"commission": 75,
"status": "shipped",
"isTest": false
}
}
}payout.created
Fires when a payout record is created for your account.
Example payload
{
"id": "b52f2e65-34c8-4fae-874e-e9f6de2e592d",
"event": "payout.created",
"created_at": "2026-05-09T20:00:00.000Z",
"data": {
"payout": {
"recordId": "5e9036aa-7772-40b6-9efa-38b5d782fb73",
"email": "partner@example.com",
"createdAt": "2026-05-01",
"payoutDate": "2026-05-01",
"payoutAmount": 1234.56,
"status": "paid"
}
}
}Weight loss
Active weight-loss products available for distribution.
These endpoints are unauthenticated. To attribute traffic to your account, distribute intake links that already include your partner_id, for example:
https://www.telehealthintakeforms.com/api/weight-loss?partner_id=RFMLPVN1
Typical response headers include Content-Type: application/json; Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate with Cdn-Cache-Control: no-store; Access-Control-Allow-Methods: GET, OPTIONS; Access-Control-Allow-Headers: Content-Type.
Example response
{
"products": [
{
"id": "06c641ae-bebd-4e53-b267-f0e6b5b4e272",
"name": "Injectable Lipo-B",
"image": "https://www.telehealthintakeforms.com/api/files/a77ee64e-4a59-4d4d-81be-f042bae0b9b3",
"imageKind": "file",
"imageFileId": "a77ee64e-4a59-4d4d-81be-f042bae0b9b3",
"imageSourceUrl": "https://www.telehealthintakeforms.com/api/files/a77ee64e-4a59-4d4d-81be-f042bae0b9b3",
"intakeUrl": "https://medical.leguprecovery.com/start-online-visit/lipob-intake-vialsrx?partner_id=RFMLPVN1",
"price1Month": "$574.00",
"price3Month": "$449.00",
"price6Month": "$849.00",
"details": "Available Nationwide"
}
],
"count": 11
}https://www.telehealthintakeforms.com/api/weight-loss?partner_id=RFMLPVN1. Counts and product lists can change as catalog availability changes.GET /weight-loss
GET /api/weight-loss?partner_id=RFMLPVN1 HTTP/1.1 Host: www.telehealthintakeforms.com Accept: application/json
Men's health
Active men's health products.
These endpoints are unauthenticated. To attribute traffic to your account, distribute intake links that already include your partner_id, for example:
https://www.telehealthintakeforms.com/api/mens-health?partner_id=RFMLPVN1
Typical response headers include Content-Type: application/json; Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate with Cdn-Cache-Control: no-store; Access-Control-Allow-Methods: GET, OPTIONS; Access-Control-Allow-Headers: Content-Type.
Example response
{
"products": [
{
"id": "c6016e64-2ef3-4dad-b999-be0efe6289fb",
"name": "Cialis",
"image": "https://www.telehealthintakeforms.com/api/files/c23ad164-fc36-4a39-b26d-065ab2df21fb",
"imageKind": "file",
"imageFileId": "c23ad164-fc36-4a39-b26d-065ab2df21fb",
"imageSourceUrl": "https://www.telehealthintakeforms.com/api/files/c23ad164-fc36-4a39-b26d-065ab2df21fb",
"intakeUrl": "https://medical.leguprecovery.com/start-online-visit/ed?partner_id=RFMLPVN1",
"price1Month": "$549.00",
"price3Month": "$0.00",
"price6Month": "$0.00",
"details": "Not available in South Carolina"
}
],
"count": 4
}https://www.telehealthintakeforms.com/api/mens-health?partner_id=RFMLPVN1. Counts and product lists can change as catalog availability changes.GET /mens-health
GET /api/mens-health?partner_id=RFMLPVN1 HTTP/1.1 Host: www.telehealthintakeforms.com Accept: application/json
Women's health
Active women's health products.
These endpoints are unauthenticated. To attribute traffic to your account, distribute intake links that already include your partner_id, for example:
https://www.telehealthintakeforms.com/api/womens-health?partner_id=RFMLPVN1
Typical response headers include Content-Type: application/json; Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate with Cdn-Cache-Control: no-store; Access-Control-Allow-Methods: GET, OPTIONS; Access-Control-Allow-Headers: Content-Type.
Example response
{
"products": [
{
"id": "694759f6-2b17-4128-ba8c-2dfa274fb87e",
"name": "Enclomiphene",
"image": "https://www.telehealthintakeforms.com/api/files/7412c668-3b12-4595-b108-2a96a809f31f",
"imageKind": "file",
"imageFileId": "7412c668-3b12-4595-b108-2a96a809f31f",
"imageSourceUrl": "https://www.telehealthintakeforms.com/api/files/7412c668-3b12-4595-b108-2a96a809f31f",
"intakeUrl": "https://medical.leguprecovery.com/start-online-visit/enclomiphene-intake?partner_id=RFMLPVN1",
"price1Month": "$224.00",
"price3Month": "$0.00",
"price6Month": "$0.00",
"details": "COMING SOON 5/31/2026! Available Nationwide!"
}
],
"count": 4
}https://www.telehealthintakeforms.com/api/womens-health?partner_id=RFMLPVN1. Counts and product lists can change as catalog availability changes.GET /womens-health
GET /api/womens-health?partner_id=RFMLPVN1 HTTP/1.1 Host: www.telehealthintakeforms.com Accept: application/json
Hair loss
Active hair-loss products.
These endpoints are unauthenticated. To attribute traffic to your account, distribute intake links that already include your partner_id, for example:
https://www.telehealthintakeforms.com/api/hair-loss?partner_id=RFMLPVN1
Typical response headers include Content-Type: application/json; Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate with Cdn-Cache-Control: no-store; Access-Control-Allow-Methods: GET, OPTIONS; Access-Control-Allow-Headers: Content-Type.
Example response
{
"products": [
{
"id": "b2abf87a-b350-4864-9df5-08cc44c4bf28",
"name": "Finasteride Tablets",
"image": "https://www.telehealthintakeforms.com/api/files/073ba9d7-a446-4471-be65-41a3ca2a451e",
"imageKind": "file",
"imageFileId": "073ba9d7-a446-4471-be65-41a3ca2a451e",
"imageSourceUrl": "https://www.telehealthintakeforms.com/api/files/073ba9d7-a446-4471-be65-41a3ca2a451e",
"intakeUrl": "https://medical.leguprecovery.com/start-online-visit/hairloss?partner_id=RFMLPVN1",
"price1Month": "$124.00",
"price3Month": "$0.00",
"price6Month": "$0.00",
"details": "Available Nationwide"
}
],
"count": 3
}https://www.telehealthintakeforms.com/api/hair-loss?partner_id=RFMLPVN1. Counts and product lists can change as catalog availability changes.GET /hair-loss
GET /api/hair-loss?partner_id=RFMLPVN1 HTTP/1.1 Host: www.telehealthintakeforms.com Accept: application/json
Wellness
Active wellness and longevity products.
These endpoints are unauthenticated. To attribute traffic to your account, distribute intake links that already include your partner_id, for example:
https://www.telehealthintakeforms.com/api/wellness?partner_id=RFMLPVN1
Typical response headers include Content-Type: application/json; Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate with Cdn-Cache-Control: no-store; Access-Control-Allow-Methods: GET, OPTIONS; Access-Control-Allow-Headers: Content-Type.
Example response
{
"products": [
{
"id": "a9bef966-e60f-4875-893e-b18c9e6a2ae4",
"name": "Injectable B-12",
"image": "https://www.telehealthintakeforms.com/api/files/f420cbc3-ff2e-4644-92b8-a2f04f845c60",
"imageKind": "file",
"imageFileId": "f420cbc3-ff2e-4644-92b8-a2f04f845c60",
"imageSourceUrl": "https://www.telehealthintakeforms.com/api/files/f420cbc3-ff2e-4644-92b8-a2f04f845c60",
"intakeUrl": "https://medical.leguprecovery.com/start-online-visit/b12-intake?partner_id=RFMLPVN1",
"price1Month": "$249.00",
"price3Month": "$0.00",
"price6Month": "$0.00",
"details": "Not available in Louisiana, Mississippi, New Mexico"
}
],
"count": 6
}https://www.telehealthintakeforms.com/api/wellness?partner_id=RFMLPVN1. Counts and product lists can change as catalog availability changes.GET /wellness
GET /api/wellness?partner_id=RFMLPVN1 HTTP/1.1 Host: www.telehealthintakeforms.com Accept: application/json
All products
Combined active medication link product catalog across all medication categories.
These endpoints are unauthenticated. To attribute traffic to your account, distribute intake links that already include your partner_id, for example:
https://www.telehealthintakeforms.com/api/all-products?partner_id=RFMLPVN1
Typical response headers include Content-Type: application/json; Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate with Cdn-Cache-Control: no-store; Access-Control-Allow-Methods: GET, OPTIONS; Access-Control-Allow-Headers: Content-Type.
Example response
{
"products": [
{
"id": "c6016e64-2ef3-4dad-b999-be0efe6289fb",
"name": "Cialis",
"image": "https://www.telehealthintakeforms.com/api/files/c23ad164-fc36-4a39-b26d-065ab2df21fb",
"imageKind": "file",
"imageFileId": "c23ad164-fc36-4a39-b26d-065ab2df21fb",
"imageSourceUrl": "https://www.telehealthintakeforms.com/api/files/c23ad164-fc36-4a39-b26d-065ab2df21fb",
"intakeUrl": "https://medical.leguprecovery.com/start-online-visit/ed?partner_id=RFMLPVN1",
"price1Month": "$549.00",
"price3Month": "$0.00",
"price6Month": "$0.00",
"details": "Not available in South Carolina"
}
],
"count": 28
}https://www.telehealthintakeforms.com/api/all-products?partner_id=RFMLPVN1. Counts and product lists can change as catalog availability changes.GET /all-products
GET /api/all-products?partner_id=RFMLPVN1 HTTP/1.1 Host: www.telehealthintakeforms.com Accept: application/json
Embed Care Services
Partners can embed their Care Services catalog on their own site with the iframe URL shown in their Care Services section. Treat that URL like a shareable tracking link, not a secret. The embedded view only renders services the partner can currently access and respects category feature flags.
Replace the placeholder src below with the Care Services embed URL copied from the partner portal. Do not build this from the API docs domain. The payment-link domain accepts a few query parameters:
partner_id(required): your partner attribution code; auto-populated by the partner portal's "Copy URL" buttons.service: one ofprimary-care,urgent-care,mental-health,dermatology,veterinary-care. Filters the embedded catalog.m: term in months:1,3,6, or12. Pre-selects a duration on the intake.sub: optional sub-account number (1-250) to attribute the order to a downstream account.redeem=true: switches the page from the Stripe checkout flow to the Bulk Code Redemption screen. Patients enter a 12-character coupon code (no card collected) and we provision the care services membership directly. Use the "Copy Bulk Code Redemption URL" button on each Care Services row to generate this URL.
<iframe
id="leguprx-care-services"
src="PASTE_YOUR_CARE_SERVICES_EMBED_URL_HERE"
style="width:100%;border:0;min-height:700px;border-radius:16px;"
loading="lazy"
></iframe>
<script>
window.addEventListener('message', (event) => {
if (event.data?.type !== 'leguprx-api-docs-height') return;
document.getElementById('leguprx-care-services').style.height = event.data.height + 'px';
});
</script>{
"placeholderUrl": "PASTE_YOUR_CARE_SERVICES_EMBED_URL_HERE",
"whereToFindIt": "Partner Links > Care Services in the partner portal",
"notes": "Use the URL shown in the partner Care Services section. It already includes the correct partner attribution."
}Errors and rate limits
All errors use a consistent envelope:
{
"error": {
"code": "invalid_request",
"message": "limit must be a positive integer."
}
}Rate limits are enforced per API token, per endpoint group. When a request is rate-limited, the response includes X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, and Retry-After headers when applicable.
| Status | Code | Description |
|---|---|---|
| 401 | missing_bearer | Request did not include `Authorization: Bearer <token>`. |
| 401 | invalid_token | Token is malformed, revoked, expired, or not user-scoped. |
| 401 | unknown_user | The token verifies, but no portal user matches its subject. |
| 403 | forbidden | The account is inactive or the endpoint is disabled for this account (for example DNA links). |
| 404 | not_found | The referenced record does not exist or does not belong to this account. |
| 400 | invalid_request | A query/body parameter failed validation. |
| 429 | rate_limited | Per-token rate limit exceeded for this endpoint. |
| 500 | internal_error | Unexpected server error. Safe to retry with backoff. |
| 503 | api_keys_unavailable | Reserved for environments where API token verification cannot run. |