Integration Key Integration
This guide is for companies that integrate TranscriMed into their own system with a company account. Your backend sends the audio of a consultation or progress note and receives the finished medical document. It can then request corrections to that document, by text or by voice.
Every document and all credit usage belong to your company's account. The API needs no identifying data about the doctor or the patient.
One request is one patient's encounter (for example, one progress note for one bed in one shift). To tie each document to your own record, send your identifier as external_reference_id: it comes back on every response and job read, and you can look jobs up by it. See Correlate jobs with your system.
Overview
- Create an integration key in TranscriMed, with your company account.
- Exchange the key for an access token (valid for 4 hours).
- Send the audio to generate a document, and receive a
jobId. - Poll the job until the document is ready.
- Optional: request a correction. It is saved to the document when the job completes.
Instead of polling, you can send "mode": "sync": the request waits for the job and answers with the document. See Sync or async.
Tablet ──► Your backend ──► TranscriMed API
│ 1. exchange the key for a token
│ 2. send the audio → jobId
│ 3. poll the job → document
The integration key grants access to every document in your account. Never install it on tablets, phones, browsers or any distributed application. The device sends the audio to your backend, and only your backend calls TranscriMed. Keep the key in a secrets vault or an environment variable, never in source code.
Environments
| Environment | Web app | API base URL | Use |
|---|---|---|---|
| Sandbox | https://sandbox.transcrimed.com.br | https://api.sandbox.transcrimed.com.br/api/v1 | Development and integration testing |
| Production | https://transcrimed.com.br | https://api.transcrimed.com.br/api/v1 | Real use |
Getting sandbox access
The sandbox is a separate TranscriMed installation, not a "test mode" of production. TranscriMed provisions access:
- TranscriMed creates your company's sandbox account and sends an e-mail invitation. The sandbox web app and API base URL are in the table above.
- You accept the invitation, set a password and set up two-factor authentication.
- In the sandbox web app, you create your templates and integration keys exactly as in step 1.
- Your backend uses the sandbox key and base URL. Everything else in this guide works the same.
The sandbox account, keys, credits, templates and documents are independent of production. A sandbox key does not work in production, nor the other way round. To go to production, TranscriMed creates the production account the same way, and you create new keys there.
The sandbox is our pre-release environment: every new version goes through it before reaching production. Use synthetic audio, or audio recorded without real patient data. Real patient data belongs in production only.
To save resources, the sandbox's processing stops after a quiet period and starts again on the next request. The first job after a quiet period may stay queued longer than usual while the sandbox wakes up; keep polling within the timeout in step 5. Production does not sleep.
The examples below use the variable BASE_URL.
1. Create the integration key
- Sign in to TranscriMed with your company account.
- Open Organization settings → Integrations → Integration Keys.
- Create a key and name it after the system that will use it (for example,
backend-production). - Copy the key immediately. It starts with
ik_and is shown only once.
You can keep several keys active. To replace a key without interrupting service: create a new one, deploy it to your backend, and only then revoke the old one. A revoked key stops working immediately, including tokens already issued from it.
2. Exchange the key for an access token
POST {BASE_URL}/auth/exchange-integration-key
Content-Type: application/json
{ "integration_key": "ik_..." }
Response 200
{
"success": true,
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 14400,
"refresh_token": "...",
"scope": "medical_records:read medical_records:write ..."
},
"meta": { "requestId": "...", "timestamp": "..." }
}
Send the access_token on every subsequent call:
Authorization: Bearer <access_token>
Refresh the token
The token is valid for 4 hours (expires_in, in seconds). Before it expires, refresh it with the refresh_token:
POST {BASE_URL}/auth/refresh
Content-Type: application/json
{ "refresh_token": "..." }
The response has the same shape as the exchange and carries a new refresh_token: always keep the most recent one. If the refresh fails, exchange the key again.
3. Get the document template ID
Each document is generated from a template in your account (for example, "ICU daily progress note"). Templates are created and edited in TranscriMed. List the account's templates to get their IDs:
GET {BASE_URL}/templates
Authorization: Bearer <access_token>
{ "success": true, "data": { "templates": [ { "id": "6f1c...", "name": "ICU daily progress note", "...": "..." } ] } }
Every generation resolves a template, in this order. No step waits for a person.
- The
template_idyou send. It must be a template of your account; otherwise the request answers400 TEMPLATE_NOT_FOUND, and no job is created and no credit is charged. - Automatic selection from the content of the audio, among your account's templates. An account with a single template always uses it.
- The account's standard template, used only when automatic selection finds no template. You choose it in the web app, on the Modelos (Templates) page, under Modelo padrão (standard template), and you can change or clear it there at any time. Deleting that template clears it.
- Otherwise the job fails with
TEMPLATE_UNRESOLVED, and no credit is charged. Sendtemplate_id, or choose a standard template on the web app's Modelos (Templates) page, and submit again.
To always get the same template, send template_id.
4. Generate a document from audio
POST {BASE_URL}/medical-records/generate
Authorization: Bearer <access_token>
Content-Type: application/json
Idempotency-Key: 5d2f8c4a-7b1e-4f3a-9c6d-2e8b0a4f1c37
X-Request-Id: 0b8f6a1e-3c2d-4e7a-9b1f-5d6c7e8f9a01
{
"audio": "<base64 audio>",
"template_id": "6f1c...",
"mode": "async",
"language": "pt-BR",
"external_reference_id": "bed-12-night-2026-10-21"
}
| Field | Required | Description |
|---|---|---|
audio | yes* | Base64-encoded audio. Use either audio or text. |
text | yes* | Already-transcribed text, instead of audio. |
template_id | see step 3 | Template ID. |
mode | no | async (default) or sync. See Sync or async. |
language | no | Language of the audio. Default: pt-BR. |
external_reference_id | no | Your own identifier for this document, up to 255 characters, never patient-identifying. Returned on every response and job read, and searchable. See Correlate jobs with your system. |
metadata | no | Free-form object, stored with the document. Do not send patient-identifying data. |
clinical_intelligence | no | true (default) or false. With false, only the document is produced: see Document only. |
Idempotency-Key header (required): a value you generate, unique per submission (a UUID is recommended; up to 255 visible ASCII characters). If the request fails or the connection drops, resend the same request with the same key. See Retries and Idempotency-Key.
X-Request-Id header (recommended): a UUID you generate, unique per submission. It identifies the request in support logs and lets you look the job up (see Errors).
Audio: WebM (Opus), M4A/MP4 (AAC), WAV, OGG, FLAC and MP3. We recommend WebM (Opus) or M4A, mono. The request body is limited to 50 MB, which is about 37 MB of audio before base64 encoding.
Response 202
{
"success": true,
"data": {
"jobId": "6f1c2a9e-3b7d-4c51-9a2e-8d4b7f0c1e23",
"status": "queued",
"statusUrl": "https://api.transcrimed.com.br/api/v1/pipeline/jobs/6f1c2a9e-3b7d-4c51-9a2e-8d4b7f0c1e23",
"eventsUrl": "https://api.transcrimed.com.br/api/v1/pipeline/jobs/6f1c2a9e-3b7d-4c51-9a2e-8d4b7f0c1e23/events",
"estimatedCompletionAt": "2026-10-21T15:02:00.000Z",
"external_reference_id": "bed-12-night-2026-10-21"
},
"meta": { "request_id": "...", "timestamp": "..." }
}
patient_id is ignoredThe API does not take a patient identifier: a document belongs to your account, not to a patient record in TranscriMed. A request that sends patient_id is processed normally, the value is not stored anywhere, and the response carries a warning in meta.warnings:
"meta": {
"request_id": "...",
"timestamp": "...",
"warnings": [{ "code": "PATIENT_ID_IGNORED", "message": "patient_id is not supported and was ignored; ..." }]
}
Remove patient_id from your requests, and use external_reference_id (not patient-identifying) to correlate.
Sync or async
Generation and both corrections take mode. Both modes create the same job; only the answer differs.
async(default):202with thejobIdat once, as above. You poll the job (step 5).sync: the request waits for the job, for up to 90 seconds:- Finished in time:
200with the result (below). - Failed: the job's error code, with the HTTP status in the Errors table. Nothing to poll.
- Still running after 90 seconds: exactly the
async202(jobId,statusUrl,eventsUrl). Poll the job as in step 5.
- Finished in time:
Set your HTTP client's timeout above 90 seconds (for example 120 seconds), so that it does not give up before the 202.
We recommend async for long audio. Its processing often takes longer than 90 seconds, and a sync request then waits 90 seconds only to receive the 202.
Response 200 (generation, sync)
{
"success": true,
"data": {
"jobId": "6f1c2a9e-3b7d-4c51-9a2e-8d4b7f0c1e23",
"status": "completed",
"record_id": "b3f0c2de-...",
"document": {
"id": "b3f0c2de-...",
"title": "ICU daily progress note",
"content": "<div>...HTML document...</div>"
},
"clinical_intelligence": { "specialty": "MEDICINA_INTENSIVA", "...": "..." },
"external_reference_id": "bed-12-night-2026-10-21"
},
"meta": { "request_id": "...", "timestamp": "..." }
}
document.contentis the document, as HTML. Storerecord_id: it is the document's ID.clinical_intelligenceis the same object as in the job result (step 5), and is absent when you sentclinical_intelligence: false.
If the connection drops while you wait, resend the same request with the same Idempotency-Key. It attaches to the same job: no second job is created and no second credit is charged. If the job has finished, you receive its result at once; if it is still running, the request waits again, for up to 90 seconds. The mode is not part of the request comparison, so you can also resend an async submission as sync, with the same key, to wait for its result. See Retries and Idempotency-Key.
Document only (clinical_intelligence: false)
By default, TranscriMed also analyses the consultation: it identifies the specialty, encounter type and urgency, and extracts medications, procedures and CID-10 codes, which inform the document and are returned in the job result (see step 5).
If you only need the document, send "clinical_intelligence": false. The analysis steps are skipped and the document is generated from the transcript alone:
- No specialty, encounter type, urgency, medication, procedure or CID-10 code is extracted, stored or returned. The result has no
clinical_intelligenceobject. - Template choice is unchanged: if you omit
template_id, it works as described in step 3. - The document consumes the same credit.
5. Poll the job until the document is ready
GET {BASE_URL}/pipeline/jobs/{jobId}
Authorization: Bearer <access_token>
status moves from queued to in_progress and ends as completed or failed. Poll every 3 to 5 seconds. Audio is usually ready in 1 to 2 minutes; allow a 10-minute timeout before treating it as an error.
Completed
{
"jobId": "6f1c2a9e-3b7d-4c51-9a2e-8d4b7f0c1e23",
"requestId": "...",
"type": "medical_record_generation",
"external_reference_id": "bed-12-night-2026-10-21",
"state": "completed",
"status": "completed",
"progress": { "...": "..." },
"result": {
"requestId": "...",
"result": {
"record_id": "b3f0c2de-...",
"text": {
"title": "ICU daily progress note",
"content": "<div>...HTML document...</div>",
"templateId": "6f1c...",
"succeeded": true
},
"transcript": "...",
"clinical_intelligence": {
"specialty": "MEDICINA_INTENSIVA",
"encounter_type": "follow_up",
"urgency": "urgent",
"medications": [
{ "name": "Noradrenalina", "dosage": "0,1 mcg/kg/min", "route": "IV", "frequency": null, "duration": null }
],
"procedures": [{ "name": "Intubação orotraqueal", "tuss_code": null }],
"cid10": [{ "code": "J96.0", "description": "Insuficiência respiratória aguda", "is_primary": true, "status": "confirmed" }]
}
},
"transcript": "..."
},
"error": null,
"document": {
"id": "b3f0c2de-...",
"title": "ICU daily progress note",
"content": "<div>...HTML document...</div>"
}
}
- The document is in
result.result.text.content, as HTML. A completed generation job also carries it asdocument(id,title,content), the same object as thesyncresponse. - Store
result.result.record_id. It is the document's ID, used to read and correct it later. - If
statusisfailed, the stable error code is inerrorCode(for exampleTEMPLATE_UNRESOLVED) and the description inerror. external_reference_idis the one you sent, in every state (queued,in_progress,completed,failed), ornullif you sent none.typeismedical_record_generation,document_correctionorvoice_correction.
result.result.clinical_intelligence is present unless you sent clinical_intelligence: false. Every field may be null or an empty list when nothing was identified.
| Field | Description |
|---|---|
specialty | Medical specialty identified, as a code (for example CLINICA_MEDICA). |
encounter_type | new_patient, follow_up, procedure, emergency, pre_operative, post_operative or report. |
urgency | routine, priority, urgent or emergency. |
medications | Medications mentioned: name, plus dosage, route, frequency and duration when stated. |
procedures | Procedures mentioned: name, plus tuss_code when it matched the TUSS table. |
cid10 | CID-10 codes: code, description, is_primary and status (confirmed: matched the official CID-10 table; ai_selected: chosen from a shortlist of the official table). Codes that are only suggestions are not returned. |
These are automated extractions for the doctor to review, not a confirmed diagnosis.
The job keeps answering after it finishes: the same GET returns its status, result, document and errorCode later on, too. To read the document as it is now (including saved corrections), use the record_id:
GET {BASE_URL}/medical-records/{record_id}
Authorization: Bearer <access_token>
{ "success": true, "data": { "record": { "id": "b3f0c2de-...", "title": "...", "content": "<div>...</div>", "...": "..." } } }
Instead of polling, you can subscribe to eventsUrl, a Server-Sent Events stream of the job's progress, with the same Authorization header.
6. Correct a document
Correct a document by sending its record_id and the correction, by text or by voice. Both take mode as generation does (Sync or async): with async (default) they return a jobId, which you poll as in step 5; with sync they answer with the corrected document.
By text
POST {BASE_URL}/medical-records/{record_id}/correct-text
Authorization: Bearer <access_token>
Content-Type: application/json
Idempotency-Key: 9a3c1e7f-2b4d-4c8e-a6f0-1d5b7e9c3a20
{ "instructions": "Change the norepinephrine dose to 0.3 mcg/kg/min.", "external_reference_id": "bed-12-night-2026-10-21" }
By voice
POST {BASE_URL}/medical-records/{record_id}/correct
Authorization: Bearer <access_token>
Content-Type: application/json
Idempotency-Key: 4e6b2d8f-1a3c-4b5e-8d7f-0c2a4e6b8d91
{ "audio": "<base64 audio>", "language": "pt-BR" }
Body limit: 10 MB.
Both correction endpoints require the Idempotency-Key header, exactly as generation does. Both accept external_reference_id as generation does: it is returned in the 202, the sync 200 and every read of the correction job.
Correction result
When the correction job finishes (status: "completed"), the correction is already saved to the document:
{
"status": "completed",
"result": {
"success": true,
"recordId": "b3f0c2de-...",
"correctedDocument": "<div>...corrected HTML document...</div>",
"saved": true
}
}
result.savedistrue: the document is now the corrected version. There is no confirmation step.result.correctedDocumentis the saved document, as HTML.GET /medical-records/{record_id}returns the same content.- The next correction starts from this version.
With "mode": "sync", the response 200 carries the same fields:
{
"success": true,
"data": {
"jobId": "b2e94d17-5a60-4f8c-a1d3-7c9e2f4b6a08",
"status": "completed",
"recordId": "b3f0c2de-...",
"correctedDocument": "<div>...corrected HTML document...</div>",
"saved": true,
"external_reference_id": "bed-12-night-2026-10-21"
},
"meta": { "request_id": "...", "timestamp": "..." }
}
A failed sync correction answers its code directly, for example 409 CORRECTION_CONFLICT (see Errors).
A correction is computed from the document as it was when you requested it. If the document changed before the correction was saved, for example because another correction to the same document was saved first, the job fails with CORRECTION_CONFLICT and nothing is saved. Read the document again and request the correction again if it is still needed. To avoid this, wait for a correction job to finish before requesting the next correction to the same document.
Correlate jobs with your system
Send your own identifier as external_reference_id on generation and on corrections. We recommend the identifier of the record in your system that the document fills, for example your per-bed, per-shift progress-note id.
- One request is one patient's encounter. Send one generation per document; do not combine several patients in one audio.
- It must not identify the patient. No name, CPF, medical record number or other patient data: use an opaque id of your own (up to 255 characters). It is stored with the job, as given.
- It is not unique on our side. The same reference can be sent again (for example, a generation and then its corrections, or a resubmission). Each request is still a separate job; only
Idempotency-Keyprevents duplicates.
Where it comes back:
| Response | Field |
|---|---|
202 of generation and corrections | data.external_reference_id |
sync 200 of generation and corrections | data.external_reference_id |
GET /pipeline/jobs/{jobId}, in every state | external_reference_id (null if none was sent) |
GET /pipeline/jobs?external_reference_id=... | external_reference_id of each job |
Look jobs up by your reference
If you lost a jobId (for example, the 202 never arrived and you had not sent an Idempotency-Key), list your account's jobs with that reference:
GET {BASE_URL}/pipeline/jobs?external_reference_id=bed-12-night-2026-10-21&limit=10
Authorization: Bearer <access_token>
{
"external_reference_id": "bed-12-night-2026-10-21",
"limit": 10,
"jobs": [
{ "jobId": "b2e94d17-5a60-4f8c-a1d3-7c9e2f4b6a08", "type": "document_correction", "external_reference_id": "bed-12-night-2026-10-21", "status": "completed", "...": "..." },
{ "jobId": "6f1c2a9e-3b7d-4c51-9a2e-8d4b7f0c1e23", "type": "medical_record_generation", "external_reference_id": "bed-12-night-2026-10-21", "status": "completed", "document": { "...": "..." } }
]
}
- Only your account's jobs are returned, newest first. Each job has the same fields as
GET /pipeline/jobs/{jobId}. limitis optional: 10 by default, at most 50.- An unknown reference returns
"jobs": [].
Retries and Idempotency-Key
Generation (/medical-records/generate) and both corrections (/correct-text, /correct) require an Idempotency-Key header. It makes a retry safe: a resent request never creates a second job, and never charges a second credit.
- Generate a new key for every new submission, and reuse it only to resend that same submission.
- Same key, same request, within 24 hours: the response is the original
202, with the samejobId,statusUrlandeventsUrl. Nothing is processed again. The response carries the headerIdempotent-Replayed: true. Its body is the original one (for example,status: "queued"): pollstatusUrlfor the current state. - The same, in
syncmode: the resend waits on that same job, for up to 90 seconds, and answers as asyncrequest does:200with the result, the job's error code, or the original202if it is still running. It also carriesIdempotent-Replayed: true. - "Same request" means the same endpoint, the same document (for corrections) and the same body fields with the same values (field order does not matter). For audio, what counts is the audio's content, not its encoding.
modedoes not count: it only says how to answer. - Same key, different request:
409IDEMPOTENCY_KEY_REUSED. Use a new key for a new submission. - Same key while the first request is still being accepted:
409IDEMPOTENCY_KEY_IN_PROGRESS. Wait for theRetry-Afterheader (in seconds) and resend with the same key. - A request that failed (for example
402,429,503or5xx): resend it with the same key. If it failed before a job was created, the resend is processed as a new submission. If it failed after the job was created, the resend returns that job (202, samejobId): a key never creates a second job. - If our server stopped while handling the first request, the resend can answer
409IDEMPOTENCY_KEY_IN_PROGRESSfor up to 5 minutes. Keep resending with the same key afterRetry-After: it then returns the job of the first request or, if none was created, processes the submission. - A resent correction is the original correction: it is computed from the document as it was at the first request. If the document changed in the meantime, the job fails with
CORRECTION_CONFLICTand nothing is saved. - After 24 hours the key is forgotten. The same key then starts a new submission.
- Keys belong to your account, not to an integration key: a retry sent with a token from another key of the same account finds the original job.
Errors
There are three error formats, depending on the endpoint.
Generation, correction, jobs, documents and templates:
{ "success": false, "error": { "code": "insufficient_credits", "message": "..." }, "requestId": "...", "timestamp": "..." }
Authentication (/auth/exchange-integration-key and /auth/refresh):
{ "success": false, "error": { "error": "key_revoked", "error_description": "..." }, "meta": { "requestId": "...", "timestamp": "..." } }
Rate limit (HTTP 429):
{ "error": "rate_limited", "error_description": "Too many requests. Please retry after the indicated delay." }
| HTTP | Code | What to do |
|---|---|---|
| 400 | VALIDATION_ERROR, MISSING_AUDIO, INVALID_AUDIO, invalid_request, invalid_key | Fix the request. Do not retry unchanged. |
| 400 | TEMPLATE_NOT_FOUND | The template_id is not a template of your account. List your templates (step 3) and send a valid ID. No job was created. |
| 400 | IDEMPOTENCY_KEY_REQUIRED, IDEMPOTENCY_KEY_INVALID | Send an Idempotency-Key header: 1 to 255 visible ASCII characters, unique per submission. |
| 401 (authentication) | invalid_key, key_inactive, key_revoked, key_expired | The key is no longer valid. Use an active key. |
| 401 (authentication) | invalid_grant | The refresh_token is no longer valid. Exchange the key again. |
| 401 (other) | TOKEN_EXPIRED, TOKEN_INVALID, INVALID_TOKEN, KEY_INVALID, UNAUTHORIZED | Refresh the token. If the key was revoked, exchange an active key. |
| 402 | insufficient_credits | The account has no credits left. Contact TranscriMed. |
| 404 | NOT_FOUND | The document or job does not exist, or does not belong to your account. |
| 409 | IDEMPOTENCY_KEY_REUSED | This Idempotency-Key was used for a different request in the last 24 hours. Use a new key. |
| 409 | IDEMPOTENCY_KEY_IN_PROGRESS | A request with this key is still being accepted. Resend with the same key after Retry-After. |
409 (sync correction) | CORRECTION_CONFLICT, CORRECTION_SUPERSEDED | The correction job failed as described below. Nothing was saved. error.details.jobId names the job. |
422 (sync generation) | TEMPLATE_UNRESOLVED, TEMPLATE_NOT_FOUND | The generation job could not resolve a template (see step 3). No credit was charged. error.details.jobId names the job. |
422 (sync correction) | CORRECTION_NOT_SAVED, NON_MEDICAL_CONTENT, STRUCTURED_CORRECTION_NO_CHANGE, STRUCTURED_CORRECTION_MISSING_DOCUMENT | The correction could not be applied. Nothing was saved. |
500 (sync) | processing_failed, CORRECTION_FAILED, other job codes | The job failed. Resending with the same Idempotency-Key returns the same failed job: to try again, submit with a new key. |
503 (sync correction) | CORRECTION_SAVE_FAILED | Saving failed after the automatic retries. Read the document: if it does not contain the correction, request it again with a new key. |
| — (failed correction job) | CORRECTION_CONFLICT | In error of a failed correction job: the document changed after the correction was requested. Nothing was saved. Read the document and request the correction again if it is still needed. |
| — (failed correction job) | CORRECTION_SUPERSEDED | In error of a failed correction job: a newer correction to the same document was requested before this one was saved. Nothing was saved by this job. |
| — (failed correction job) | CORRECTION_NOT_SAVED | In error of a failed correction job: your account can no longer write the document (for example, it was deleted). Nothing was saved. |
| — (failed correction job) | CORRECTION_SAVE_FAILED | In error of a failed correction job: saving failed after the automatic retries. Read the document: if it does not contain the correction, request it again. |
| 429 | rate_limited, user_generation_limit_reached | Limit reached. Honour the Retry-After header when present, or retry with exponential backoff. |
| 503 | *_capacity_reached, IDEMPOTENCY_STORE_UNAVAILABLE | Momentarily unavailable. Retry with exponential backoff and the same Idempotency-Key. |
| 5xx | — | Server error. Retry with exponential backoff and log the requestId. |
| — (failed job) | TEMPLATE_UNRESOLVED | In errorCode of a failed job: no template could be resolved (see step 3). No credit was charged. Send template_id, or choose a standard template on the web app's Modelos (Templates) page, and submit again. |
jobIdResend the same request with the same Idempotency-Key. If the first one was accepted, you receive its jobId (or, in sync mode, its result) and nothing is generated or charged again. A resend with a new key is a new submission, and generates and charges a second document.
You can also look the job up by the X-Request-Id you sent:
GET {BASE_URL}/pipeline/jobs/by-request/{X-Request-Id}
Authorization: Bearer <access_token>
200: the submission was accepted. The response carriesjobId,requestId,external_reference_id,state,status,progress,resultanderror, but nottype,documentorerrorCode. Take thejobIdand continue by polling as in step 5.404: the submission was never accepted. Resend it with the sameIdempotency-Key.
For corrections, this lookup also finds the job by the external_reference_id you sent with the correction. For any job, you can also look it up by your reference.
Limits and credits
- Credits: each generated document consumes 1 credit from the company account. Corrections do not consume credits.
- Request rate: up to 30 generations and 60 corrections per minute per account. If you need more, contact TranscriMed.
- Keys: the number of active keys depends on the account's plan.
Complete example (Node.js)
const BASE_URL = process.env.TRANSCRIMED_BASE_URL; // sandbox or production
const INTEGRATION_KEY = process.env.TRANSCRIMED_INTEGRATION_KEY;
async function getToken() {
const res = await fetch(`${BASE_URL}/auth/exchange-integration-key`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ integration_key: INTEGRATION_KEY })
});
if (!res.ok) throw new Error(`Exchange failed: ${res.status}`);
return (await res.json()).data.access_token;
}
async function waitForJob(token, jobId, { intervalMs = 4000, timeoutMs = 10 * 60 * 1000 } = {}) {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const res = await fetch(`${BASE_URL}/pipeline/jobs/${jobId}`, {
headers: { Authorization: `Bearer ${token}` }
});
const job = await res.json();
if (job.status === 'completed') return job.result;
if (job.status === 'failed') throw new Error(`Job failed: ${job.errorCode ?? job.error}`);
await new Promise((r) => setTimeout(r, intervalMs));
}
throw new Error('Timed out waiting for the job');
}
async function generateDocument(audioBuffer, templateId, externalId) {
const token = await getToken();
// One key per submission. Store it with the submission and resend with the
// same key if the request fails or the connection drops.
const idempotencyKey = crypto.randomUUID();
const submit = await fetch(`${BASE_URL}/medical-records/generate`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey,
'X-Request-Id': crypto.randomUUID()
},
body: JSON.stringify({
audio: audioBuffer.toString('base64'),
template_id: templateId,
mode: 'async',
external_reference_id: externalId
})
});
if (submit.status !== 202) throw new Error(`Submit failed: ${submit.status} ${await submit.text()}`);
const { jobId } = (await submit.json()).data;
const result = await waitForJob(token, jobId);
return { recordId: result.result.record_id, html: result.result.text.content };
}
async function correctDocument(recordId, instructions) {
const token = await getToken();
const submit = await fetch(`${BASE_URL}/medical-records/${recordId}/correct-text`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID() // reuse it only to resend this correction
},
body: JSON.stringify({ instructions })
});
if (submit.status !== 202) throw new Error(`Submit failed: ${submit.status} ${await submit.text()}`);
const { jobId } = (await submit.json()).data;
// The correction is saved when the job completes. A failed job saved nothing:
// `CORRECTION_CONFLICT` means the document changed; read it and request again if needed.
const result = await waitForJob(token, jobId);
return result.correctedDocument;
}
In production, reuse the token while it is valid and refresh it with /auth/refresh, instead of exchanging the key on every call.
Security checklist
- The integration key exists only on the backend, in a secrets vault.
- Tablets and other devices never receive the key or the token.
- One key per system or environment, with clear names, and immediate revocation of any key suspected of leaking.
- No patient-identifying data in
metadataorexternal_reference_id. - A new
Idempotency-Keyper submission, reused only to resend that submission. - Log the
requestIdof error responses to speed up support.
Support
Contact TranscriMed through your support channel and include the requestId or jobId of the operation.