{"openapi":"3.1.0","info":{"title":"Tendral Health API","version":"1.0.0","description":"Public API for Tendral Health.\n\n**Base URL:** `https://api.tendralhealth.com/v1/`. Authentication is bearer-token (`Authorization: Bearer tk_live_xxxxxxxxxxxx`); partner endpoints rate-limit at 100 req/min; idempotency is supported via `Idempotency-Key`; outbound webhooks are signed Stripe-style. Resources carry an `object` discriminator and a `livemode` boolean; lists carry `has_more` and `next_cursor`.\n\nDiscrete guides on the developer portal:\n\n- [Quickstart](https://developer.tendralhealth.com/docs/quickstart) — first call in 5 minutes\n- [Authentication](https://developer.tendralhealth.com/docs/authentication) — bearer tokens, scopes, rotation\n- [Webhooks](https://developer.tendralhealth.com/docs/webhooks) — signature format, event catalog, HMAC verification\n- [Errors](https://developer.tendralhealth.com/docs/errors) — every error code with retry guidance\n- [Rate limits](https://developer.tendralhealth.com/docs/rate-limits) · [Idempotency](https://developer.tendralhealth.com/docs/idempotency) · [Pagination](https://developer.tendralhealth.com/docs/pagination)\n- [Changelog](https://developer.tendralhealth.com/docs/changelog)\n\nStitched Health is the first partner consumer — see [the protocol contract](https://github.com/epaynter/tendral-stitched-integration). Legacy non-Partner endpoints (`Surveys`, `Uploads`) use the simpler `{ code, message, errors }` shape via the `ErrorResponse` schema; Partners use the Stripe-style `PartnerErrorResponse` envelope (`error.type`, `error.code`, `error.doc_url`, `error.request_id`).\n"},"servers":[{"url":"https://api.tendralhealth.com","description":"Production"},{"url":"https://api.staging.tendralhealth.com","description":"Staging"}],"security":[{"BearerAuth":[]}],"paths":{"/v1/health":{"get":{"summary":"Health check","description":"Returns 200 with status `ok` when the API is reachable. Unauthenticated. Useful as a smoke test before partner onboarding.","operationId":"getHealth","tags":["System"],"security":[],"responses":{"200":{"description":"Service is healthy","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ok"},"version":{"type":"string","example":"1.0.0"},"timestamp":{"type":"string","format":"date-time","example":"2026-02-13T00:00:00.000Z"}},"required":["status","version","timestamp"]}}}}}}},"/v1/surveys":{"post":{"summary":"[Coming soon] Create a survey","description":"Import a complete survey from JSON. **Not yet shipped** — schema is published for partner planning. Calling this endpoint returns 501.","operationId":"createSurvey","tags":["Surveys"],"deprecated":true,"x-internal":true,"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SurveyInput"}}}},"responses":{"201":{"description":"Survey created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SurveyCreated"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/surveys/validate":{"post":{"summary":"[Coming soon] Validate a survey (dry run)","description":"Validate survey JSON without creating it. **Not yet shipped** — schema is published for partner planning.","operationId":"validateSurvey","tags":["Surveys"],"deprecated":true,"x-internal":true,"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SurveyInput"}}}},"responses":{"200":{"description":"Validation passed","content":{"application/json":{"schema":{"type":"object","properties":{"valid":{"type":"boolean","example":true},"questionCount":{"type":"integer","example":12},"caseCount":{"type":"integer","example":0}},"required":["valid","questionCount"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/uploads/presign":{"get":{"summary":"[Coming soon] Get a pre-signed upload URL","description":"Get a pre-signed URL for uploading a PDF (e.g. case publication). **Not yet shipped** — schema is published for partner planning.","operationId":"getPresignedUrl","tags":["Uploads"],"deprecated":true,"x-internal":true,"parameters":[{"name":"filename","in":"query","required":true,"schema":{"type":"string"},"description":"Original filename (used for content-type detection)"},{"name":"contentType","in":"query","required":false,"schema":{"type":"string","default":"application/pdf"},"description":"MIME type of the file"}],"responses":{"200":{"description":"Pre-signed URL generated","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"Pre-signed upload URL (PUT)"},"publicUrl":{"type":"string","format":"uri","description":"Public URL to reference after upload completes"},"expiresIn":{"type":"integer","description":"Seconds until the pre-signed URL expires","example":3600}},"required":["url","publicUrl","expiresIn"]}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/partners/stitched/tokens/{token}":{"get":{"summary":"Resolve a Tendral short-link token","description":"Single-token lookup. Returns the recipient record bound to a Tendral-generated 6-character base62 token. Used for ad-hoc / debugging only — Stitched's redirect handler MUST NOT call this on the click path (see integration spec §3.2).\n\nEmits a strong `ETag` derived from the row's `updated_at`. Re-requests with `If-None-Match: <etag>` return `304` with no body and rate-limit headers retained.","operationId":"getPartnerStitchedToken","tags":["Partners"],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","pattern":"^[0-9A-Za-z]{6}$"},"description":"6-character base62 short-link token."},{"name":"expand[]","in":"query","schema":{"type":"array","items":{"type":"string","enum":["campaign","program"]}},"style":"form","explode":true,"description":"Inline related objects in the response. Allowed: `campaign`, `program`."},{"name":"If-None-Match","in":"header","schema":{"type":"string"},"description":"ETag from a prior response. Matching → 304 with no body."}],"responses":{"200":{"description":"Recipient record","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"ETag":{"schema":{"type":"string"},"description":"Strong ETag for `If-None-Match` revalidation."},"Cache-Control":{"schema":{"type":"string"},"description":"`private, max-age=10, must-revalidate` — only the partner caches; revalidate within 10s."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipientRecord"}}}},"304":{"description":"Not modified — ETag matched If-None-Match. No body.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"403":{"description":"Token lacks tokens:read scope","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"404":{"description":"Token not found","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"429":{"description":"Rate limit exceeded","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}}}}},"/v1/partners/stitched/tokens":{"get":{"summary":"List recipient records since a timestamp","description":"Paginated list of Tendral recipient records updated since `since`. Composite cursor pagination on `(updated_at, id)` for stable ordering across rows that share a timestamp.","operationId":"listPartnerStitchedTokens","tags":["Partners"],"parameters":[{"name":"since","in":"query","required":true,"schema":{"type":"string","format":"date-time"}},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque cursor returned by the previous page."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"name":"expand[]","in":"query","schema":{"type":"array","items":{"type":"string","enum":["campaign","program"]}},"style":"form","explode":true}],"responses":{"200":{"description":"Page of recipient records","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListResponseRecipient"}}}},"400":{"description":"Validation error","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"403":{"description":"Token lacks tokens:read scope","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"429":{"description":"Rate limit exceeded","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}}}}},"/v1/partners/stitched/events":{"get":{"summary":"List Tendral-emitted outreach events since a timestamp","description":"Paginated list of `outreach.email_sent` and `outreach.email_bounced` events for traffic campaigns under Stitched-bound programs. Each row is wrapped in the spec §4 envelope so consumers can dedupe by `event_id` exactly as with webhooks.\n\nFilter by event type with `?type=outreach.email_sent` (single) or repeated `?types[]=outreach.email_sent&types[]=outreach.email_bounced` (multi).","operationId":"listPartnerStitchedEvents","tags":["Partners"],"parameters":[{"name":"since","in":"query","required":true,"schema":{"type":"string","format":"date-time"}},{"name":"cursor","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"name":"type","in":"query","schema":{"type":"string","enum":["outreach.email_sent","outreach.email_bounced"]},"description":"Single event type filter."},{"name":"types[]","in":"query","schema":{"type":"array","items":{"type":"string","enum":["outreach.email_sent","outreach.email_bounced"]}},"style":"form","explode":true,"description":"Multi event type filter."},{"name":"expand[]","in":"query","schema":{"type":"array","items":{"type":"string","enum":["campaign","program","recipient"]}},"style":"form","explode":true}],"responses":{"200":{"description":"Page of envelopes","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListResponseEvent"}}}},"400":{"description":"Validation error","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"403":{"description":"Token lacks events:read scope","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"429":{"description":"Rate limit exceeded","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}}}}},"/v1/partners/stitched/campaigns/{id}/recipients":{"get":{"summary":"List recipients for a Tendral campaign","description":"Paginated list of recipient records for the given campaign. `id` must be the prefixed `cmp_<hex>` form.","operationId":"listPartnerStitchedCampaignRecipients","tags":["Partners"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Tendral campaign id (`cmp_<hex>`)."},{"name":"cursor","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"name":"expand[]","in":"query","schema":{"type":"array","items":{"type":"string","enum":["campaign","program"]}},"style":"form","explode":true}],"responses":{"200":{"description":"Page of recipient records","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListResponseRecipient"}}}},"400":{"description":"Invalid campaign id","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"403":{"description":"Token lacks recipients:read scope","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"429":{"description":"Rate limit exceeded","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}}}}},"/v1/partners/stitched/digest":{"get":{"summary":"Aggregated digest of Stitched-bound program activity for a UTC date","description":"Returns one record per Stitched-bound program under the caller's organization for the requested UTC date: qualification funnel (today + cumulative), outreach throughput (today), and quota status. Backs the daily Slack digest delivered to Stitched Health.","operationId":"getPartnerStitchedDigest","tags":["Partners"],"parameters":[{"name":"date","in":"query","required":true,"schema":{"type":"string","format":"date"},"description":"UTC calendar date in YYYY-MM-DD form."}],"responses":{"200":{"description":"Aggregated digest","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StitchedDigest"}}}},"400":{"description":"Validation error","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"403":{"description":"Token lacks digest:read scope","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"429":{"description":"Rate limit exceeded","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}}}}}},"webhooks":{"outreach.recipients_published":{"post":{"summary":"Pre-arrival batch of recipients (Tendral → consumer)","description":"Fired before a campaign goes live. Carries the full set of recipients (or a chunk of them) that Tendral plans to email, so the consumer can seat token → NPI mappings ahead of time. **No bearer auth — verify the `X-Signature` header.** See [/docs/webhooks](https://developer.tendralhealth.com/docs/webhooks) for HMAC verification samples.","operationId":"webhookOutreachRecipientsPublished","tags":["Partners"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerEventEnvelope"},"example":{"object":"event","livemode":true,"event_id":"evt_018f5a2e7c127c8da123cafed00dfeed","event_type":"outreach.recipients_published","event_version":1,"occurred_at":"2026-04-26T18:00:00Z","emitted_at":"2026-04-26T18:00:01Z","source":"tendral","data":{"tendral_campaign_id":"cmp_018f5a2e7c127c8da123cafed00dfeef","tendral_program_id":"pgm_018f5a2e7c127c8da123cafed00dffff","stitched_program_slug":"alz-tx-decisions","scheduled_send_window_start":"2026-04-27T13:00:00Z","scheduled_send_window_end":"2026-04-28T13:00:00Z","recipients":[{"token":"Ab3kQ9","npi":"1234567890","email":"jdoe@example.com","tendral_recipient_id":"rcp_018f5a2e7c127c8da123cafed00dfeed"}]}}}}},"responses":{"200":{"description":"Consumer acknowledges receipt. Body ignored."},"4XX":{"description":"Permanent failure — Tendral will not retry."},"5XX":{"description":"Transient failure — Tendral retries on the schedule documented at /docs/webhooks."}}}},"outreach.email_sent":{"post":{"summary":"Per-recipient email-sent event (Tendral → consumer)","description":"Fired when an outreach email is delivered to the recipient mailbox. **No bearer auth — verify `X-Signature`.**","operationId":"webhookOutreachEmailSent","tags":["Partners"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerEventEnvelope"},"example":{"object":"event","livemode":true,"event_id":"evt_018f5a2e7c127c8da123cafed00dfeed","event_type":"outreach.email_sent","event_version":1,"occurred_at":"2026-04-27T13:02:00Z","emitted_at":"2026-04-27T13:02:01Z","source":"tendral","data":{"tendral_recipient_id":"rcp_018f5a2e7c127c8da123cafed00dfeed","tendral_campaign_id":"cmp_018f5a2e7c127c8da123cafed00dfeef","token":"Ab3kQ9"}}}}},"responses":{"200":{"description":"Consumer acknowledges receipt."},"4XX":{"description":"Permanent failure — no retry."},"5XX":{"description":"Transient failure — Tendral retries."}}}},"outreach.email_bounced":{"post":{"summary":"Per-recipient email-bounced event (Tendral → consumer)","description":"Fired when an outreach email hard-bounces. **No bearer auth — verify `X-Signature`.**","operationId":"webhookOutreachEmailBounced","tags":["Partners"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerEventEnvelope"},"example":{"object":"event","livemode":true,"event_id":"evt_018f5a2e7c127c8da123cafed00dfeed","event_type":"outreach.email_bounced","event_version":1,"occurred_at":"2026-04-27T13:02:30Z","emitted_at":"2026-04-27T13:02:31Z","source":"tendral","data":{"tendral_recipient_id":"rcp_018f5a2e7c127c8da123cafed00dfeed","tendral_campaign_id":"cmp_018f5a2e7c127c8da123cafed00dfeef","token":"Ab3kQ9","bounce_reason":"mailbox_full"}}}}},"responses":{"200":{"description":"Consumer acknowledges receipt."},"4XX":{"description":"Permanent failure — no retry."},"5XX":{"description":"Transient failure — Tendral retries."}}}},"clinician.account_created":{"post":{"summary":"Action event: clinician created an account on the partner platform","description":"Fired by Stitched (or future partners) when a clinician creates an account. Tendral consumes this to grow the suppression set and (when `attribution = tendral_token`) update the recipient record. **No bearer auth — verify `X-Signature`.** Documented from Tendral's perspective as the inbound contract Stitched delivers.","operationId":"webhookClinicianAccountCreated","tags":["Partners"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerEventEnvelope"},"example":{"object":"event","livemode":true,"event_id":"evt_018f5a2e7c127c8da123cafed01dfeed","event_type":"clinician.account_created","event_version":1,"occurred_at":"2026-04-27T15:30:00Z","emitted_at":"2026-04-27T15:30:01Z","source":"stitched","data":{"npi":"1234567890","email":"jdoe@example.com","stitched_program_slug":"alz-tx-decisions","attribution":"tendral_token","token":"Ab3kQ9"}}}}},"responses":{"200":{"description":"Tendral acknowledges receipt."},"4XX":{"description":"Permanent failure — Stitched will not retry."},"5XX":{"description":"Transient — Stitched retries."}}}},"clinician.learner_qualified":{"post":{"summary":"Action event: clinician crossed the learner threshold on a program","description":"Fired by Stitched when a clinician hits the learner-quota threshold. Tendral consumes this to increment local quota counters and withdraw matching active leads. **No bearer auth — verify `X-Signature`.**","operationId":"webhookClinicianLearnerQualified","tags":["Partners"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerEventEnvelope"},"example":{"object":"event","livemode":true,"event_id":"evt_018f5a2e7c127c8da123cafed02dfeed","event_type":"clinician.learner_qualified","event_version":1,"occurred_at":"2026-04-28T10:00:00Z","emitted_at":"2026-04-28T10:00:01Z","source":"stitched","data":{"npi":"1234567890","stitched_program_slug":"alz-tx-decisions","token":"Ab3kQ9"}}}}},"responses":{"200":{"description":"Tendral acknowledges receipt."},"4XX":{"description":"Permanent failure — no retry."},"5XX":{"description":"Transient — Stitched retries."}}}},"clinician.completer_qualified":{"post":{"summary":"Action event: clinician completed a program","description":"Fired by Stitched when a clinician completes a program. Same handler shape as `clinician.learner_qualified`. **No bearer auth — verify `X-Signature`.**","operationId":"webhookClinicianCompleterQualified","tags":["Partners"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerEventEnvelope"},"example":{"object":"event","livemode":true,"event_id":"evt_018f5a2e7c127c8da123cafed03dfeed","event_type":"clinician.completer_qualified","event_version":1,"occurred_at":"2026-04-29T12:00:00Z","emitted_at":"2026-04-29T12:00:01Z","source":"stitched","data":{"npi":"1234567890","stitched_program_slug":"alz-tx-decisions","token":"Ab3kQ9"}}}}},"responses":{"200":{"description":"Tendral acknowledges receipt."},"4XX":{"description":"Permanent failure — no retry."},"5XX":{"description":"Transient — Stitched retries."}}}}},"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"API key in the format `tk_live_xxxxxxxxxxxx`"}},"headers":{"X-RateLimit-Limit":{"description":"Requests allowed per window for this API key (default 100/min for partner endpoints).","schema":{"type":"integer","example":100}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer","example":99}},"X-RateLimit-Reset":{"description":"Unix-seconds when the current window resets.","schema":{"type":"integer","example":1715040000}},"X-Request-Id":{"description":"Correlation id (`req_<hex>`); also surfaced in `error.request_id` on failures.","schema":{"type":"string","example":"req_018f5a2e7c127c8da123cafed00dfeed"}},"Retry-After":{"description":"Seconds to wait before retrying. Present only on 429 responses.","schema":{"type":"integer","example":60}},"Idempotent-Replayed":{"description":"`true` when the response is a replayed cached idempotency response. Absent on first execution.","schema":{"type":"string","enum":["true"]}}},"schemas":{"SurveyInput":{"type":"object","required":["title","estimatedTime","incentive"],"properties":{"title":{"type":"string","minLength":1,"maxLength":200,"description":"Survey title"},"description":{"type":"string","description":"Rich text introduction (shown after sign-in)"},"briefDescription":{"type":"string","maxLength":150,"description":"Welcome screen subtitle"},"sponsor":{"type":"string","description":"Welcome screen sponsor"},"background":{"type":"string","description":"Study context shown to respondents"},"directions":{"type":"string","description":"\"What you'll do\" text"},"outcomes":{"type":"string","description":"Expected outcomes text"},"format":{"type":"string","enum":["standard","case-based"],"default":"standard","description":"Survey format. Standard uses flat questions[], case-based nests questions inside cases."},"estimatedTime":{"$ref":"#/components/schemas/EstimatedTime"},"incentive":{"type":"number","minimum":0,"description":"Compensation in dollars"},"questions":{"type":"array","items":{"$ref":"#/components/schemas/Question"},"description":"Questions array (required for standard format)"},"introQuestions":{"type":"array","items":{"$ref":"#/components/schemas/Question"},"description":"Questions before cases (case-based only, optional)"},"cases":{"type":"array","items":{"$ref":"#/components/schemas/Case"},"description":"Cases array (required for case-based format)"},"closingQuestions":{"type":"array","items":{"$ref":"#/components/schemas/Question"},"description":"Questions after cases (case-based only, optional)"},"targeting":{"$ref":"#/components/schemas/Targeting"}},"example":{"title":"Treatment decision-making in early Alzheimer's disease","description":"A 12-minute survey on how neurologists assess disease-modifying therapy candidates.","briefDescription":"12 minutes · Sponsored by Stitched Health","format":"standard","estimatedTime":{"min":10,"max":14},"incentive":75,"questions":[{"type":"single-choice","text":"How often do you order amyloid PET scans in your practice?","options":["Never","Rarely","Sometimes","Often","Always"],"required":true},{"type":"scale","text":"Confidence in selecting disease-modifying therapy for an MCI patient.","points":5,"labels":{"start":"Not confident","end":"Very confident"},"required":true}],"targeting":{"audiences":[{"name":"US neurologists","specialtyCodes":["2084N0400X"],"states":["CA","NY","TX"],"quota":50}]}}},"EstimatedTime":{"type":"object","required":["min","max"],"properties":{"min":{"type":"integer","minimum":1,"description":"Minimum estimated minutes"},"max":{"type":"integer","minimum":1,"description":"Maximum estimated minutes (must be >= min)"}}},"Question":{"type":"object","required":["type","text"],"description":"A survey question. Additional fields depend on the type.","properties":{"type":{"type":"string","enum":["text","single-choice","multiple-choice","scale","yes-no","number","maxdiff","compound"]},"text":{"type":"string","minLength":1,"description":"Question text"},"description":{"type":"string","description":"Help text shown below question"},"required":{"type":"boolean","default":true,"description":"Whether response is required"},"placeholder":{"type":"string"},"voiceEnabled":{"type":"boolean","default":false},"validation":{"type":"object","properties":{"minLength":{"type":"integer"},"maxLength":{"type":"integer"},"min":{"type":"number"},"max":{"type":"number"}}},"options":{"type":"array","items":{"oneOf":[{"type":"string"},{"$ref":"#/components/schemas/OptionObject"}]},"description":"Options for single-choice and multiple-choice (min 2)"},"allowOther":{"type":"boolean","default":false},"maxSelections":{"type":"integer"},"randomizeOptions":{"type":"boolean","default":false},"points":{"type":"integer","minimum":2,"maximum":10,"default":5,"description":"Number of scale points"},"labels":{"$ref":"#/components/schemas/ScaleLabels"},"visualization":{"type":"string","enum":["slider","likert","semantic","descriptive","frequency","comparative","numerical","graphic","multi-dimension"],"default":"likert"},"showLabels":{"type":"boolean","default":true},"showValue":{"type":"boolean","default":true},"flipped":{"type":"boolean","default":false},"yesText":{"type":"string","default":"Yes"},"noText":{"type":"string","default":"No"},"items":{"type":"array","items":{"type":"string"},"minItems":4,"maxItems":20,"description":"Items to compare (MaxDiff)"},"config":{"type":"object","properties":{"itemsPerTask":{"type":"integer"},"totalTasks":{"type":"integer"}}},"contextHeader":{"type":"string"},"subQuestions":{"type":"array","items":{"$ref":"#/components/schemas/SubQuestion"}}}},"OptionObject":{"type":"object","required":["text"],"properties":{"text":{"type":"string","minLength":1},"value":{"type":"string"}}},"ScaleLabels":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"},"points":{"type":"array","items":{"type":"string"}}}},"SubQuestion":{"type":"object","required":["type","label"],"properties":{"type":{"type":"string","enum":["scale","single-choice","multiple-choice","yes-no"]},"label":{"type":"string","minLength":1},"required":{"type":"boolean","default":true},"options":{"type":"array","items":{"oneOf":[{"type":"string"},{"$ref":"#/components/schemas/OptionObject"}]}},"points":{"type":"integer","minimum":2,"maximum":10,"default":5},"labels":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}}}}},"Case":{"type":"object","required":["title","content","questions"],"properties":{"ref":{"type":"string","description":"Unique reference for this case"},"title":{"type":"string","minLength":1},"content":{"type":"string","minLength":1,"description":"HTML case content"},"prompt":{"type":"string","description":"Directions shown above the case"},"publication":{"$ref":"#/components/schemas/Publication"},"keyPoints":{"type":"array","items":{"type":"string"}},"summary":{"type":"string"},"headerLabel":{"type":"string"},"reflectionLabel":{"type":"string"},"introLabel":{"type":"string"},"questions":{"type":"array","items":{"$ref":"#/components/schemas/Question"},"minItems":1}}},"Publication":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["pdf","link","embedded","text"]},"url":{"type":"string","format":"uri"},"metadata":{"type":"object","properties":{"journal":{"type":"string"},"authors":{"type":"string"},"year":{"type":"integer"},"title":{"type":"string"},"doi":{"type":"string"}}}}},"Targeting":{"type":"object","required":["audiences"],"properties":{"audiences":{"type":"array","items":{"$ref":"#/components/schemas/AudienceInput"},"minItems":1}}},"AudienceInput":{"type":"object","properties":{"name":{"type":"string"},"roles":{"type":"array","items":{"type":"string"},"description":"NUCC groupings"},"specialtyCodes":{"type":"array","items":{"type":"string"},"description":"NUCC specialty codes"},"states":{"type":"array","items":{"type":"string"},"description":"US state codes"},"zipCodes":{"type":"array","items":{"type":"string"}},"compensation":{"type":"number","minimum":0,"description":"Override compensation in dollars"},"quota":{"type":"integer","minimum":1,"description":"Target response count"}}},"SurveyCreated":{"type":"object","required":["id","title","status","questionCount","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"status":{"type":"string","example":"draft"},"questionCount":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"}},"example":{"id":"018f5a2e-7c12-7c8d-a123-cafed00dfeed","title":"Treatment decision-making in early Alzheimer's disease","status":"draft","questionCount":12,"createdAt":"2026-05-06T13:02:30Z"}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable error code","example":"VALIDATION_ERROR"},"message":{"type":"string","description":"Human-readable error description","example":"Survey validation failed"},"errors":{"type":"array","items":{"$ref":"#/components/schemas/ImportError"},"description":"Field-level validation errors (if applicable)"}}}}},"ImportError":{"type":"object","required":["code","path","message"],"properties":{"code":{"type":"string","example":"MISSING_OPTIONS"},"path":{"type":"string","example":"questions[2].options"},"message":{"type":"string","example":"single-choice questions require an \"options\" array"}}},"ErrorType":{"type":"string","description":"Coarse machine-routable category (Stripe-style). Branch retry/UX logic on `type` rather than `code`.\n\n| Type | Typical HTTP | Retryable? | What it means |\n| --- | --- | --- | --- |\n| `authentication_error` | 401 | No | The request is not authenticated (missing/expired/revoked bearer). |\n| `invalid_request_error` | 400, 404 | No | The request is malformed or refers to something that doesn't exist. Fix the request. |\n| `permission_error` | 403 | No | Authentication succeeded but the token lacks the required scope. Re-issue with appropriate scopes. |\n| `rate_limit_error` | 429 | Yes — after `Retry-After` | API key exceeded its rate limit. Back off and retry. |\n| `idempotency_error` | 409 | No | The provided `Idempotency-Key` was reused with a different request body. Use a fresh key. |\n| `api_error` | 5xx | Yes — exponential backoff | Unexpected server error. |\n","enum":["authentication_error","invalid_request_error","permission_error","rate_limit_error","idempotency_error","api_error"]},"ErrorCode":{"type":"string","description":"Specific reason code. Stable enum; new codes are additive. Full reference at [/docs/errors](https://developer.tendralhealth.com/docs/errors).\n\n| Code | Type | HTTP | Retryable? | What it means |\n| --- | --- | --- | --- | --- |\n| `UNAUTHORIZED` | `authentication_error` | 401 | No | Bearer token missing, malformed, expired, or revoked. |\n| `INSUFFICIENT_SCOPE` | `permission_error` | 403 | No | Token does not include the required scope (e.g. `tokens:read`). |\n| `INVALID_TOKEN` | `invalid_request_error` | 400 | No | The 6-character URL token is malformed (must match `[0-9A-Za-z]{6}`). |\n| `INVALID_CAMPAIGN_ID` | `invalid_request_error` | 400 | No | Campaign id must be the prefixed `cmp_<hex>` form. |\n| `TOKEN_NOT_FOUND` | `invalid_request_error` | 404 | No | No record exists for the provided token. |\n| `MISSING_PARAMETER` | `invalid_request_error` | 400 | No | A required query/body parameter is missing. See `error.details.parameter`. |\n| `INVALID_PARAMETER` | `invalid_request_error` | 400 | No | A parameter is present but malformed (wrong type, format, or value). |\n| `INVALID_EXPAND` | `invalid_request_error` | 400 | No | A value in `expand[]` is not allowed for this endpoint. |\n| `INVALID_TYPE_FILTER` | `invalid_request_error` | 400 | No | A value in `type`/`types[]` is not in the allowed enum. |\n| `IDEMPOTENCY_KEY_REUSED` | `idempotency_error` | 409 | No | Same `Idempotency-Key` + different request body. Retry with a fresh key. |\n| `RATE_LIMITED` | `rate_limit_error` | 429 | Yes (after `Retry-After`) | API key has exceeded its rate limit. |\n| `INTERNAL_ERROR` | `api_error` | 500 | Yes (exponential backoff) | Unexpected server error. |\n","enum":["UNAUTHORIZED","INSUFFICIENT_SCOPE","INVALID_TOKEN","INVALID_CAMPAIGN_ID","TOKEN_NOT_FOUND","MISSING_PARAMETER","INVALID_PARAMETER","INVALID_EXPAND","INVALID_TYPE_FILTER","IDEMPOTENCY_KEY_REUSED","RATE_LIMITED","INTERNAL_ERROR"]},"PartnerErrorResponse":{"type":"object","required":["error"],"description":"Error envelope for Partners-tagged endpoints. Spec §5.4.","properties":{"error":{"type":"object","required":["type","code","message","request_id","doc_url"],"properties":{"type":{"$ref":"#/components/schemas/ErrorType"},"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string","example":"No record exists for the provided token."},"details":{"type":"object","additionalProperties":true,"nullable":true},"doc_url":{"type":"string","format":"uri","example":"https://developer.tendralhealth.com/docs/errors#TOKEN_NOT_FOUND"},"request_id":{"type":"string","example":"req_018f5a2e7c127c8da123cafed00dfeed"}},"example":{"type":"invalid_request_error","code":"TOKEN_NOT_FOUND","message":"No record exists for the provided token.","details":{"token":"Ab3kQ9"},"doc_url":"https://developer.tendralhealth.com/docs/errors#TOKEN_NOT_FOUND","request_id":"req_018f5a2e7c127c8da123cafed00dfeed"}}}},"RecipientRecord":{"type":"object","required":["object","livemode","token","npi","email","tendral_recipient_id","tendral_campaign_id","tendral_program_id","created_at"],"properties":{"object":{"type":"string","enum":["recipient_record"]},"livemode":{"type":"boolean","description":"True only on the production deployment."},"token":{"type":"string","pattern":"^[0-9A-Za-z]{6}$","example":"Ab3kQ9"},"npi":{"type":"string","pattern":"^\\d{10}$","example":"1234567890"},"email":{"type":"string","format":"email"},"tendral_recipient_id":{"type":"string","pattern":"^rcp_[0-9a-f]+$"},"tendral_campaign_id":{"type":"string","pattern":"^cmp_[0-9a-f]+$"},"tendral_program_id":{"type":"string","pattern":"^pgm_[0-9a-f]+$"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time","nullable":true},"sent_at":{"type":"string","format":"date-time","nullable":true},"bounced_at":{"type":"string","format":"date-time","nullable":true},"last_event_at":{"type":"string","format":"date-time","nullable":true},"campaign":{"$ref":"#/components/schemas/CampaignSummary","nullable":true,"description":"Inlined when expand[]=campaign."},"program":{"$ref":"#/components/schemas/ProgramSummary","nullable":true,"description":"Inlined when expand[]=program."}},"example":{"object":"recipient_record","livemode":false,"token":"Ab3kQ9","npi":"1234567890","email":"jdoe@example.com","tendral_recipient_id":"rcp_018f5a2e7c127c8da123cafed00dfeed","tendral_campaign_id":"cmp_018f5a2e7c127c8da123cafed00dfeef","tendral_program_id":"pgm_018f5a2e7c127c8da123cafed00dffff","created_at":"2026-04-26T18:00:00.000Z","updated_at":"2026-04-27T15:30:00.000Z","sent_at":"2026-04-27T13:02:00.000Z","bounced_at":null,"last_event_at":"2026-04-27T15:30:00.000Z"}},"PartnerEventEnvelope":{"type":"object","required":["object","livemode","event_id","event_type","event_version","occurred_at","emitted_at","source","data"],"description":"Spec §4 envelope wrapping `outreach.*` rows.","properties":{"object":{"type":"string","enum":["event"]},"livemode":{"type":"boolean"},"event_id":{"type":"string","pattern":"^evt_[0-9a-f]+$"},"event_type":{"type":"string","example":"outreach.email_sent"},"event_version":{"type":"integer","minimum":1,"example":1},"occurred_at":{"type":"string","format":"date-time"},"emitted_at":{"type":"string","format":"date-time"},"source":{"type":"string","enum":["tendral","stitched"]},"data":{"type":"object","additionalProperties":true},"metadata":{"type":"object","additionalProperties":true,"nullable":true},"campaign":{"$ref":"#/components/schemas/CampaignSummary","nullable":true},"program":{"$ref":"#/components/schemas/ProgramSummary","nullable":true},"recipient":{"$ref":"#/components/schemas/RecipientRecord","nullable":true}},"example":{"object":"event","livemode":false,"event_id":"evt_018f5a2e7c127c8da123cafed00dfeed","event_type":"outreach.email_sent","event_version":1,"occurred_at":"2026-04-27T13:02:00Z","emitted_at":"2026-04-27T13:02:01Z","source":"tendral","data":{"tendral_recipient_id":"rcp_018f5a2e7c127c8da123cafed00dfeed","tendral_campaign_id":"cmp_018f5a2e7c127c8da123cafed00dfeef","token":"Ab3kQ9"}}},"CampaignSummary":{"type":"object","required":["object","livemode","id","name","program_id","status"],"properties":{"object":{"type":"string","enum":["campaign"]},"livemode":{"type":"boolean"},"id":{"type":"string","pattern":"^cmp_[0-9a-f]+$"},"name":{"type":"string"},"program_id":{"type":"string","pattern":"^pgm_[0-9a-f]+$"},"status":{"type":"string","example":"active"}}},"ProgramSummary":{"type":"object","required":["object","livemode","id","slug","name","status"],"properties":{"object":{"type":"string","enum":["program"]},"livemode":{"type":"boolean"},"id":{"type":"string","pattern":"^pgm_[0-9a-f]+$"},"slug":{"type":"string","example":"alz-tx-decisions"},"name":{"type":"string"},"status":{"type":"string"}}},"ListResponseRecipient":{"type":"object","required":["object","livemode","data","has_more","next_cursor","url"],"properties":{"object":{"type":"string","enum":["list"]},"livemode":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/RecipientRecord"}},"has_more":{"type":"boolean"},"next_cursor":{"type":"string","nullable":true,"description":"Opaque cursor; null when no more pages."},"url":{"type":"string","description":"Echoed request path + query for client-side correlation."}},"example":{"object":"list","livemode":false,"data":[{"object":"recipient_record","livemode":false,"token":"Ab3kQ9","npi":"1234567890","email":"jdoe@example.com","tendral_recipient_id":"rcp_018f5a2e7c127c8da123cafed00dfeed","tendral_campaign_id":"cmp_018f5a2e7c127c8da123cafed00dfeef","tendral_program_id":"pgm_018f5a2e7c127c8da123cafed00dffff","created_at":"2026-04-26T18:00:00.000Z","updated_at":"2026-04-27T15:30:00.000Z","sent_at":"2026-04-27T13:02:00.000Z","bounced_at":null,"last_event_at":"2026-04-27T15:30:00.000Z"}],"has_more":false,"next_cursor":null,"url":"/v1/partners/stitched/tokens?since=2026-04-01T00%3A00%3A00Z&limit=100"}},"ListResponseEvent":{"type":"object","required":["object","livemode","data","has_more","next_cursor","url"],"properties":{"object":{"type":"string","enum":["list"]},"livemode":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/PartnerEventEnvelope"}},"has_more":{"type":"boolean"},"next_cursor":{"type":"string","nullable":true},"url":{"type":"string"}},"example":{"object":"list","livemode":false,"data":[{"object":"event","livemode":false,"event_id":"evt_018f5a2e7c127c8da123cafed00dfeed","event_type":"outreach.email_sent","event_version":1,"occurred_at":"2026-04-27T13:02:00Z","emitted_at":"2026-04-27T13:02:01Z","source":"tendral","data":{"tendral_recipient_id":"rcp_018f5a2e7c127c8da123cafed00dfeed","tendral_campaign_id":"cmp_018f5a2e7c127c8da123cafed00dfeef","token":"Ab3kQ9"}}],"has_more":false,"next_cursor":null,"url":"/v1/partners/stitched/events?since=2026-04-01T00%3A00%3A00Z&limit=100"}},"StitchedDigestFunnelCounts":{"type":"object","required":["account_created","learner_qualified","completer_qualified"],"properties":{"account_created":{"type":"integer","minimum":0},"learner_qualified":{"type":"integer","minimum":0},"completer_qualified":{"type":"integer","minimum":0}}},"StitchedDigestProgram":{"type":"object","required":["tendral_program_id","program_name","stitched_program_slug","program_status","funnel","outreach","quota"],"properties":{"tendral_program_id":{"type":"string","description":"Tendral program id (`pgm_<hex>`)."},"program_name":{"type":"string"},"stitched_program_slug":{"type":"string"},"program_status":{"type":"string","enum":["draft","active","completed","archived"]},"funnel":{"type":"object","required":["today","all_time"],"properties":{"today":{"$ref":"#/components/schemas/StitchedDigestFunnelCounts"},"all_time":{"$ref":"#/components/schemas/StitchedDigestFunnelCounts"}}},"outreach":{"type":"object","required":["today"],"properties":{"today":{"type":"object","required":["email_sent","email_opened","email_clicked","email_bounced"],"properties":{"email_sent":{"type":"integer","minimum":0},"email_opened":{"type":"integer","minimum":0,"description":"Bot-suspected opens excluded."},"email_clicked":{"type":"integer","minimum":0},"email_bounced":{"type":"integer","minimum":0}}}}},"quota":{"type":"object","required":["learner","completer","paused_at"],"properties":{"learner":{"type":"object","required":["count","quota"],"properties":{"count":{"type":"integer","minimum":0},"quota":{"type":"integer","minimum":0,"nullable":true,"description":"Null when no cap is set."}}},"completer":{"type":"object","required":["count","quota"],"properties":{"count":{"type":"integer","minimum":0},"quota":{"type":"integer","minimum":0,"nullable":true,"description":"Null when no cap is set."}}},"paused_at":{"type":"string","format":"date-time","nullable":true}}}}},"StitchedDigest":{"type":"object","required":["object","livemode","date","generated_at","programs"],"properties":{"object":{"type":"string","enum":["digest"]},"livemode":{"type":"boolean"},"date":{"type":"string","format":"date"},"generated_at":{"type":"string","format":"date-time"},"programs":{"type":"array","items":{"$ref":"#/components/schemas/StitchedDigestProgram"}}},"example":{"object":"digest","livemode":false,"date":"2026-05-12","generated_at":"2026-05-12T13:00:01.234Z","programs":[{"tendral_program_id":"pgm_018f5a2e7c127c8da123cafed00dfeed","program_name":"Alzheimer's CME Traffic","stitched_program_slug":"stitched-alz-traffic","program_status":"active","funnel":{"today":{"account_created":12,"learner_qualified":8,"completer_qualified":3},"all_time":{"account_created":145,"learner_qualified":92,"completer_qualified":41}},"outreach":{"today":{"email_sent":230,"email_opened":87,"email_clicked":0,"email_bounced":4}},"quota":{"learner":{"count":92,"quota":200},"completer":{"count":41,"quota":100},"paused_at":null}}]}}}},"tags":[{"name":"System","description":"Health and status endpoints.","externalDocs":{"url":"https://developer.tendralhealth.com/docs/quickstart","description":"Quickstart"}},{"name":"Surveys","description":"Survey creation and validation. Endpoints in this group are not yet shipped — schema is published for partner planning.","externalDocs":{"url":"https://developer.tendralhealth.com/docs/quickstart","description":"Quickstart"}},{"name":"Uploads","description":"File upload utilities. Endpoints in this group are not yet shipped — schema is published for partner planning.","externalDocs":{"url":"https://developer.tendralhealth.com/docs/quickstart","description":"Quickstart"}},{"name":"Partners","description":"Partner-organization integration endpoints. Bearer-token authenticated, rate-limited per key (default 100/min), Idempotency-Key on POSTs, Stripe-style type-prefixed IDs. Stitched Health is the first consumer — see [the protocol contract](https://github.com/epaynter/tendral-stitched-integration). Webhook events fired by Tendral are documented at [/docs/webhooks](https://developer.tendralhealth.com/docs/webhooks); error codes at [/docs/errors](https://developer.tendralhealth.com/docs/errors).","externalDocs":{"url":"https://developer.tendralhealth.com/docs/quickstart","description":"Quickstart for partners"}}]}