- Each fact becomes one resource.
- Codes are positive. Negation, history, and uncertainty go into a status or value field.
- Codes come from a fixed scope. A mention with no fitting code produces no resource.
- Values come from the note. The endpoint fills only what the note states.
- Every value points back to the note. Mentions show the source text, and alternatives show other codes that could fit.
Each fact becomes one resource
The endpoint splits a note into single facts, and creates one resource for each:
The kind of fact decides the resource group:
A procedure and its result are two facts. “Chest X-ray shows consolidation” gives a Procedure for the X-ray and an imaging Observation for the consolidation.
Every response also holds one Patient and one Encounter, even when the note says nothing about them.
Codes are positive
SNOMED CT has codes that build a negation or a time into the code itself, such as86699002 Afebrile or 399211009 History of myocardial infarction. The endpoint is built to avoid these codes. It uses the code for the thing itself, and puts the negation, the time, or the uncertainty in a separate field:
Each resource group carries this meaning in a different field:
The result is one code per concept, however the note phrases it. To find every patient with a fever history, you search for one code,
386661006 Fever, and then read the status fields. You do not also need to search for Afebrile, History of fever, or No fever.
Codes come from a fixed scope
For each coded field, the endpoint only searches a fixed part of a coding system. The scope keeps each code the right kind of concept: a Conditioncode is always a disorder, finding, or event, never a procedure.
Each resource group page lists its full scope, including fixed lists such as severities and categories, under “Codes the endpoint can assign”.
Two consequences follow from the scope:
- No fitting code, no resource. When nothing in the scope fits a mention, the endpoint leaves the resource out rather than assign a code that does not fit. “Father gets chest pain” produces no FamilyMemberHistory, because chest pain is a finding, not a disorder.
- Filters only narrow the scope. A filter can remove codes from the built-in scope, but never add codes outside it.
Values come from the note
The endpoint fills a field only when the note states the value. It is built not to calculate, guess, or fill in a value from context, apart from the few fixed defaults listed below.
The fixed defaults are:
- A few common measurements get a default unit when the note leaves it out: a heart rate gets
beats/min, and a temperature of 37.2 gets°C. - Condition
clinicalStatusand AllergyIntoleranceclinicalStatusdefault toactive. A Condition whose note states that it ended (anabatement) getsinactiveinstead ofactive, because R4 does not allow an ended condition to be active. - Observation
statusdefaults tofinal, Procedurestatustocompleted, and FamilyMemberHistorystatustocompleted. - Encounter
statusdefaults toin-progresswhen the note describes the encounter but not its status, and tounknownwhen the note says nothing about the encounter.
Every value points back to the note
Every resource can carry two kinds of Corti extensions: mentions and alternatives. They let a reviewer, or your code, check each value against the note. Any FHIR system that does not know these extensions ignores them, so the extensions never block an import.Mentions
A mention links one field of a resource to the exact words in the note that support it. For the note “58-year-old woman with type 2 diabetes, poorly controlled. BP 150/90.”, the mentions on the systolic blood pressure Observation read:
In JSON, each mention is one extension with the URL
https://docs.corti.ai/fhir/StructureDefinition/mention. Inside it, field names the FHIR field, and each evidence block holds one text span. The span gives its position in the note (start, end):
evidence blocks when the note supports it in more than one place.
When you send the note as a string (context_type: "other"), start and end count inside that string. In Python, context["data"][62:65] returns "150".
When you send sections (context_type: "clinical_note"), the endpoint writes each section into the note as Title: on its own line, then the body, then a blank line. start and end count across that whole note. Each span inside one section also gets its position in that section:
sectionIdis theidof the section you sent.sectionStartandsectionEndcount inside the section’s title and body joined together. In Python,(section["title"] + section["body"])[sectionStart:sectionEnd]returns the span.
{"id": "dx", "title": "Diagnoses", "body": "Type 2 diabetes."} and {"id": "vitals", "title": "Vitals", "body": "BP 150/90."}, the word BP has start: 37, end: 39, sectionId: "vitals", sectionStart: 6 and sectionEnd: 8. A span that crosses from one section into the next has only start and end.
A field whose value comes from the request, such as a patient.birthDate you sent, or from a fixed default, such as clinicalStatus: active, has no mention, because the note is not its source.
Alternatives
Sometimes more than one code could fit a mention. The endpoint picks one, and lists up to 10 runners-up in an alternative extension:code and bodySite. On FamilyMemberHistory, the field is condition.code. FHIR datatypes lists every sub-extension.
How the response meets FHIR R4
The response is valid FHIR R4 (4.0.1): a FHIR server that validates against R4 accepts the Bundle as it is, apart from the Corti extensions, which a server can either ignore or load from their definition pages, and one Condition status described below. Five rules make that possible without adding anything the note does not say. Every resource links to the Patient. Each entry in the Bundle has afullUrl such as urn:uuid:4f30e37b-653a-5a85-8974-753b6acbef6a, which matches the resource’s id. Clinical resources point to the Patient with subject (Condition, Procedure, Observation, and the Encounter) or patient (AllergyIntolerance and FamilyMemberHistory), and Condition, Procedure, and Observation point to the Encounter with encounter. Each reference names a fullUrl in the same Bundle.
The Bundle is a transaction. The Bundle has type: transaction, and every entry has a request that creates its resource. To store the resources, POST the Bundle as it is to the base URL of your FHIR server. The server creates every resource and replaces the urn:uuid references with the IDs it assigns, as for any FHIR transaction. You do not need to change the Bundle first. The Patient and the Encounter are conditional creates: their request has an ifNoneExist with their identifier, so when your server already holds a Patient or Encounter with that identifier, the new resources link to it instead of creating a duplicate. Send your own IDs in the request to make use of this. See Reduce extraction errors.
A required field the note does not state is marked unknown. R4 requires some fields that a note often leaves out. Instead of guessing a value, the endpoint returns the field with only the standard data-absent-reason extension:
class, FamilyMemberHistory relationship, AllergyIntolerance reaction.manifestation, and Procedure performer.actor. Optional fields the note does not state, such as Patient name, are left out.
A time of day needs a timezone. R4 accepts a time only with seconds and a timezone offset. Notes rarely state a timezone, so a time without one is cut back to its date: “09:00 on 3 November 2026” becomes "2026-11-03". The words stay in the field’s mention. A time that comes with an offset keeps it. Fields that have no text variant, such as AllergyIntolerance lastOccurrence and Observation effectiveDateTime, leave out free text such as “last month”.
Statuses never contradict each other. A Condition with an abatement gets inactive instead of active, and a Condition or AllergyIntolerance with the verificationStatus entered-in-error carries no clinicalStatus, as R4 requires.
One Condition status is outside R4. The endpoint can return Condition clinicalStatus unknown. The R4 value set has a required binding and does not include unknown, so a strict R4 validator rejects it. Map unknown to a status your system accepts before you import the Bundle into a strict R4 server.
Next steps
Reduce extraction errors
Send what you know, skip structured data, and review the results.
Filter FHIR resources
Produce only the resources you need and restrict the codes they carry.