Transcript Trim
Status:
stableCategory:trinityassistAdded:2026-05-26Last reviewed:2026-05-26
Summary
Condense one window of raw diarized transcript into a tight clinical-content extract. The engine drops conversational filler -- pleasantries, scheduling chatter, repeated content -- and keeps the medically-relevant material (symptoms, findings, assessment, plan, patient-reported barriers). Intended to run periodically during a live visit so the downstream SOAP draft can read condensed text instead of re-processing every byte of raw transcript on each refresh.
This is the periodic half of TrinityAssist's transcript pipeline: the
consumer accumulates raw diarized turns from a live capture, ships
one window per tick to this endpoint, and then feeds the
trimmed_transcript it gets back into
/api/encounters/soap/draft at
call_mode: "refresh" (mid-call) or call_mode: "final"
(end-of-call wrap-up).
Endpoint
| Field | Value |
|---|---|
| Method | POST |
| Path | /api/encounters/transcript/trim |
| Full URL | https://engine.tripod-health.com/api/encounters/transcript/trim |
| Authentication | API key (required) |
| Request body | application/json |
| Response body | application/json |
| Idempotent | No (each call may produce a slightly different rewrite) |
| Rate-limited | Yes |
Generated text passes the engine's outbound de-identification scan
before returning; identifier-shaped output fails with
502 UpstreamError rather than scrubbing silently.
Purpose
Long clinical visits produce long transcripts. Re-sending the entire raw transcript to the SOAP-draft endpoint every few minutes would be expensive, slow, and would push the model to re-read content it already saw. The trim endpoint is a small, cheap condensation pass that runs on the engine's Haiku tier and emits a clinical-content extract one window at a time. The consumer is expected to accumulate the returned extracts (or replace its rolling extract each tick) and hand the result to SOAP draft when a refresh is due.
The engine selects the model. The trim pass always runs on Haiku -- the cost profile is what makes a 5-minute cadence viable. There is no consumer-controlled tier escalation on this endpoint.
Authentication
Standard API-key. Set apikey: $ENGINE_API_KEY on the request. See
authentication.md.
Request
Headers
| Header | Required | Description |
|---|---|---|
apikey | Yes | Consumer API key. |
Content-Type | Yes | application/json. |
X-Request-ID | No | Optional client correlation ID. Echoed back in the response. |
Body
{
"window_text": "provider: What brings you in today?\npatient: My lower back has been bad for about three weeks since helping my brother move...\nprovider: Numbness or weakness anywhere?\npatient: No, but it tingles down my right calf sometimes.\nprovider: Pain scale?\npatient: Five sitting here, eight driving over.\n...",
"encounter_meta": {
"visit_type": "initial",
"encounter_type": "in_person"
}
}
Body fields
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
window_text | string | Yes | non-empty, ≤ 64,000 chars | Raw diarized transcript for one window. Newline-joined speaker: text lines is the expected shape, but any prose with attributable turns is accepted. |
encounter_meta | object | Yes | — | Encounter context the engine uses to calibrate the trim aggressiveness. |
encounter_meta.visit_type | enum | Yes | one of initial, follow_up, touchbase, amendment, final | Lets the engine drop more baseline context on follow-ups and keep more on initial visits. |
encounter_meta.encounter_type | enum | Yes | one of in_person, telemedicine, telephonic | Modality of the visit. |
The body MUST NOT contain identifier-shaped values. The engine's inbound de-identification posture rejects any payload that carries names, dates of birth, SSN-shaped values, phone numbers, email addresses, MRNs, or similar identifiers. Strip identifiers from the transcript on the consumer side before calling this endpoint. See conventions.md.
A model_tier_hint field is reserved for forward compatibility but
is currently ignored. Send it or don't -- the trim pass runs on
Haiku regardless.
Response
Success — 200 OK
{
"trimmed_text": "Chief Complaint: Right lower back pain for three weeks, onset after lifting during a move.\n\nHistory of Present Illness: The patient describes a deep ache in the right lower back with sharp, knife-like pain on bending or twisting. Tingling radiates down the right leg, occasionally reaching the calf. Current pain is 5/10 sitting at rest; pain was 8/10 while driving despite ibuprofen use. Sitting exacerbates symptoms; walking is tolerable; lying flat with pillow under knees provides relief. Patient has been on light duty at work (warehouse picker) for one week due to pain. Denies numbness, weakness, or bowel/bladder changes.\n\nPhysical Examination: Forward flexion limited to 30 degrees. Straight leg raise positive on right at 50 degrees. Reflexes 2+ and symmetric. Sensation intact. Motor strength 5/5 throughout.\n\nAssessment: Lumbar strain with right L5 nerve root irritation. No red flags.\n\nPlan: Naproxen 500 mg twice daily with food for two weeks; ice four times daily; physical therapy twice weekly for four weeks focused on McKenzie extension exercises and core stabilization. Continue light duty with no lifting over 20 pounds for two more weeks. Re-evaluation in three weeks. The clinician will have the front desk verify insurance benefits prior to discharge and offered financial-assistance application given patient's concern about PT costs and reduced work hours with high-deductible plan.",
"model_tier": "haiku",
"request_id": "abc-123",
"timestamp": "2026-05-26T03:53:19.504Z"
}
The example above is a real production response from a synthetic initial in-person visit covering three weeks of right lower-back pain with mild radicular signs. The trim pass discarded scheduling chatter and pleasantries, preserved the financial-barrier mention in the Plan section because it changes the care plan, and produced prose in clinical voice with one paragraph per clinical topic.
Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
trimmed_text | string | Yes | Condensed clinical-content rewrite of the input window, in clinical voice. Max 8,000 characters. May be short when the window is thin -- the engine does not pad. |
model_tier | string | Yes | Always "haiku" for this endpoint. Returned so callers can confirm tier selection and budget accordingly. |
request_id | string | null | Yes | Echo of the inbound X-Request-ID if present, otherwise null. |
timestamp | string | Yes | ISO 8601 UTC server timestamp at response emission. |
Status codes
| Code | Meaning | When |
|---|---|---|
200 | OK | Trim generated. |
400 | ValidationError | Missing or empty window_text, missing encounter_meta, or wrong type. field names the offender when known. |
400 | DeIdentificationViolation | Body carries an identifier-shaped value. |
401 | Unauthenticated | Missing or invalid API key. |
413 | PayloadTooLarge | Body exceeds 512 KB. |
415 | UnsupportedMediaType | Content-Type is not application/json. |
422 | UnprocessableEntity | Body parsed but a downstream validation rule rejected it (e.g. unknown encounter_type enum value). |
429 | RateLimited | Per-consumer rate limit exceeded. |
500 | InternalError | Unexpected failure. |
502 | UpstreamError | Upstream model call failed, or generated text failed the outbound de-id pass. |
503 | ServiceUnavailable | Service temporarily unhealthy. |
504 | UpstreamTimeout | Upstream model call exceeded the engine's trim budget. |
Error envelope
{
"timestamp": "2026-05-26T03:56:27.691Z",
"error": "ValidationError",
"message": "Field 'window_text' must be a non-empty string",
"field": "window_text",
"request_id": "abc-123"
}
See errors.md for the full error code list.
Examples
curl — success
curl -sS -X POST "https://engine.tripod-health.com/api/encounters/transcript/trim" \
-H "apikey: $ENGINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"window_text": "provider: What brings you in today?\npatient: My lower back has been bad for about three weeks since helping my brother move. Deep ache, sharp on bending, sometimes tingles to the right calf.\nprovider: Numbness or weakness? Bowel or bladder issues?\npatient: No.\nprovider: Pain scale right now?\npatient: Five sitting, eight driving over.\nprovider: Better or worse with what?\npatient: Sitting kills it. Walking is fine. Lying flat with a pillow under my knees helps.\nprovider: Exam shows forward flexion to thirty degrees, right SLR positive at fifty, reflexes two plus symmetric, sensation intact, motor five over five.\nprovider: Lumbar strain with right L5 irritation. Naproxen five hundred twice daily for two weeks, ice four times a day, PT twice a week for four weeks on McKenzie extension and core stabilization. Light duty two more weeks, no lifting over twenty pounds. Back in three weeks.",
"encounter_meta": { "visit_type": "initial", "encounter_type": "in_person" }
}'
curl — validation error
curl -sS -X POST "https://engine.tripod-health.com/api/encounters/transcript/trim" \
-H "apikey: $ENGINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"window_text":"","encounter_meta":{"visit_type":"initial","encounter_type":"in_person"}}'
Expected response (400):
{
"timestamp": "2026-05-26T03:56:27.691Z",
"error": "ValidationError",
"message": "Field 'window_text' must be a non-empty string",
"field": "window_text",
"request_id": "abc-123"
}
Python — requests
import os, requests
resp = requests.post(
"https://engine.tripod-health.com/api/encounters/transcript/trim",
headers={"apikey": os.environ["ENGINE_API_KEY"]},
json={
"window_text": window_text,
"encounter_meta": {"visit_type": "initial", "encounter_type": "in_person"},
},
timeout=60,
)
resp.raise_for_status()
trimmed = resp.json()["trimmed_text"]
Node — fetch
const resp = await fetch(
"https://engine.tripod-health.com/api/encounters/transcript/trim",
{
method: "POST",
headers: {
apikey: process.env.ENGINE_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
window_text: windowText,
encounter_meta: { visit_type: "initial", encounter_type: "in_person" },
}),
},
);
if (!resp.ok) throw new Error(`Engine ${resp.status}: ${await resp.text()}`);
const { trimmed_text } = await resp.json();
Intended use
- Periodic condensation during a live capture. The expected pattern is one call per visit per tick on a roughly 5-minute cadence, shipping whatever transcript has accumulated since the last tick.
- Feeding the result into
/api/encounters/soap/draftastrimmed_transcriptwhen calling withcall_mode: "refresh"orcall_mode: "final". - Bounded keepalive of clinical content. Because the engine never persists transcripts beyond the call, this endpoint is also the consumer's mechanism to compact a long visit's transcript into a size the SOAP-draft prompt can carry.
Mistaken use
- Calling this endpoint with the full encounter transcript at the
end of the visit instead of using SOAP draft's
call_mode: "final"with the accumulated trim text. The trim endpoint emits a tight extract, not a SOAP note -- it has no Subjective / Objective / Assessment / Plan structure and no ICD-10 candidates. - Persisting
trimmed_textas the clinical record. The trim pass is an intermediate condensation step, not a signed note. The signed artifact is the clinician-edited SOAP draft. - Sending raw patient identifiers in
window_text. The transcript must be de-identified before it reaches this endpoint. - Trying to escalate the model with
model_tier_hint. The engine picks the tier; the hint is accepted for forward-compatibility but ignored. Trim always runs on Haiku. - Treating the output as deterministic. The model produces a fresh rewrite each call; even identical input may differ on rerun.
Debugging
- Capture the
request_idfrom the response (success or error) or theX-Request-IDresponse header. Every support ticket starts here. 400 ValidationError-- thefieldextra names the offender. Most oftenwindow_textis empty orencounter_metais missing.422 UnprocessableEntity-- a downstream enum or shape constraint was violated (e.g.encounter_type: "clinic"is not a valid value). The engine surfaces this as a genericUnprocessableEntityenvelope today; check the request body against the schema in this doc before retrying.502 UpstreamError-- either the model call failed, or the generated text failed the outbound de-id scan. The latter indicates the upstream model produced an identifier-shaped string from a window that already contained one. Strip identifiers from the input transcript and retry.504 UpstreamTimeout-- the trim took too long. Most likely the window is too large; trim shorter windows. The engine's internal budget for this endpoint is 25 s.
Rate limits
Per-consumer limits are enforced at the gateway. See rate-limits.md.
Related
POST /api/encounters/soap/draft-- consumes thetrimmed_textfrom this endpoint astrimmed_transcriptduring periodic refresh and end-of-call final-draft passes.- conventions.md -- de-identification contract, request IDs, timestamp shape.
- errors.md -- error envelope and code list.
Change history
| Date | Change |
|---|---|
| 2026-05-26 | Initial publication. |