Integration

Connect Ssemora to your systems with the REST API.

External systems such as the HIS (HBYS) can send the audio recording of an encounter to Ssemora, track its processing status, and retrieve the physician-approved report as JSON or Word.

Integration in Four Steps

  1. Get a Key

    An API key with defined scopes and limits is created for your institution. Test the connection with GET /integration/me.

  2. Send the Encounter

    Send the audio file and the consent declaration with POST /integration/encounters. The response comes back with 202.

  3. Track the Status

    Query the encounter. While poll_after_sec has a value, processing is still in progress; use this value (3 s) as the wait interval.

  4. Retrieve the Report

    Once the physician approves, the report can be retrieved as JSON or Word. For a report that has not been approved, 409 report_not_approved is returned.

Endpoints

All paths start with the /api/v1 prefix; the prefix is omitted below. The scope column shows the permission the key must have.

MethodPathScopeWhat It Does
GET/integration/me—Identifies the key and the institution; used to test the connection.
POST/integration/encountersencounters:writeCreates an encounter from an audio file and consent (202).
GET/integration/encountersencounters:readLists encounters; filter with external_ref and page.
GET/integration/encounters/{id}encounters:readReturns the processing status of the encounter.
GET/integration/encounters/{id}/reportreports:readReturns the report as JSON, including evidence spans.
GET/integration/encounters/{id}/report.docxreports:readReturns the report as a Word file.
DELETE/integration/encounters/{id}encounters:writeDeletes the encounter. An approved encounter cannot be deleted (409 invalid_state).

Fields Sent When Creating an Encounter

FieldRequiredDescription
audioYesAudio file: webm, wav, mp3, m4a, mp4, ogg, opus, flac or aac.
consent_confirmedYesDeclaration that patient consent has been obtained; must be true. The declaration is written to the audit log with source=api.
external_refNoThe record number in your own system (up to 200 characters, unique). A second encounter with the same value returns 409 duplicate_external_ref.
file_no, patient_ref, patient_name, subject_titleAt least oneFields that identify the encounter. The patient reference is masked in responses.
template_idNoID of the report template to use. If not found, 422 template_not_found.
subject_typeNoThe subject type of the encounter.
physician_usernameNoUsername of the physician the encounter is assigned to. If not found, 422 physician_not_found.

Examples

In the examples, $SEMORA_URL stands for your institution's address and $SEMORA_API_KEY for your key. Do not put the key in the URL; send it only in the Authorization header.

1. Creating an Encounter (cURL)
curl -X POST "$SEMORA_URL/api/v1/integration/encounters" \
  -H "Authorization: Bearer $SEMORA_API_KEY" \
  -F "audio=@muayene.webm" \
  -F "consent_confirmed=true" \
  -F "external_ref=HBYS-2026-0001" \
  -F "file_no=A-1042"
Response (202): Processing in Progress
{
  "id": "6f1c2d3e-4a5b-6789-abcd-0123456789ab",
  "external_ref": "HBYS-2026-0001",
  "source": "api",
  "status": "processing",
  "progress_percent": null,
  "error_code": null,
  "subject_title": null,
  "file_no": "A-1042",
  "patient_ref_masked": null,
  "created_at": "2026-10-27T09:15:03.120000Z",
  "ended_at": "2026-10-27T09:15:03.101000Z",
  "poll_after_sec": 3,
  "links": {
    "self": "/api/v1/integration/encounters/6f1c2d3e-4a5b-6789-abcd-0123456789ab",
    "report": "/api/v1/integration/encounters/6f1c2d3e-4a5b-6789-abcd-0123456789ab/report",
    "report_docx": "/api/v1/integration/encounters/6f1c2d3e-4a5b-6789-abcd-0123456789ab/report.docx"
  }
}
2. Full Flow (Python)
import os
import time
import requests

BASE = os.environ["SEMORA_URL"]
HEADERS = {"Authorization": "Bearer " + os.environ["SEMORA_API_KEY"]}

with open("muayene.webm", "rb") as audio:
    r = requests.post(
        f"{BASE}/api/v1/integration/encounters",
        headers=HEADERS,
        data={"consent_confirmed": "true", "external_ref": "HBYS-2026-0001", "file_no": "A-1042"},
        files={"audio": audio},
    )
r.raise_for_status()
encounter = r.json()

# While poll_after_sec has a value, the encounter is still being processed.
while encounter["poll_after_sec"]:
    time.sleep(encounter["poll_after_sec"])
    encounter = requests.get(BASE + encounter["links"]["self"], headers=HEADERS).json()

# Until the physician approves, this endpoint returns 409 report_not_approved.
report = requests.get(BASE + encounter["links"]["report"], headers=HEADERS)
print(report.status_code)
3. Retrieving the Report (cURL)
# JSON
curl "$SEMORA_URL/api/v1/integration/encounters/$ENCOUNTER_ID/report" \
  -H "Authorization: Bearer $SEMORA_API_KEY"

# Word
curl "$SEMORA_URL/api/v1/integration/encounters/$ENCOUNTER_ID/report.docx" \
  -H "Authorization: Bearer $SEMORA_API_KEY" \
  -o rapor.docx

# To see the draft before the physician approves
curl "$SEMORA_URL/api/v1/integration/encounters/$ENCOUNTER_ID/report?status=any" \
  -H "Authorization: Bearer $SEMORA_API_KEY"
Report Response (Abridged): Every Field Carries Evidence Spans
{
  "encounter_id": "6f1c2d3e-4a5b-6789-abcd-0123456789ab",
  "external_ref": "HBYS-2026-0001",
  "encounter_status": "approved",
  "report_status": "onaylandi",
  "approved": true,
  "report": {
    "version": 1,
    "status": "onaylandi",
    "fields": {
      "anamnez": {
        "label": "History",
        "content": "Cough for three days…",
        "spans": [
          { "start": 120, "end": 168, "verified": true, "t_start": 12.4, "t_end": 19.0 }
        ],
        "attention": [],
        "edited": false
      }
    }
  },
  "text": "History\nCough for three days…"
}

Each spans item gives the character range of the sentence in the transcript (start, end) and the range in seconds in the audio recording (t_start, t_end). The attention field shows the attention items in that section, and edited shows whether the physician edited it.

Authentication, Scopes and Limits

Authentication

The Authorization: Bearer smk_… header is sent with every request. Keys are created for your institution by Ssemora administration; the name, scopes, per-minute limit, maximum file size, optional expiry and allowed IP/CIDR list are set at creation. The key is shown only once.

Scopes

  • encounters:write — create encounters (audio upload) and delete them.
  • encounters:read — read encounter status and the encounter list.
  • reports:read — read the report as JSON and Word.

Limits

LimitValueIf Exceeded
Request rateDefault: 60 per minute per key (adjustable up to 6000)429 rate_limited and a Retry-After header
File sizeDefault 25 MB (adjustable between 1 and 100 MB)413 payload_too_large
Audio formatwebm, wav, mp3, m4a, mp4, ogg, opus, flac, aac422 unsupported_audio_type
Allowed IP/CIDROptional list403 ip_not_allowed
ExpiryOptional date401 api_key_expired

Error Contract

Errors always use the same JSON format: a detailed error object plus short code and status fields.

Example Error (409)
{
  "error": {
    "code": "report_not_approved",
    "message": "The report has not yet been approved by the physician.",
    "details": {}
  },
  "code": "report_not_approved",
  "status": 409
}
StatusCodeMeaning
400consent_requiredconsent_confirmed=true was not sent.
401missing_api_keyThe Authorization: Bearer header is missing.
401invalid_api_keyThe key was not found or its format is invalid.
401api_key_revokedThe key has been revoked.
401api_key_inactiveThe key has been deactivated.
401api_key_expiredThe key's expiry date has passed.
403ip_not_allowedThe request came from outside the key's allowed IP/CIDR list.
403insufficient_scopeThe key does not have the scope required for this endpoint (details.required_scopes).
403tenant_inactiveThe institution account is not active.
404not_foundThe resource does not exist or is outside this key's access.
405method_not_allowedThe method is not supported for this path (see the Allow header).
409duplicate_external_refAn encounter with this external_ref already exists (details.encounter_id).
409report_not_approvedThe report has not yet been approved by the physician; use status=any for the draft.
409not_readyThe encounter is still being processed.
409processing_failedThe encounter could not be processed (details.error_code).
409invalid_stateThe state of the encounter does not allow this operation (e.g. an approved encounter cannot be deleted).
413payload_too_largeThe file exceeds the key's maximum size.
422validation_errorFields are invalid or missing.
422unsupported_audio_typeThe audio format is not supported.
422template_not_foundThe template was not found or is not active.
422physician_not_foundphysician_username was not found or is not active.
429rate_limitedThe per-minute request limit was exceeded (Retry-After).

Connecting to the HIS

In Turkey, every hospital's HIS is different. For this reason, no single shared HIS connection is built into Ssemora: for each institution, a connector (adapter) specific to that institution's system is written and selected in the institution settings. The connector's credentials are kept in the institution settings and are not embedded in the code.

The connector interface defines patient lookup and report saving operations. The design rules are: the same request is never sent twice, partial success is treated as normal, and a slowdown in an HIS must not lock up the application.

Current status. We do not publish a ready-made HIS connector; there is only a sample connector that illustrates the interface and its tests. Which HIS to target, access details and the test environment are planned together with the institution. Integration is also possible without waiting for a connector: your HIS sends the encounter through the API and retrieves the approved report.

No data is sent to the HIS without physician approval.

Security Architecture

The security features of the API, from the key level to the data level.

Key Security

  • The key is shown only once, at creation; Ssemora stores only its SHA-256 digest.
  • Scope, optional expiry and the allowed IP/CIDR list are defined per key.
  • The per-minute request limit applies per key.

Access Boundary

  • A key can only touch encounters that it created through the API.
  • By default, the report endpoints return only the physician-approved report.
  • The patient reference is masked in responses.

Traceability

  • The consent declaration is mandatory and is written to the audit log with source=api.
  • The API call log is kept for 30 days; it contains neither the request body nor patient data.

Institution Isolation

  • Each institution runs in its own isolated instance and database; no data is shared between institutions.
  • A key is bound to a single institution; it cannot access another institution's data.

Data Processing

At which step, and where, is an encounter's data processed?

StepWhereDetail
Audio recordingIn the institution's isolated instanceDeleted after 7 days by default (up to 30 days at most, via institution setting). The transcript and report remain.
Speech recognitionLocal by default (faster-whisper)Raw audio is not sent to the AI provider. Cloud speech recognition can be used only under the "cloud-allowed" policy and with explicit approval.
De-identificationAlways, regardless of policyNames, Turkish national ID numbers, file numbers, phone numbers, e-mail addresses, addresses, institution names and exact dates are converted to placeholders. Age, sex, clinical content and relative durations such as "for 3 days" are preserved. The mapping table is held in memory only for the duration of a single request; it is not written to disk, logs or cache.
Report and suggestion generationAccording to the institution's AI policyUnder the cloud-allowed and de-identified-only policies, de-identified text may be sent to cloud AI providers. Under the local-model-only policy, all stages run on the local model.
ResultIn the institution's isolated instanceThe report, transcript and evidence spans are stored; approvals, deletions and setting changes are written to the audit log.

Residual risk. Even when direct identifiers are removed, the risk of indirect identifiability in very small patient populations (a rare disease, a unit that sees few patients) cannot theoretically be reduced to zero. For this reason, the AI policy is selected at the institution level; only under the local-model-only policy does the text never leave the institution. This page is a technical summary; it does not replace contract and disclosure texts.

Let's Plan the Integration Together

Tell us about your system, the flow you are aiming for and your constraints; we will determine the best path together.