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
-
Get a Key
An API key with defined scopes and limits is created for your institution. Test the connection with
GET /integration/me. -
Send the Encounter
Send the audio file and the consent declaration with
POST /integration/encounters. The response comes back with 202. -
Track the Status
Query the encounter. While
poll_after_sechas a value, processing is still in progress; use this value (3 s) as the wait interval. -
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_approvedis 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.
| Method | Path | Scope | What It Does |
|---|---|---|---|
| GET | /integration/me | — | Identifies the key and the institution; used to test the connection. |
| POST | /integration/encounters | encounters:write | Creates an encounter from an audio file and consent (202). |
| GET | /integration/encounters | encounters:read | Lists encounters; filter with external_ref and page. |
| GET | /integration/encounters/{id} | encounters:read | Returns the processing status of the encounter. |
| GET | /integration/encounters/{id}/report | reports:read | Returns the report as JSON, including evidence spans. |
| GET | /integration/encounters/{id}/report.docx | reports:read | Returns the report as a Word file. |
| DELETE | /integration/encounters/{id} | encounters:write | Deletes the encounter. An approved encounter cannot be deleted (409 invalid_state). |
Fields Sent When Creating an Encounter
| Field | Required | Description |
|---|---|---|
audio | Yes | Audio file: webm, wav, mp3, m4a, mp4, ogg, opus, flac or aac. |
consent_confirmed | Yes | Declaration that patient consent has been obtained; must be true. The declaration is written to the audit log with source=api. |
external_ref | No | The 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_title | At least one | Fields that identify the encounter. The patient reference is masked in responses. |
template_id | No | ID of the report template to use. If not found, 422 template_not_found. |
subject_type | No | The subject type of the encounter. |
physician_username | No | Username 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.
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"
{
"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"
}
}
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)
# 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"
{
"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
| Limit | Value | If Exceeded |
|---|---|---|
| Request rate | Default: 60 per minute per key (adjustable up to 6000) | 429 rate_limited and a Retry-After header |
| File size | Default 25 MB (adjustable between 1 and 100 MB) | 413 payload_too_large |
| Audio format | webm, wav, mp3, m4a, mp4, ogg, opus, flac, aac | 422 unsupported_audio_type |
| Allowed IP/CIDR | Optional list | 403 ip_not_allowed |
| Expiry | Optional date | 401 api_key_expired |
Error Contract
Errors always use the same JSON format: a detailed error object plus short code and status fields.
{
"error": {
"code": "report_not_approved",
"message": "The report has not yet been approved by the physician.",
"details": {}
},
"code": "report_not_approved",
"status": 409
}
| Status | Code | Meaning |
|---|---|---|
| 400 | consent_required | consent_confirmed=true was not sent. |
| 401 | missing_api_key | The Authorization: Bearer header is missing. |
| 401 | invalid_api_key | The key was not found or its format is invalid. |
| 401 | api_key_revoked | The key has been revoked. |
| 401 | api_key_inactive | The key has been deactivated. |
| 401 | api_key_expired | The key's expiry date has passed. |
| 403 | ip_not_allowed | The request came from outside the key's allowed IP/CIDR list. |
| 403 | insufficient_scope | The key does not have the scope required for this endpoint (details.required_scopes). |
| 403 | tenant_inactive | The institution account is not active. |
| 404 | not_found | The resource does not exist or is outside this key's access. |
| 405 | method_not_allowed | The method is not supported for this path (see the Allow header). |
| 409 | duplicate_external_ref | An encounter with this external_ref already exists (details.encounter_id). |
| 409 | report_not_approved | The report has not yet been approved by the physician; use status=any for the draft. |
| 409 | not_ready | The encounter is still being processed. |
| 409 | processing_failed | The encounter could not be processed (details.error_code). |
| 409 | invalid_state | The state of the encounter does not allow this operation (e.g. an approved encounter cannot be deleted). |
| 413 | payload_too_large | The file exceeds the key's maximum size. |
| 422 | validation_error | Fields are invalid or missing. |
| 422 | unsupported_audio_type | The audio format is not supported. |
| 422 | template_not_found | The template was not found or is not active. |
| 422 | physician_not_found | physician_username was not found or is not active. |
| 429 | rate_limited | The 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?
| Step | Where | Detail |
|---|---|---|
| Audio recording | In the institution's isolated instance | Deleted after 7 days by default (up to 30 days at most, via institution setting). The transcript and report remain. |
| Speech recognition | Local 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-identification | Always, regardless of policy | Names, 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 generation | According to the institution's AI policy | Under 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. |
| Result | In the institution's isolated instance | The 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.