Loading documentation

Access required

Your account doesn't have access to the documentation. Contact your administrator.

Sign out
Skip to main content

Encounter Document Draft

Status: stable Category: trinityassist Added: 2026-07-16 Last reviewed: 2026-07-16

Summary

Generate a complete clinical encounter document from one visit transcript. Where /api/encounters/soap/draft returns just the SOAP note, this endpoint returns every content and attestation section a clinician reviews and signs off for a full visit: the SOAP note, the injury and patient baselines (initial visit), the since-last-visit block (follow-up visit), next-visit goals, work-relatedness inputs, work status, and disposition.

The response carries only the drafted sections. It is designed to be dropped straight into a sign-off surface: content fields are drafted from the transcript, and clinician-owned attestations (work status, disposition, work-relatedness, prior-goal scoring) are pre-filled proposals the clinician confirms or edits before signing.

This is a sibling of the SOAP-draft endpoint, not a replacement. The SOAP endpoint still serves the live mid-call proposal feed; this endpoint produces the whole document at review time.

Endpoint

FieldValue
MethodPOST
Path/api/encounters/document/draft
Full URLhttps://engine.tripod-health.com/api/encounters/document/draft
AuthenticationAPI key (required)
Request bodyapplication/json
Response bodyapplication/json
IdempotentNo (each call may return a slightly different draft)
Rate-limitedYes

Purpose

The endpoint exists so a consumer can assemble a fully pre-filled clinician sign-off surface from a visit transcript in one call, instead of stitching together a SOAP note, goals, baselines, work status, and disposition from separate surfaces. The clinician reviews the drafted document, edits anything, and signs -- sign-off is the only moment the data becomes a clinical record on the consumer's side.

The engine returns a plain JSON object whose keys are the document's sections. The consumer owns the surrounding envelope (the document's schema version, its encounter id, the delivery medium, and the patient contact values) and fills those in itself; the engine never sees or returns them.

Authentication

Standard API key. Set apikey: $ENGINE_API_KEY on the request. See authentication.md. A missing or unknown key returns 401 Unauthenticated at the gateway before the request reaches the engine.

Request

Headers

HeaderRequiredNotes
apikeyYesYour engine API key.
Content-TypeYesapplication/json.
X-Request-IDNoEchoed in the X-Request-ID response header; lets you correlate logs.

Body

{
"sessions": [
{
"session_id": "00000000-0000-0000-0000-000000000001",
"started_at": "2026-05-12T14:02:00.000Z",
"segments": [
{
"speaker": "provider",
"speaker_label": "Provider",
"text": "Hello, can you describe what brings you in today?",
"started_at_ms": 0,
"ended_at_ms": 3500
},
{
"speaker": "patient",
"speaker_label": "Patient",
"text": "I've had lower-back pain for about a week. It started after I was lifting boxes at work.",
"started_at_ms": 3500,
"ended_at_ms": 14000
}
]
}
],
"encounter_meta": {
"visit_type": "initial",
"encounter_type": "telephonic"
},
"open_prior_goals": []
}
FieldTypeAlways presentDescription
sessionsarrayYesDiarized transcript sessions in chronological order. At least one segment across all sessions is required. Max 32 sessions.
sessions[].session_idstringYesConsumer-supplied opaque session id. Max 64 chars.
sessions[].started_atstringYesISO 8601 timestamp of the session's first segment.
sessions[].segmentsarrayYesDiarized transcript turns in chronological order. Max 4,096 per session.
sessions[].segments[].speakerenumYesOne of provider, patient, other, unknown.
sessions[].segments[].speaker_labelstring | nullNoOptional human-readable label. Opaque to the engine. Max 128 chars.
sessions[].segments[].textstringYesUtterance text. Max 8,000 chars.
sessions[].segments[].started_at_msintegerYesMilliseconds since session start.
sessions[].segments[].ended_at_msintegerYesMilliseconds since session start.
encounter_metaobjectYesEncounter context.
encounter_meta.visit_typeenumYesOne of initial, follow_up. Selects which document sections are returned.
encounter_meta.encounter_typestringYesThe delivery medium as an opaque categorical (e.g. telephonic, messaging, telemedicine). May be an empty string. Max 64 chars.
open_prior_goalsarrayNoThe currently-open goals from prior visits that a follow-up may score. Empty (or omitted) for initial visits. Max 64.
open_prior_goals[].goal_idstringYesOpaque id of an open prior goal. The engine only ever references these ids back in sinceLastVisit.priorGoalScores. Max 64 chars.
open_prior_goals[].measure_keystringYesThe controlled measure key of that goal. Max 64 chars.

At least one transcript segment MUST be present; a request with no segments returns 400 ValidationError: "At least one transcript segment is required".

The body MUST NOT contain identifier-shaped values. The engine's inbound de-identification middleware rejects any such payload with 400 DeIdentificationViolation. session_id, speaker_label, and open_prior_goals[].goal_id are opaque to the engine -- use consumer-side surrogates, never patient identifiers. See conventions.md.

Response

The response is a JSON object whose top-level keys are the document sections for the requested visit_type. All keys are camelCase. The engine returns only the sections below -- it never returns schemaVersion, encounterId, medium, purpose, or contact; the consumer owns those and fills them in around the drafted sections.

Sections by visit type:

  • initial: soap, injuryBaseline, patientBaseline, workRelatedness, nextVisitGoals, workStatus, disposition.
  • follow_up: soap, sinceLastVisit, nextVisitGoals, workStatus, disposition.

Content fields (soap, baselines, sinceLastVisit.grocScore, nextVisitGoals) are drafted from the transcript. Attestation fields (workStatus.status, disposition.decision, workRelatedness, sinceLastVisit.priorGoalScores) are clinician-owned proposals: the engine pre-fills them only when the transcript supports a valid value and every dependency is satisfied, and leaves them null otherwise for the clinician to decide.

Success -- 200 OK (initial visit)

The example below is a real production response generated from a synthetic single-session transcript covering one week of work-related lower-back pain, called with encounter_meta.visit_type: "initial".

{
"soap": {
"subjective": "The patient presents with a chief complaint of lower-back pain for approximately one week. The patient reports onset following a full day of lifting and moving inventory boxes at work. Pain is rated 6/10 at baseline, spiking to 8/10 with forward bending. Symptoms are worse in the morning and improve somewhat with movement. The patient denies any numbness, tingling, or lower-extremity weakness. No current medications reported. No known drug allergies.",
"objective": "Range of motion is mildly limited in lumbar flexion. Straight-leg raise is negative bilaterally. No focal neurological deficits noted.",
"assessment": "The patient presents with acute lumbar strain following an occupational lifting incident approximately one week prior. Pain pattern is consistent with mechanical low-back pain. Absence of radicular symptoms, negative straight-leg raise, and no neurological deficits suggest no radiculopathy at this time. Functional limitation is present, particularly with forward flexion and lifting activities.",
"plan": "Initiate NSAID therapy for pain management. Begin home exercise program focusing on lumbar mobility. Modified duty work restrictions with no lifting over 10 lbs. Follow-up in two weeks to reassess pain, range of motion, and functional progress.",
"structuredExtraction": {
"chiefComplaint": "Lower-back pain after lifting boxes at work",
"vitals": [],
"assessments": [
{ "summary": "Acute lumbar strain, occupational onset, mechanical pain pattern, no radicular signs", "confidence": 0.9 }
],
"planItems": [
{ "summary": "Initiate NSAID therapy for pain management", "kind": "medication" },
{ "summary": "Begin home exercise program with lumbar mobility exercises", "kind": "exercise" },
{ "summary": "Modified duty: no lifting over 10 lbs", "kind": "work_restriction" },
{ "summary": "Follow-up visit in two weeks to reassess pain, ROM, and functional tolerance", "kind": "followup" }
],
"followUp": "Two weeks",
"icd10Candidates": [
{ "code": "M54.50", "label": "Low back pain, unspecified", "confidence": 0.85 },
{ "code": "M54.59", "label": "Other low back pain", "confidence": 0.7 },
{ "code": "S39.012A", "label": "Strain of muscle, fascia and tendon of lower back, initial encounter", "confidence": 0.8 }
]
}
},
"injuryBaseline": {
"mechanismCategory": "lifting_overexertion",
"bodyRegion": "spine_lumbar",
"injuryType": "strain_sprain",
"laterality": "na",
"onsetAt": "2026-05-05",
"incidentOccurredAt": "2026-05-05T00:00:00Z",
"natureOfIllnessNote": "Patient reports acute onset of lower-back pain following a full day of lifting and moving inventory boxes at work. Pain worsens with forward bending and is most prominent in the morning."
},
"patientBaseline": {
"medications": [],
"allergies": [],
"chronicConditions": [],
"priorSurgeries": [],
"pastMedicalHistoryNarrative": "No current medications reported. No known drug allergies. No additional past medical, surgical, or chronic condition history elicited during this visit."
},
"workRelatedness": {
"initialAssessment": "likely",
"patientClaim": "yes",
"employerClaim": null
},
"nextVisitGoals": [
{ "measureKey": "pain_intensity", "qualifierKey": null, "targetMedicationId": null, "baselineValue": 6, "targetValue": 3, "targetDirectionKey": null, "priority": "primary" },
{ "measureKey": "lifting_tolerance", "qualifierKey": null, "targetMedicationId": null, "baselineValue": 10, "targetValue": 20, "targetDirectionKey": null, "priority": "secondary" },
{ "measureKey": "rom_limitation", "qualifierKey": null, "targetMedicationId": null, "baselineValue": 1, "targetValue": 0, "targetDirectionKey": null, "priority": "secondary" },
{ "measureKey": "hep_adherence", "qualifierKey": null, "targetMedicationId": null, "baselineValue": 0, "targetValue": 3, "targetDirectionKey": null, "priority": "secondary" }
],
"workStatus": {
"status": "modified_duty",
"effectiveFrom": "2026-05-12",
"effectiveThrough": "2026-05-26",
"restrictions": {
"structured": ["no_lifting_over_10_lbs"],
"custom": "No lifting over 10 lbs. Patient to remain on modified duty pending reassessment in two weeks."
},
"mmiDate": null,
"returnToWorkAt": null
},
"disposition": {
"decision": "schedule_followup",
"followupAt": "2026-05-26T00:00:00Z",
"followupChannel": "sms",
"closeReason": null
}
}

Success -- 200 OK (follow-up visit)

A real production response for a follow-up visit. The request supplied three open_prior_goals; the engine returns a sinceLastVisit block that scores or carries each forward, and omits the injury and patient baselines (those persist from the initial visit).

{
"soap": {
"subjective": "The patient reports significant overall improvement in lower-back pain since the last visit two weeks ago. The patient self-rates global change as approximately +4 on the change scale. Worst pain in the last 24 hours is reported at 3/10. The patient notes improved ability to lift at work without pain flaring up.",
"objective": "Provider notes range of motion is close to full at today's visit.",
"assessment": "The patient is demonstrating meaningful progress with lower-back pain, reporting reduced pain intensity (3/10) and improved lifting tolerance at work. ROM appears near full per provider observation. The trajectory is positive on a modified-duty course with continued home exercise program.",
"plan": "Continue home exercise program. Target pain intensity of 1/10 and lifting tolerance of 30 lbs by next visit. Maintain modified-duty work status. Follow-up in two weeks.",
"structuredExtraction": {
"chiefComplaint": "Lower-back pain follow-up",
"vitals": [],
"assessments": [
{ "summary": "Lower-back pain -- improving, pain now 3/10, ROM near full, lifting tolerance improving with modified duty maintained.", "confidence": 0.95 }
],
"planItems": [
{ "summary": "Continue home exercise program as directed.", "kind": "exercise" },
{ "summary": "Maintain modified-duty work status.", "kind": "work_status" },
{ "summary": "Follow-up visit in two weeks.", "kind": "followup" }
],
"followUp": "Two weeks.",
"icd10Candidates": [
{ "code": "M54.50", "label": "Low back pain, unspecified", "confidence": 0.9 }
]
}
},
"sinceLastVisit": {
"grocScore": 4,
"priorGoalScores": [
{ "goalId": "11111111-1111-1111-1111-111111111111", "scoredValue": null, "scoredOutcomeKey": null, "carryForward": true },
{ "goalId": "22222222-2222-2222-2222-222222222222", "scoredValue": null, "scoredOutcomeKey": null, "carryForward": true },
{ "goalId": "33333333-3333-3333-3333-333333333333", "scoredValue": null, "scoredOutcomeKey": null, "carryForward": true }
]
},
"nextVisitGoals": [
{ "measureKey": "pain_intensity", "qualifierKey": null, "targetMedicationId": null, "baselineValue": 3, "targetValue": 1, "targetDirectionKey": null, "priority": null },
{ "measureKey": "lifting_tolerance", "qualifierKey": null, "targetMedicationId": null, "baselineValue": 0, "targetValue": 30, "targetDirectionKey": null, "priority": null },
{ "measureKey": "hep_adherence", "qualifierKey": null, "targetMedicationId": null, "baselineValue": 2, "targetValue": 3, "targetDirectionKey": null, "priority": null }
],
"workStatus": {
"status": "modified_duty",
"effectiveFrom": null,
"effectiveThrough": null,
"restrictions": {
"structured": [],
"custom": "Clinician to specify applicable lifting and activity restrictions consistent with modified duty."
},
"mmiDate": null,
"returnToWorkAt": null
},
"disposition": {
"decision": "schedule_followup",
"followupAt": "2026-06-09T00:00:00Z",
"followupChannel": "sms",
"closeReason": null
}
}

Response fields

soap (both visit types)

Identical shape to the SOAP draft response, but nested under soap and with camelCase keys (structuredExtraction, chiefComplaint, planItems, icd10Candidates, followUp).

FieldTypeAlways presentDescription
soap.subjectivestring | nullYesPatient-reported history in clinical voice. Null when the transcript is too thin.
soap.objectivestring | nullYesObserved findings during the visit.
soap.assessmentstring | nullYesClinical judgment integrating subjective + objective.
soap.planstring | nullYesNext steps.
soap.structuredExtractionobjectYesStructured read of the note; inner arrays may be empty.
soap.structuredExtraction.chiefComplaintstring | nullNoOne-line presenting complaint.
soap.structuredExtraction.vitals[]arrayYes{ name, value, unit, ts } entries. Up to 32.
soap.structuredExtraction.assessments[]arrayYes{ summary, confidence }. Up to 32.
soap.structuredExtraction.planItems[]arrayYes{ summary, kind }. Up to 32.
soap.structuredExtraction.followUpstring | nullNoFree-text follow-up summary.
soap.structuredExtraction.icd10Candidates[]arrayYes{ code, label, confidence }. Up to 32.

injuryBaseline (initial only)

FieldTypeAlways presentDescription
injuryBaseline.mechanismCategoryenum | nullYesOne of lifting_overexertion, fall_slip_trip, struck_by, caught_in, repetitive_motion, motor_vehicle, laceration_cut, burn, exposure, other.
injuryBaseline.bodyRegionstring | nullYesA controlled body-region code, or null when the engine cannot map one confidently.
injuryBaseline.injuryTypestring | nullYesA controlled injury-type code, or null.
injuryBaseline.lateralityenum | nullYesOne of left, right, bilateral, na.
injuryBaseline.onsetAtstring | nullYesISO date (YYYY-MM-DD).
injuryBaseline.incidentOccurredAtstring | nullYesISO date-time.
injuryBaseline.natureOfIllnessNotestring | nullYesFree-text description of how the injury occurred.

patientBaseline (initial only)

FieldTypeAlways presentDescription
patientBaseline.medications[]arrayYes{ name, dose, frequency, route, status }; status is active or discontinued.
patientBaseline.allergies[]arrayYesStrings.
patientBaseline.chronicConditions[]arrayYesStrings.
patientBaseline.priorSurgeries[]arrayYesStrings.
patientBaseline.pastMedicalHistoryNarrativestring | nullYesFree-text history summary.

workRelatedness (initial only)

Clinician inputs only -- the engine never returns a derived work-relatedness verdict; the consumer recomputes the binary decision from these inputs.

FieldTypeAlways presentDescription
workRelatedness.initialAssessmentenum | nullYesOne of definitely, likely, unsure, unlikely, absolutely_not. When unsure, patientClaim is non-null.
workRelatedness.patientClaimenum | nullYesOne of yes, maybe, no.
workRelatedness.employerClaimenum | nullYesOne of yes, maybe, no.

sinceLastVisit (follow-up only)

FieldTypeAlways presentDescription
sinceLastVisit.grocScoreinteger | nullYesPatient-reported global change, -7..+7.
sinceLastVisit.priorGoalScores[]arrayYesOne entry per supplied open_prior_goals id.
sinceLastVisit.priorGoalScores[].goalIdstringYesAn id from the request's open_prior_goals; the engine references no other ids.
sinceLastVisit.priorGoalScores[].scoredValuenumber | nullNoObjective score, when the transcript clearly supports one.
sinceLastVisit.priorGoalScores[].scoredOutcomeKeystring | nullNoSubjective outcome, when supported.
sinceLastVisit.priorGoalScores[].carryForwardbooleanYestrue (the safe default) rolls the goal into the next interval unscored.

nextVisitGoals (both visit types)

Each entry is a goal to set for the next visit. measureKey is always a valid controlled measure key; objective measures carry a baselineValue and targetValue within the measure's scale, subjective measures carry a targetDirectionKey. When nothing is well-supported the array is empty.

FieldTypeAlways presentDescription
nextVisitGoals[].measureKeystringYesA valid controlled measure key.
nextVisitGoals[].qualifierKeystring | nullNoRequired controlled qualifier for measures that declare one; null otherwise.
nextVisitGoals[].targetMedicationIdstring | nullNoAlways null (the engine holds no chart medication ids).
nextVisitGoals[].baselineValuenumber | nullNoObjective measures: the value on the measure's scale.
nextVisitGoals[].targetValuenumber | nullNoObjective measures: the target on the measure's scale.
nextVisitGoals[].targetDirectionKeystring | nullNoSubjective measures: the chosen target direction.
nextVisitGoals[].prioritystring | nullNoprimary or secondary, or null.

workStatus (both visit types) -- attestation

FieldTypeAlways presentDescription
workStatus.statusenum | nullYesOne of full_duty, modified_duty, off_work, mmi_reached, or null (clinician decides). modified_duty always carries restrictions; mmi_reached always carries mmiDate.
workStatus.effectiveFromstring | nullYesISO date.
workStatus.effectiveThroughstring | nullYesISO date; null means "until next visit".
workStatus.restrictionsobject | nullYes{ structured: string[], custom: string } when status is modified_duty, else null.
workStatus.mmiDatestring | nullYesISO date; present when status is mmi_reached.
workStatus.returnToWorkAtstring | nullYesISO date; optional clinician-set milestone.

disposition (both visit types) -- attestation

FieldTypeAlways presentDescription
disposition.decisionenum | nullYesOne of schedule_followup, close_case, or null. schedule_followup always carries followupAt + followupChannel and requires at least one nextVisitGoals entry; close_case requires workStatus.status = mmi_reached + a closeReason.
disposition.followupAtstring | nullYesISO date-time.
disposition.followupChannelenum | nullYesOne of sms, email.
disposition.closeReasonstring | nullYesPresent when decision is close_case.

Generated free-text passes the engine's outbound de-identification scan before returning; identifier-shaped output fails the call with 502 UpstreamError rather than being silently scrubbed. The structured date, code, and enum fields above are not free text and are validated against the contract, not the de-id scan.

Status codes

CodeMeaningWhen
200OKDocument generated.
400ValidationErrorBody shape, enum violation, or no transcript segments.
400DeIdentificationViolationBody carries an identifier-shaped value.
401UnauthenticatedMissing or invalid API key.
413PayloadTooLargeBody exceeds 512 KB.
415UnsupportedMediaTypeContent-Type is not application/json.
429RateLimitedPer-consumer rate limit exceeded.
500InternalErrorUnexpected failure.
502UpstreamErrorUpstream model call failed, or generated text failed the outbound de-id pass.
503ServiceUnavailableService temporarily unhealthy.
504UpstreamTimeoutUpstream model call exceeded the engine's generation budget.

Error envelope

{
"timestamp": "2026-07-16T07:04:30.558Z",
"error": "UpstreamError",
"message": "Generated text failed safety check: appears to contain a phone number",
"request_id": "bf63b5f3-b809-46fb-8d3b-c4b146fd1ecd"
}

See errors.md for the full code list.

Examples

curl -- initial visit

curl -sS -X POST "https://engine.tripod-health.com/api/encounters/document/draft" \
-H "apikey: $ENGINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessions": [
{
"session_id": "00000000-0000-0000-0000-000000000001",
"started_at": "2026-05-12T14:02:00.000Z",
"segments": [
{ "speaker": "provider", "speaker_label": "Provider", "text": "What brings you in today?", "started_at_ms": 0, "ended_at_ms": 3500 },
{ "speaker": "patient", "speaker_label": "Patient", "text": "Lower-back pain for a week, started lifting boxes at work. Worst pain about a six.", "started_at_ms": 3500, "ended_at_ms": 14000 }
]
}
],
"encounter_meta": { "visit_type": "initial", "encounter_type": "telephonic" },
"open_prior_goals": []
}'

curl -- follow-up visit (scoring open prior goals)

curl -sS -X POST "https://engine.tripod-health.com/api/encounters/document/draft" \
-H "apikey: $ENGINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessions": [
{
"session_id": "00000000-0000-0000-0000-0000000000f1",
"started_at": "2026-05-26T14:00:00.000Z",
"segments": [
{ "speaker": "patient", "speaker_label": "Patient", "text": "Quite a bit better, maybe a plus four. Worst pain is down to a three and I can lift more at work.", "started_at_ms": 0, "ended_at_ms": 12000 }
]
}
],
"encounter_meta": { "visit_type": "follow_up", "encounter_type": "telephonic" },
"open_prior_goals": [
{ "goal_id": "11111111-1111-1111-1111-111111111111", "measure_key": "pain_intensity" },
{ "goal_id": "22222222-2222-2222-2222-222222222222", "measure_key": "lifting_tolerance" }
]
}'

Python (requests)

import os, requests

resp = requests.post(
"https://engine.tripod-health.com/api/encounters/document/draft",
headers={"apikey": os.environ["ENGINE_API_KEY"]},
json={
"sessions": [
{
"session_id": "00000000-0000-0000-0000-000000000001",
"started_at": "2026-05-12T14:02:00.000Z",
"segments": [
{"speaker": "patient", "speaker_label": "Patient",
"text": "Lower-back pain for a week after lifting boxes at work.",
"started_at_ms": 0, "ended_at_ms": 8000},
],
}
],
"encounter_meta": {"visit_type": "initial", "encounter_type": "telephonic"},
"open_prior_goals": [],
},
timeout=60,
)
resp.raise_for_status()
doc = resp.json()
print(doc["soap"]["subjective"])
for goal in doc["nextVisitGoals"]:
print(goal["measureKey"], goal.get("baselineValue"), "->", goal.get("targetValue"))

Intended use

  • Assemble a pre-filled clinician sign-off surface for a full visit: render each section as editable fields, let the clinician confirm attestations, and persist on your side at sign-off.
  • Pass encounter_meta.visit_type to select the document shape -- initial for a first encounter (with baselines and work-relatedness), follow_up for a continuing visit (with the since-last-visit block).
  • On a follow-up, supply the open prior goals in open_prior_goals; render the returned sinceLastVisit.priorGoalScores for the clinician to confirm or adjust. The safe default carries each goal forward unscored.
  • Treat workStatus and disposition as pre-filled proposals; a null status or decision means the engine deliberately left the decision to the clinician.

Mistaken use

  • Persisting the document without clinician review. Every field is a draft or a proposal; sign-off is where the clinical record is created.
  • Reading workRelatedness as a decision. It is the clinician's input set; the binary work-relatedness determination is recomputed on your side.
  • Using this endpoint for the live mid-call proposal feed. That is /api/encounters/soap/draft; this endpoint is the full-document review-time draft.
  • Scoring goals that were not supplied in open_prior_goals. The engine only references the ids you send; any other id is ignored.

Debugging

  1. Capture the X-Request-ID response header (or the request_id in an error envelope) before anything else.
  2. 400 ValidationError "At least one transcript segment is required" -- the request had no segments across any session.
  3. 502 UpstreamError "Generated text failed safety check: ..." -- the model produced identifier-shaped free text; the engine refused to return it. Retry; if it persists, open a ticket with the request_id.
  4. 504 UpstreamTimeout -- the document generation exceeded the engine's budget. The engine's budget sits below the recommended 60s client timeout.

Rate limits

Per-consumer limits are enforced at the gateway. See rate-limits.md. Responses include standard RateLimit-* headers.

Change history

DateChange
2026-07-16Initial publication.