Skip to main content

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​

  1. Create an integration key in TranscriMed, with your company account.
  2. Exchange the key for an access token (valid for 4 hours).
  3. Send the audio to generate a document, and receive a jobId.
  4. Poll the job until the document is ready.
  5. 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 key stays on your backend

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​

EnvironmentWeb appAPI base URLUse
Sandboxhttps://sandbox.transcrimed.com.brhttps://api.sandbox.transcrimed.com.br/api/v1Development and integration testing
Productionhttps://transcrimed.com.brhttps://api.transcrimed.com.br/api/v1Real use

Getting sandbox access​

The sandbox is a separate TranscriMed installation, not a "test mode" of production. TranscriMed provisions access:

  1. 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.
  2. You accept the invitation, set a password and set up two-factor authentication.
  3. In the sandbox web app, you create your templates and integration keys exactly as in step 1.
  4. 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.

Use only fictitious data in the sandbox

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.

The sandbox sleeps when idle

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​

  1. Sign in to TranscriMed with your company account.
  2. Open Organization settings → Integrations → Integration Keys.
  3. Create a key and name it after the system that will use it (for example, backend-production).
  4. 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", "...": "..." } ] } }
How the template is chosen

Every generation resolves a template, in this order. No step waits for a person.

  1. The template_id you send. It must be a template of your account; otherwise the request answers 400 TEMPLATE_NOT_FOUND, and no job is created and no credit is charged.
  2. Automatic selection from the content of the audio, among your account's templates. An account with a single template always uses it.
  3. 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.
  4. Otherwise the job fails with TEMPLATE_UNRESOLVED, and no credit is charged. Send template_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"
}
FieldRequiredDescription
audioyes*Base64-encoded audio. Use either audio or text.
textyes*Already-transcribed text, instead of audio.
template_idsee step 3Template ID.
modenoasync (default) or sync. See Sync or async.
languagenoLanguage of the audio. Default: pt-BR.
external_reference_idnoYour 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.
metadatanoFree-form object, stored with the document. Do not send patient-identifying data.
clinical_intelligencenotrue (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 ignored

The 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): 202 with the jobId at once, as above. You poll the job (step 5).
  • sync: the request waits for the job, for up to 90 seconds:
    • Finished in time: 200 with 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 async 202 (jobId, statusUrl, eventsUrl). Poll the job as in step 5.

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.content is the document, as HTML. Store record_id: it is the document's ID.
  • clinical_intelligence is the same object as in the job result (step 5), and is absent when you sent clinical_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_intelligence object.
  • 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 as document (id, title, content), the same object as the sync response.
  • Store result.result.record_id. It is the document's ID, used to read and correct it later.
  • If status is failed, the stable error code is in errorCode (for example TEMPLATE_UNRESOLVED) and the description in error.
  • external_reference_id is the one you sent, in every state (queued, in_progress, completed, failed), or null if you sent none. type is medical_record_generation, document_correction or voice_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.

FieldDescription
specialtyMedical specialty identified, as a code (for example CLINICA_MEDICA).
encounter_typenew_patient, follow_up, procedure, emergency, pre_operative, post_operative or report.
urgencyroutine, priority, urgent or emergency.
medicationsMedications mentioned: name, plus dosage, route, frequency and duration when stated.
proceduresProcedures mentioned: name, plus tuss_code when it matched the TUSS table.
cid10CID-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>", "...": "..." } } }
Follow progress in real time

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.saved is true: the document is now the corrected version. There is no confirmation step.
  • result.correctedDocument is 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).

One correction at a time per document

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-Key prevents duplicates.

Where it comes back:

ResponseField
202 of generation and correctionsdata.external_reference_id
sync 200 of generation and correctionsdata.external_reference_id
GET /pipeline/jobs/{jobId}, in every stateexternal_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}.
  • limit is 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 same jobId, statusUrl and eventsUrl. Nothing is processed again. The response carries the header Idempotent-Replayed: true. Its body is the original one (for example, status: "queued"): poll statusUrl for the current state.
  • The same, in sync mode: the resend waits on that same job, for up to 90 seconds, and answers as a sync request does: 200 with the result, the job's error code, or the original 202 if it is still running. It also carries Idempotent-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. mode does not count: it only says how to answer.
  • Same key, different request: 409 IDEMPOTENCY_KEY_REUSED. Use a new key for a new submission.
  • Same key while the first request is still being accepted: 409 IDEMPOTENCY_KEY_IN_PROGRESS. Wait for the Retry-After header (in seconds) and resend with the same key.
  • A request that failed (for example 402, 429, 503 or 5xx): 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, same jobId): a key never creates a second job.
  • If our server stopped while handling the first request, the resend can answer 409 IDEMPOTENCY_KEY_IN_PROGRESS for up to 5 minutes. Keep resending with the same key after Retry-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_CONFLICT and 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." }
HTTPCodeWhat to do
400VALIDATION_ERROR, MISSING_AUDIO, INVALID_AUDIO, invalid_request, invalid_keyFix the request. Do not retry unchanged.
400TEMPLATE_NOT_FOUNDThe template_id is not a template of your account. List your templates (step 3) and send a valid ID. No job was created.
400IDEMPOTENCY_KEY_REQUIRED, IDEMPOTENCY_KEY_INVALIDSend an Idempotency-Key header: 1 to 255 visible ASCII characters, unique per submission.
401 (authentication)invalid_key, key_inactive, key_revoked, key_expiredThe key is no longer valid. Use an active key.
401 (authentication)invalid_grantThe refresh_token is no longer valid. Exchange the key again.
401 (other)TOKEN_EXPIRED, TOKEN_INVALID, INVALID_TOKEN, KEY_INVALID, UNAUTHORIZEDRefresh the token. If the key was revoked, exchange an active key.
402insufficient_creditsThe account has no credits left. Contact TranscriMed.
404NOT_FOUNDThe document or job does not exist, or does not belong to your account.
409IDEMPOTENCY_KEY_REUSEDThis Idempotency-Key was used for a different request in the last 24 hours. Use a new key.
409IDEMPOTENCY_KEY_IN_PROGRESSA request with this key is still being accepted. Resend with the same key after Retry-After.
409 (sync correction)CORRECTION_CONFLICT, CORRECTION_SUPERSEDEDThe correction job failed as described below. Nothing was saved. error.details.jobId names the job.
422 (sync generation)TEMPLATE_UNRESOLVED, TEMPLATE_NOT_FOUNDThe 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_DOCUMENTThe correction could not be applied. Nothing was saved.
500 (sync)processing_failed, CORRECTION_FAILED, other job codesThe 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_FAILEDSaving 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_CONFLICTIn 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_SUPERSEDEDIn 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_SAVEDIn 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_FAILEDIn 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.
429rate_limited, user_generation_limit_reachedLimit reached. Honour the Retry-After header when present, or retry with exponential backoff.
503*_capacity_reached, IDEMPOTENCY_STORE_UNAVAILABLEMomentarily unavailable. Retry with exponential backoff and the same Idempotency-Key.
5xx—Server error. Retry with exponential backoff and log the requestId.
— (failed job)TEMPLATE_UNRESOLVEDIn 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.
If the connection drops before you receive the jobId

Resend 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 carries jobId, requestId, external_reference_id, state, status, progress, result and error, but not type, document or errorCode. Take the jobId and continue by polling as in step 5.
  • 404: the submission was never accepted. Resend it with the same Idempotency-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 metadata or external_reference_id.
  • A new Idempotency-Key per submission, reused only to resend that submission.
  • Log the requestId of error responses to speed up support.

Support​

Contact TranscriMed through your support channel and include the requestId or jobId of the operation.