SHOMA AI Health API — Important Notice
This API is an administrative support tool intended exclusively for authorised staff of hospitals and clinics. It helps structure and organise clinical documents to reduce administrative workload.
- It does not provide medical advice, diagnoses or treatment recommendations.
- It is not clinical decision support and is not intended as a medical device.
- All clinical decisions remain the sole responsibility of the licensed healthcare professional.
- The platform operates under a strict zero-retention policy: clinical data is permanently deleted immediately after the temporary report is generated and is never stored or used for training.
By using this API you confirm that you understand and accept these limitations.
01 · Getting started
Overview
The SHOMA Health API securely processes clinical documents for authorised hospital and clinic staff. Files are envelope-encrypted in transit, PII/PHI is redacted before model processing, and results come back as a temporary, structured administrative report. Nothing is retained afterwards.
What you can do
/api/v1/analysisStructure 1–3 encrypted clinical documents into an administrative report/api/v1/auth/public-keyFetch the RSA public key used to wrap your AES session keyLimits at a glance
Base URL & response modes
| Item | Value | Notes |
|---|---|---|
| Base URL | https://api.health.shoma-ai.com | All paths are prefixed with /api/v1. |
| Payload format | multipart/form-data | Required for /analysis. Binary file parts plus form string fields. |
| Default response | application/json | One complete JSON envelope when isStreamingResponse=false. |
| Streaming response | text/event-stream | Server-Sent Events when isStreamingResponse=true. |
| Output language | auto | The engine detects the document language and replies in it; falls back to English. Reported as responseLanguage. |
02 · Getting started
Quickstart
Four steps from a fresh account to a structured report.
- 1
Get your API key
Create a key on the API Keys page and send it as the X-API-Key header on every request.
- 2
Point at the base URL
https://api.health.shoma-ai.com/api/v1
- 3
Encrypt your document
Documents must be envelope-encrypted before upload — see Encryption for the full flow.
- 4
Make your first request
Call POST /api/v1/analysis — a complete, runnable example is in Code examples.
Create your key on the API Keys page.
03 · Getting started
Authentication
The analysis endpoint accepts three authentication schemes. Server-to-server integrations should use the API key.
| Scheme | Header / carrier | Intended use |
|---|---|---|
| API key | X-API-Key: sk-shoma-… | Server-to-server integrations. The primary scheme for this API. |
| Bearer token | Authorization: Bearer … | OAuth2 session token issued to a signed-in user. |
| Session cookie | access_token (HttpOnly) | Browser sessions inside the SHOMA dashboard and playground. |
| Public key endpoint | — | GET /api/v1/auth/public-key requires no authentication. |
# Fetch the RSA public keycurl https://api.health.shoma-ai.com/api/v1/auth/public-key# Authenticate an analysis requestcurl -X POST https://api.health.shoma-ai.com/api/v1/analysis \-H "X-API-Key: YOUR_API_KEY" \-F "encryptedAesKey=<base64-rsa-wrapped-aes-key>" \-F "files=@document.enc"
Key lifecycle
| Topic | Behaviour |
|---|---|
| Rotation | Rotating a key immediately invalidates the old credential and generates a new one. Update the key in your integrations before rotating — there is no grace period. |
| Enable / disable | Disabling a key temporarily blocks all API traffic for that key and returns AKEY004. Enabling restores access instantly using the same key value. |
| Multiple keys | Basic: 1 key. Pro: up to 10 keys. Enterprise: unlimited — use separate keys to isolate testing and production workloads. |
| Test vs production | There is no separate sandbox environment. Isolation is key-based only — issue a distinct key per workload and track usage per key. |
| Expiration | Keys never expire on a timer. They remain valid indefinitely until you rotate or delete them — so treat a leaked key as live until you act on it. |
04 · Getting started
Versioning
The API is versioned in the URL path. The version segment is mandatory — there is no unversioned route.
https://api.health.shoma-ai.com/api/v1/analysis└── version segment
| Rule | Policy |
|---|---|
| Selecting a version | Set it in the path (/api/v1/…). No header or query parameter is used. |
| Breaking changes | Ship only in a new major version (/api/v2). v1 keeps its contract. |
| Additive changes | New optional fields and new response keys may appear within v1. Clients must ignore unknown keys. |
| Status labels | Each version carries a status in the changelog. v1 is Current and nothing is deprecated today. |
05 · Uploads
Supported file formats & limits
Each request carries between one and three documents. Every file is encrypted client-side before it leaves your machine.
| Format | Extension | Typical use |
|---|---|---|
.pdf | Lab reports, discharge letters, referrals | |
| Word | .docx | Clinical notes and dictated summaries |
| JPEG | .jpg · .jpeg | Photographed or scanned paper documents |
| PNG | .png | Screenshots and exported images |
| WebP | .webp | Compressed images exported from web tools |
.dcm file will fail server-side after upload, so screen uploads in your own client before encrypting.john_doe_report.pdf; use a generic name like document.enc.06 · Uploads
Encrypting your documents
RSA can't encrypt a file directly (~200-byte limit), so you encrypt the document with a one-time AES key and wrap that key with our RSA public key. This is envelope encryption.
- 1
Fetch the RSA public key
Call GET /api/v1/auth/public-key to get the server's public key (PEM).
- 2
Generate a random AES-256 key
Create a random 32-byte key, used for this upload only.
- 3
Encrypt the document with AES-256-GCM
Encrypt the file bytes and prepend the 12-byte IV to the ciphertext.
- 4
Wrap the AES key with RSA
Encrypt the AES key using RSA-OAEP (SHA-256), then base64-encode it.
- 5
Upload
POST to /api/v1/analysis as multipart form data: the encrypted files, the encryptedAesKey field, and an optional message if you want to add context.
- 6
Server decrypts & discards
The backend unwraps the AES key with its private key, decrypts the document in memory, processes it, then clears it.
encryptedAesKey.07 · Reference
API reference
Full parameters, headers, request and response schemas for both endpoints.
/api/v1/auth/public-keyRetrieve the RSA-2048 public key in PEM format to encrypt session keys client-side before sending medical files to the analysis pipeline. Authentication: none (public).
Request
GET /api/v1/auth/public-key HTTP/1.1Host: api.health.shoma-ai.com
Response schema — 200 OK
| Field | Type | Description |
|---|---|---|
status | integer | Mirrors the HTTP status code. |
message | string | Human-readable outcome. |
errorCode | string | SUC000 on success; an ERR*/AUTH* code otherwise. |
data.publicKey | string | RSA-2048 public key, PEM encoded, newlines escaped as \n. |
Example response
{"status": 200,"message": "Public key retrieved successfully.","errorCode": "SUC000","data": {"publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n-----END PUBLIC KEY-----"}}
/api/v1/analysisCore document analysis pipeline. Accepts 1–3 envelope-encrypted documents and returns a temporary structured administrative report, as one JSON body or as a Server-Sent Events stream.
Headers
| Header | Required | Description |
|---|---|---|
X-API-Key | one of three | Your API key. Preferred for server-to-server calls. |
Authorization | one of three | Bearer <token> — OAuth2 session token. |
Cookie | one of three | HttpOnly access_token session cookie. |
Content-Type | yes | multipart/form-data (set automatically by most HTTP clients) |
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
encryptedAesKey | string | required | Base64 of the AES-256 session key wrapped with RSA-OAEP (SHA-256). |
files | file[] | 1–3 required | Each file AES-256-GCM encrypted, 12-byte IV prepended. Repeat the field for multiple files. |
message | string | optional | Free-text clinical context or a specific question sent along with the documents. |
isStreamingResponse | boolean | optional · default false | "true" streams pipeline progress as Server-Sent Events instead of one JSON response. |
Request payload
POST /api/v1/analysis HTTP/1.1Host: api.health.shoma-ai.comX-API-Key: YOUR_API_KEYContent-Type: multipart/form-dataencryptedAesKey: <base64 RSA-OAEP-wrapped AES key>files: document.enc # binary, repeat for up to 3message: Patient presents with acute chest pain...isStreamingResponse: true # optional
Response schema — 200 OK
| Field | Type | Description |
|---|---|---|
data.medicalInsights | string | Narrative administrative summary of the submitted documents. |
data.extractedEntities | object | Structured extraction: symptoms, medicalHistory, statedConditions, medications[], labValues[]. |
data.completenessScore | number | How complete the source documentation appeared, 0–100. |
data.conflictFlags | string[] | Documentation inconsistencies noted between records. |
data.suggestions | string[] | Administrative follow-up items extracted from the documents. |
data.visitTrends | object[] | Parameter values over time: date, parameter, value. |
data.highlights | string[] | Short headline points from the report. |
data.disclaimer | string | Mandatory AI-output notice. Must be surfaced to the end user verbatim. |
data.analysisScore | number | Overall confidence in the structuring pass. |
data.scoreComputationStatus | string | success when scoring completed. |
data.creditsUsed | integer | Credits deducted for this request. Always 1 — a flat rate regardless of file count. |
data.inputTokens · outputTokens | integer | Token counts consumed by the model pass. Recorded for auditing only; they do not affect billing. |
data.effortScoring | object | rawReadingTimeMinutes, assistedReviewTimeMinutes, effortReductionPercentage, basis. |
data.responseLanguage | string | Language the report was written in, detected from the documents. |
A complete example body is in Success responses.
08 · Reference
Code examples
End-to-end: fetch the public key, encrypt, upload, handle the response. No SDK required — this is a plain REST API.
# End-to-end: fetch key -> encrypt -> upload -> read responseimport os, base64, requestsfrom cryptography.hazmat.primitives import hashes, serializationfrom cryptography.hazmat.primitives.asymmetric import paddingfrom cryptography.hazmat.primitives.ciphers.aead import AESGCMBASE = "https://api.health.shoma-ai.com/api/v1"HEADERS = {"X-API-Key": os.environ["SHOMA_API_KEY"]}# 1. Fetch the RSA public key (no auth required)pem = requests.get(f"{BASE}/auth/public-key").json()["data"]["publicKey"]public_key = serialization.load_pem_public_key(pem.encode())# 2. Generate a one-time AES-256 keyaes_key = AESGCM.generate_key(bit_length=256)# 3. Encrypt the document: 12-byte IV prepended to the ciphertextiv = os.urandom(12)plaintext = open("report.pdf", "rb").read()encrypted_payload = iv + AESGCM(aes_key).encrypt(iv, plaintext, None)# 4. Wrap the AES key with RSA-OAEP (SHA-256), then base64wrapped = public_key.encrypt(aes_key,padding.OAEP(padding.MGF1(hashes.SHA256()), hashes.SHA256(), None),)encrypted_aes_key_b64 = base64.b64encode(wrapped).decode()# 5. Upload (1-3 files, all sharing this one AES key)resp = requests.post(f"{BASE}/analysis",headers=HEADERS,data={"encryptedAesKey": encrypted_aes_key_b64,"message": "Patient presents with acute chest pain radiating to left arm...","isStreamingResponse": "false",},files={"files": ("document.enc", encrypted_payload, "application/octet-stream")},)# 6. Handle the responsebody = resp.json()if resp.status_code != 200:raise RuntimeError(f"{body['errorCode']}: {body['message']}")report = body["data"]print(report["medicalInsights"])print("credits used:", report["creditsUsed"])print(report["disclaimer"]) # must be shown to the end user verbatim
09 · Reference
Streaming responses (SSE)
Send isStreamingResponse=true to receive Server-Sent Events as the pipeline runs. Read the fetch body stream directly — the native EventSource only supports GET.
Event types
| type | When it fires | Payload |
|---|---|---|
pipeline_start | Once, when processing begins. | Run metadata. |
step | Once per phase: decryption, extraction, redaction, llm_analysis, billing, audit_log. | Step name and state. |
analysis_complete | Once, on success. | The full report in data, identical to the JSON body. |
error | On failure, at any point. The stream then closes. | status, message, errorCode. |
Consuming the stream
// Set isStreamingResponse=true to receive Server-Sent Events.// Read the fetch body directly — the browser EventSource is GET-only.const res = await fetch("https://api.health.shoma-ai.com/api/v1/analysis", {method: "POST", headers, body: form});const reader = res.body.getReader();const decoder = new TextDecoder();let buffer = "";while (true) {const { done, value } = await reader.read();if (done) break;buffer += decoder.decode(value, { stream: true });for (const block of buffer.split("\n\n")) {const line = block.split("\n").find(l => l.startsWith("data:"));if (!line) continue;const event = JSON.parse(line.slice(5).trim());switch (event.type) {case "pipeline_start": console.log("started"); break;case "step": console.log("step:", event.data.step); break;case "analysis_complete": console.log(event.data); break;case "error": throw new Error(`${event.errorCode}: ${event.message}`);}}}
Example event sequence
data: {"type":"pipeline_start","data":{"files":2}}data: {"type":"step","data":{"step":"decryption","status":"completed"}}data: {"type":"step","data":{"step":"extraction","status":"completed"}}data: {"type":"step","data":{"step":"redaction","status":"completed"}}data: {"type":"step","data":{"step":"llm_analysis","status":"completed"}}data: {"type":"analysis_complete","data":{ /* full report — see Success responses */ }}
Streaming error event
Errors raised after the stream has opened arrive as an error event, not as an HTTP status — the response is already 200 with text/event-stream. Always branch on event.type.
data: {"type":"step","data":{"step":"decryption","status":"completed"}}data: {"type": "error","status": 400,"message": "LLM returned an invalid JSON response schema. Please try again.","errorCode": "ERR400","data": null}
10 · Reference
Success responses
Every response — success or failure — uses the same envelope: status, message, errorCode, data. A success always carries errorCode: "SUC000".
/analysis:"AI administrative processing complete. Temporary report ready for professional review. Data will be permanently deleted immediately."
Complete example — POST /api/v1/analysis
{"status": 200,"message": "AI administrative processing complete. Temporary report ready for professional review. Data will be permanently deleted immediately.","errorCode": "SUC000","data": {"medicalInsights": "The patient exhibits chronic stable blood pressure with mild fluctuations. The general indicators suggest high adherence to the primary therapeutic routine with no critical safety alerts flagged.","extractedEntities": {"symptoms": ["Mild headache", "occasional dizziness"],"medicalHistory": ["Essential hypertension diagnosed in 2021", "Hyperlipidemia"],"statedConditions": ["Chronic stable hypertension"],"medications": [{"name": "Lisinopril","dose": "10mg","frequency": "Once daily in the morning"},{"name": "Atorvastatin","dose": "20mg","frequency": "Once daily at night"}],"labValues": [{"parameter": "Blood Pressure","value": "135/85","unit": "mmHg","referenceRange": "120/80"},{"parameter": "Total Cholesterol","value": "195","unit": "mg/dL","referenceRange": "< 200"}]},"completenessScore": 92,"conflictFlags": ["No significant clinical conflicts identified between documentation records."],"suggestions": ["Maintain active low-sodium dietary regimen.","Monitor blood pressure twice daily (morning and evening).","Follow up in 4 weeks for laboratory reassessment."],"visitTrends": [{"date": "2026-08-01","parameter": "Systolic BP","value": "138"},{"date": "2026-08-10","parameter": "Systolic BP","value": "135"}],"highlights": ["Target blood pressure goals are being approached.","Lipid levels are controlled within references."],"disclaimer": "IMPORTANT NOTICE — This is an AI-generated administrative output only. It does not constitute medical advice, a diagnosis or clinical decision support. You must review and validate all content before any clinical use. No patient data is stored by SHOMA AI after this report is generated.","analysisScore": 90.0,"scoreComputationStatus": "success","creditsUsed": 1,"inputTokens": 1420,"outputTokens": 320,"effortScoring": {"rawReadingTimeMinutes": 15.0,"assistedReviewTimeMinutes": 3.0,"effortReductionPercentage": 80,"basis": "Quantified duration comparison between typical manual reading/extraction of multiple documents and reviewing structural AI outputs."},"responseLanguage": "English"}}
disclaimer field is mandatory. It must be displayed verbatim to any end user who sees the report. Do not truncate, paraphrase or hide it.11 · Reference
Errors & status codes
Errors return the same envelope as successes, with data: null (or a validation detail array) and a machine-readable errorCode. Branch on errorCode, not on the message text — messages may be reworded.
HTTP status codes
| Status | Meaning | Typical causes |
|---|---|---|
| 200 | OK | Processing complete, or an SSE stream opened. |
| 400 | Bad Request | AUTH002 · ANA001 · ERR400 · ERR500 |
| 401 | Unauthorized | AKEY003 · AKEY004 · AUTH003 |
| 403 | Forbidden | AUTH008 · ERR403 · ANA003 |
| 422 | Unprocessable Entity | AUTH002 — form-field validation failure. |
| 500 | Internal Server Error | ERR500 · ANA002 |
| 502 | Bad Gateway | Upstream AI provider failure. |
| 503 | Service Unavailable | Provider temporarily unreachable — retry with backoff. |
Error code index
| Code | HTTP | Meaning | What to do |
|---|---|---|---|
SUC000 | 200 | Success | — |
AUTH002 | 400 · 422 | Invalid file count, file over 10 MB, or form validation failure | Fix the request. Do not retry unchanged. |
AUTH003 | 401 | Session logged out or revoked | Re-authenticate the user. |
AUTH008 | 403 | Account not permitted: email unverified, org pending approval, or subscription expired | Surface the message to the account owner. |
AKEY003 | 401 | Missing or invalid API key | Check the X-API-Key header. |
AKEY004 | 401 | API key deactivated | Re-enable or rotate the key. |
ANA001 | 400 | Patient name does not match the registered user | Individual accounts only. Verify the document. |
ANA002 | 500 · 502 · 503 | AI service provider error | Retry with exponential backoff. |
ANA003 | 403 | Free trial rate limit exceeded | Wait for the window stated in the message, or upgrade. |
ERR400 | 400 | Model returned an invalid JSON schema | Retry once; no credits are deducted on a schema failure. |
ERR403 | 403 | Credit limit reached | Purchase add-on credits or upgrade the plan. |
ERR500 | 400 · 500 | Server misconfiguration or unhandled internal error | Retry; if persistent, contact support. |
Error catalogue — GET /api/v1/auth/public-key
400ERR500Public key not configured+
The server is missing the RSA public key configuration settings.
Public key is not configured on the server.
{"status": 400,"message": "Public key is not configured on the server.","errorCode": "ERR500","data": null}
Error catalogue — POST /api/v1/analysis
400AUTH002Invalid file count+
Fewer than 1 or more than 3 files were uploaded.
You must upload between 1 and 3 files.
{"status": 400,"message": "You must upload between 1 and 3 files.","errorCode": "AUTH002","data": null}
400AUTH002File too large+
One of the uploaded files exceeds the 10 MB per-file limit. The check runs per file, so a request of three files fails if any single one is over the limit.
File size exceeds the limit of 10 MB.
{"status": 400,"message": "File size exceeds the limit of 10 MB.","errorCode": "AUTH002","data": null}
Raised as BadRequestException(ErrorMessage.FILE_TOO_LARGE) with no explicit code, so it falls back to that exception's default of AUTH002.
400ANA001Patient name mismatch+
The patient's name does not match the registered user name. Enforced strictly on individual account types.
Patient name in the document does not match the registered user's name.
{"status": 400,"message": "Patient name in the document does not match the registered user's name.","errorCode": "ANA001","data": null}
400ERR400Invalid LLM response format+
The AI engine failed to return a valid structured JSON output matching the required schema.
LLM returned an invalid JSON response schema. Please try again.
{"status": 400,"message": "LLM returned an invalid JSON response schema. Please try again.","errorCode": "ERR400","data": null}
401AKEY003 / AUTH003Missing or invalid API key / auth token+
The API key header (X-API-Key) or OAuth2 session token is missing or invalid. The code returned depends on the authorization scheme used.
Invalid or missing API key. Provide a valid key in the X-API-Key header.
Missing authentication token.
{"status": 401,"message": "Invalid or missing API key. Provide a valid key in the X-API-Key header.","errorCode": "AKEY003","data": null}
401AKEY004API key deactivated+
The API key passed in X-API-Key has been deactivated or deleted by the user.
This API key has been deactivated. Please generate or rotate your key.
{"status": 401,"message": "This API key has been deactivated. Please generate or rotate your key.","errorCode": "AKEY004","data": null}
401AUTH003Session revoked+
The browser cookie or bearer token session was logged out or revoked.
Session has been logged out or revoked.
{"status": 401,"message": "Session has been logged out or revoked.","errorCode": "AUTH003","data": null}
403AUTH008Email not verified+
The user's account has not completed the email verification step.
Email is not verified. Access denied.
{"status": 403,"message": "Email is not verified. Access denied.","errorCode": "AUTH008","data": null}
403AUTH008Organization access pending approval+
The organization profile or legal agreements are pending administrator approval.
Access pending. Organization profile and agreements must be approved by admin.
{"status": 403,"message": "Access pending. Organization profile and agreements must be approved by admin.","errorCode": "AUTH008","data": null}
403AUTH008Subscription expired / no active plan+
The subscription has expired or the user does not have an active plan.
Subscription or Free Trial has expired. Please upgrade to a paid plan to restore access.
No active plan. Please subscribe to a plan to access the API.
{"status": 403,"message": "Subscription or Free Trial has expired. Please upgrade to a paid plan to restore access.","errorCode": "AUTH008","data": null}
403ERR403Credit limit reached+
The remaining balance in the user's active credit ledger buckets is zero.
Processing limit reached. No data has been retained. Please contact your administrator or purchase additional credits.
{"status": 403,"message": "Processing limit reached. No data has been retained. Please contact your administrator or purchase additional credits.","errorCode": "ERR403","data": null}
403ANA003Free trial rate limit exceeded+
Free trial accounts are limited to a maximum of 1 successful analysis per 24 hours. The message interpolates the remaining wait.
Free trial limits to 1 successful analysis per 24 hours. You can perform your next analysis in {hours}h {minutes}m.
{"status": 403,"message": "Free trial limits to 1 successful analysis per 24 hours. You can perform your next analysis in 23h 45m.","errorCode": "ANA003","data": null}
422AUTH002Request validation failure+
Form data fields do not match the expected structural requirements — for example encryptedAesKey is missing. Unlike other errors, data carries an array of per-field details.
Validation failed: body.encryptedAesKey: field required
{"status": 422,"message": "Validation failed: body.encryptedAesKey: field required","errorCode": "AUTH002","data": [{"type": "missing","loc": ["body", "encryptedAesKey"],"msg": "Field required","input": null}]}
500 · 502 · 503ERR500 / ANA002LLM provider or server error+
General fallback when the LLM service fails or an unhandled traceback is logged. Safe to retry with exponential backoff.
An internal server error occurred.
An error occurred with the AI service provider while processing your request. Please try again later.
{"status": 500,"message": "An internal server error occurred.","errorCode": "ERR500","data": null}
12 · Operations
Rate limits & credits
Access is governed by two independent mechanisms: a rate limit on how often you may call, and a credit balance for how much you may process.
Rate limits
| Account type | Limit | On exceed |
|---|---|---|
| Free trial | 1 successful analysis per 24 hours, and 3 credits in total | 403 · ANA003 with the remaining wait in the message. |
| Paid plans | No rate limit | Access is governed solely by a positive, active credit balance. There is no per-second, per-minute or per-day request cap. |
How credits are consumed
- Every successful call to a SHOMA AI processing endpoint deducts 1 credit. The amount is echoed back in data.creditsUsed.
- inputTokens and outputTokens are recorded for metadata and auditing only. They do not affect what you are charged.
- The free trial grants 3 credits in total, spendable at no more than one analysis per 24 hours.
- Plan credits reset to the plan limit at the start of each billing cycle and do not roll over.
- Add-on credit packs never expire and are consumed only after the monthly plan credits are fully exhausted.
- A request that fails before the model runs — validation, authentication, decryption — does not deduct credits.
When credits run out
403 · ERR403 with the message "Processing limit reached. No data has been retained. Please contact your administrator or purchase additional credits." No document is processed and nothing is retained. Access is restored immediately once an add-on credit pack is purchased or the plan is upgraded.API key quotas by plan
13 · Operations
Changelog & release notes
What changed in each API version — expand a version to see additions, changes and deprecations, and plan your migration.
