Future Technologies Company

Clinical Scribe API

Submit a consultation recording, poll until it is ready, and receive a structured clinical note for your record system. This page is the reference; every other path on this host returns JSON.

This host serves machines. There is no sign-in and no interface here. If you are looking for the clinician application, it is on a different address, and your FTC contact will give it to you.

How it works

Your system posts a recording and gets an identifier back in under a second. Transcription and note generation happen in the background, so nothing waits on a long request. You poll the identifier until the note is ready, typically within about thirty-five seconds.

The answer comes back in two halves that cannot be confused with each other. note is what belongs in the patient record. advisory is what a clinician reads before signing: warnings, drug register verdicts, and the transcript.

Nothing under advisory may be written into a patient record. It is decision support, not a clinical conclusion. The two halves are separate keys precisely so that writing response["note"] into your record is the correct thing to do and needs no filtering on your side.

Authentication

Every request carries the API key FTC issued you, in either header:

Authorization: Bearer ftcs_a1b2c3d4_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
X-API-Key: ftcs_a1b2c3d4_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Keys are 46 characters: a fixed ftcs label, an 8-character identifier, and a 32-character secret. The identifier is safe to log and is how we tell your keys apart. The secret is shown once, when the key is issued, and is stored only as a hash, so we cannot recover it for you. If a key is lost, ask for a new one and the old one is revoked.

Every rejection returns the same 401 with the same body, whether the key is unknown, wrong, revoked, or belongs to a suspended account. That is deliberate, so the response cannot be used to work out which keys exist.

Submit a recording

POST/v1/encounters

Send multipart/form-data:

FieldRequiredMeaning
audioyesThe consultation recording. Up to 90 MB.
mrnnoYour patient identifier, kept on the audit record.
patient_sexnoMale or Female. Omitted means the sex-based safety checks do not run.
patient_agenoAge in years. Omitted means the age-based safety checks do not run.
languagenoar, en, or omit. The recording is always recognised as Arabic and English together, so a consultation that switches language is heard whole; this value only says which language was mainly spoken. The note is written in English; anything that could not be translated is flagged in advisory.warnings.

Retrying safely

Send an Idempotency-Key header. If you repeat a request with the same key, you get the original submission back rather than a second consultation. Keys are scoped to your account, so they cannot collide with another hospital's.

Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7

Response

202 Accepted, with a Location header pointing at the poll URL.

{
  "id": "0b7f3d5a-9c21-4a8e-b4f1-3d2c9a8e7f06",
  "status": "queued",
  "stage": "received",
  "percent": 0
}

Collect the note

GET/v1/encounters/{id}

Poll every few seconds. note and advisory are empty objects until status becomes ready.

FieldMeaning
statusqueued, running, ready or failed. The last two are final.
stagereceived, transcribing, drafting, ready.
percent0, 15, 40 or 100.
elapsed_msMilliseconds since you submitted.
typical_total_ms35000. What a normal consultation takes.
noteThe record fields. See below.
advisoryReview material. Never written to a record.

percent is fixed per stage, not measured. It tells you which stage the work has reached and nothing about how long the rest will take. Showing it as a progress bar is fine; treating it as a time estimate is not.

A failed job keeps the stage and percent it stopped at, so a failure can report drafting and 40. Read status, never percent, to decide whether the work finished.

When a submission fails

The poll still returns 200, with status: "failed" and an error object carrying code, message and retryable. Retry only when retryable is true.

codeRetryableWhat it means
no_speech_detectednoThere was no speech in the recording.
engine_restartedyesInterrupted before it finished. Send it again.
speech_unavailableyesA fault at our end, not a problem with your recording.
speech_not_configurednoMisconfiguration at our end. Retrying will not help; contact us.
internal_errornoSomething else went wrong.

The note

Eleven fields, named to match the Patient Episode form. A field the consultation did not cover may be absent or present and null, so treat the two the same. The one exception is allergies: in the note this API returns it is always present and never null. What a clinician later signs in your own system is theirs to edit.

FieldTypeContents
primary_chief_complainttextThe main presenting complaint, stated concisely.
onset_course_durationtextWhen it started, how it progressed, for how long.
other_complaintstextSecondary complaints or associated symptoms.
present_illnesstextThe history of present illness.
allergiestextOne of three values: the allergies stated; exactly "Denied (patient or companion states no known allergies)" when the patient, or a relative or companion answering for them, was recorded as asked and saying none; or exactly "Not discussed" when the note records no allergy statement at all (including an empty or placeholder answer). Always present. These values report what the note records from the conversation; they are not a separate check against the transcript, so the clinician confirms them.
smokertext"Yes", "No", or "Not mentioned".
investigations_orderedtextTests, imaging and procedures ordered, with any stated timing.
medications_prescribedtextDrugs prescribed, changed or stopped, with dose, route and frequency.
current_medicationslistMedicines the patient already takes, unchanged at this visit.
new_prescriptionslistMedicines prescribed, changed or stopped at this visit.
plan_follow_uptextAdvice given and the follow-up interval.

Each entry in the two medication lists:

{
  "name_as_said": "Metformin",
  "dose": "500 mg",
  "route": "oral",
  "frequency": "twice daily",
  "duration": "for 3 months",
  "evidence_segments": [14, 15]
}

name_as_said is the drug exactly as the clinician named it, before any register lookup. evidence_segments point at the numbered transcript in advisory.segments, so any entry can be checked against what was said.

allergies distinguishes a denial from silence. "Denied (patient or companion states no known allergies)" means the note records the question as asked and answered no, by the patient or by someone answering for them. "Not discussed" means the note records no allergy statement: it never came up, or the answer was empty or unclear. Neither value is a separate check against the transcript; the clinician confirms both against the conversation. With "Not discussed", advisory.warnings carries an allergies_not_discussed item. Only a denial is an allergy history; "Not discussed" is not. Notes stored before this change may still read "None stated", which does not tell the two apart.

The advisory half

FieldContents
warningsWhat a clinician should look at before signing.
drug_matchesEach drug mentioned, checked against the Saudi drug register.
segmentsThe numbered transcript. evidence_segments point here. Each segment carries role, who said it (clinician, patient or companion, a relative or companion speaking for or with the patient), and role_source: voice when the speakers were told apart by voice, content when the speaker was inferred from what was said. Both are null when not known.
labeled_transcriptThe dialogue in English, each turn labelled Doctor, Patient or Relative/companion. When every segment has a role, it is built from those roles.
raw_transcriptThe recognised speech verbatim, in the language spoken.
transcript_englishPresent only when the consultation was translated.
source_segmentsPresent only when translated: the pre-translation segments, with the same numbers and roles.
unmapped_contentClinically significant statements that fit no note field. For review, not for the record.

Warnings

Three severities, and they describe how likely something is to be a mistake:

SeverityMeaning
criticalWe believe something is wrong. Do not sign without looking.
cautionWe cannot tell. Worth checking.
infoWorth knowing. Nothing is wrong.

Each warning carries a stable code for counting and testing, a message for a clinician to read, a detail explaining it, and evidence_segments pointing at the transcript so any warning can be checked against what was actually said.

Drug register verdicts

The drugs listed in the note's current medications and new prescriptions (or, only when both are empty, in its free-text medication field) are checked by our own code against the Saudi register, never by a language model. A drug mentioned elsewhere in the note is not checked. Three outcomes:

statusMeaning
matchedOne registered product fits. product carries it, with its registration number.
ambiguousSeveral fit. candidates lists them; a clinician picks.
not_foundNothing in the register fits.

not_found does not mean the drug is wrong. The Saudi register does not contain every medicine legally dispensed in the Kingdom. A medicine imported for a named patient, one your pharmacy compounded, one the patient brought from abroad, and supplements, which are registered separately, will all return not_found. Present it as "not on the register", never as an error.

Errors

Every non-2xx response uses one shape:

{
  "error": {
    "code": "not_found",
    "message": "No such encounter.",
    "request_id": "d719bfc0adbe"
  }
}

Quote request_id when you contact us. It is how we find your exact request in our logs.

HTTPcodeWhen
400invalid_requestThe request could not be read. The message names the fields to check.
401unauthorisedMissing, malformed, unknown or revoked key.
404not_foundNo such encounter, or no such path.
405method_not_allowedWrong method for that path.
413payload_too_largeThe recording is over 90 MB.
429rate_limitedOver your rate limit. Back off and retry. This one is generated before your request is processed, so its request_id is the fixed string rate-limited rather than a traceable reference.
503service_unavailableTemporarily unable to serve. Retry shortly.
500internal_errorA fault at our end. Quote the request id.

Validation failures return 400, not 422. A 404 is returned for an encounter belonging to another account exactly as for one that does not exist, so the API cannot be used to discover whether an identifier is real.

A complete exchange

curl -X POST https://api.ftech.sa/v1/encounters \
  -H "Authorization: Bearer $FTC_API_KEY" \
  -H "Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7" \
  -F "audio=@consultation.m4a" \
  -F "mrn=1140027" \
  -F "patient_sex=Female" \
  -F "patient_age=52"

# 202 Accepted
# {"id":"0b7f3d5a-...","status":"queued","stage":"received","percent":0}

curl https://api.ftech.sa/v1/encounters/0b7f3d5a-... \
  -H "Authorization: Bearer $FTC_API_KEY"

# 200 OK, once status is "ready"
# {"status":"ready","stage":"ready","percent":100,
#  "note":{...},"advisory":{...}}