Skip to main content

Code Examples

This page provides practical examples for integrating TranscriMed API into your healthcare applications. Examples are provided in multiple programming languages.

Basic Integration Examples​

Generate Medical Record from Audio​

Transform audio recordings into structured medical records.

JavaScript/Node.js​

const fs = require('fs');
const FormData = require('form-data');

class TranscriMedClient {
constructor(accessToken) {
this.accessToken = accessToken;
this.baseUrl = 'https://api.transcrimed.com.br';
}

async generateFromAudio(audioFilePath, options = {}) {
try {
// Read and encode audio file
const audioBuffer = fs.readFileSync(audioFilePath);
const audioBase64 = audioBuffer.toString('base64');

const response = await fetch(`${this.baseUrl}/api/v1/medical-records/generate`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
audio: audioBase64,
template_id: options.templateId || 'general-consultation',
language: options.language || 'en',
mode: options.mode || 'sync',
patient_id: options.patientId,
metadata: options.metadata || {}
})
});

if (!response.ok) {
const errorData = await response.json();
throw new Error(`API Error: ${errorData.error.message}`);
}

const result = await response.json();
return result.data.medical_record;
} catch (error) {
console.error('Failed to generate medical record:', error);
throw error;
}
}
}

// Usage
const client = new TranscriMedClient('your_access_token');

client.generateFromAudio('./consultation.wav', {
templateId: 'cardiology-consultation',
language: 'en',
patientId: 'patient_123',
metadata: {
provider: 'Dr. Smith',
appointment_type: 'follow-up'
}
}).then(record => {
console.log('Generated Medical Record:');
console.log('Title:', record.title);
console.log('Content:', record.content);
}).catch(error => {
console.error('Error:', error);
});

Python​

import requests
import base64
import json
from typing import Optional, Dict, Any

class TranscriMedClient:
def __init__(self, access_token: str, base_url: str = "https://api.transcrimed.com.br"):
self.access_token = access_token
self.base_url = base_url
self.session = requests.Session()
self.session.headers.update({
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json'
})

def generate_from_audio(
self,
audio_file_path: str,
template_id: str = "general-consultation",
language: str = "en",
mode: str = "sync",
patient_id: Optional[str] = None,
metadata: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""Generate medical record from audio file."""

# Read and encode audio file
with open(audio_file_path, 'rb') as audio_file:
audio_content = audio_file.read()
audio_base64 = base64.b64encode(audio_content).decode('utf-8')

payload = {
'audio': audio_base64,
'template_id': template_id,
'language': language,
'mode': mode,
'metadata': metadata or {}
}

if patient_id:
payload['patient_id'] = patient_id

response = self.session.post(
f'{self.base_url}/api/v1/medical-records/generate',
json=payload
)

if not response.ok:
error_data = response.json()
raise Exception(f"API Error: {error_data['error']['message']}")

result = response.json()
return result['data']['medical_record']

def list_medical_records(
self,
page: int = 1,
limit: int = 20,
patient_id: Optional[str] = None,
search: Optional[str] = None
) -> Dict[str, Any]:
"""List medical records with optional filtering."""

params = {'page': page, 'limit': limit}
if patient_id:
params['patient_id'] = patient_id
if search:
params['search'] = search

response = self.session.get(
f'{self.base_url}/api/v1/medical-records',
params=params
)

if not response.ok:
error_data = response.json()
raise Exception(f"API Error: {error_data['error']['message']}")

return response.json()['data']

# Usage example
if __name__ == "__main__":
client = TranscriMedClient('your_access_token')

try:
# Generate medical record from audio
record = client.generate_from_audio(
'consultation.wav',
template_id='cardiology-consultation',
language='en',
patient_id='patient_123',
metadata={
'provider': 'Dr. Smith',
'appointment_type': 'follow-up'
}
)

print(f"Generated Medical Record:")
print(f"Title: {record['title']}")
print(f"Content: {record['content']}")
print(f"Created: {record['created_at']}")

# List medical records
records_data = client.list_medical_records(limit=10)
print(f"\nFound {records_data['pagination']['total']} total records")

except Exception as e:
print(f"Error: {e}")

PHP​

<?php

class TranscriMedClient {
private $accessToken;
private $baseUrl;

public function __construct($accessToken, $baseUrl = 'https://api.transcrimed.com.br') {
$this->accessToken = $accessToken;
$this->baseUrl = $baseUrl;
}

public function generateFromAudio($audioFilePath, $options = []) {
// Read and encode audio file
$audioContent = file_get_contents($audioFilePath);
$audioBase64 = base64_encode($audioContent);

$payload = [
'audio' => $audioBase64,
'template_id' => $options['template_id'] ?? 'general-consultation',
'language' => $options['language'] ?? 'en',
'mode' => $options['mode'] ?? 'sync',
'metadata' => $options['metadata'] ?? []
];

if (isset($options['patient_id'])) {
$payload['patient_id'] = $options['patient_id'];
}

$response = $this->makeRequest(
'POST',
'/api/v1/medical-records/generate',
$payload
);

return $response['data']['medical_record'];
}

public function listMedicalRecords($options = []) {
$params = [
'page' => $options['page'] ?? 1,
'limit' => $options['limit'] ?? 20
];

if (isset($options['patient_id'])) {
$params['patient_id'] = $options['patient_id'];
}

if (isset($options['search'])) {
$params['search'] = $options['search'];
}

$response = $this->makeRequest(
'GET',
'/api/v1/medical-records?' . http_build_query($params)
);

return $response['data'];
}

private function makeRequest($method, $endpoint, $data = null) {
$url = $this->baseUrl . $endpoint;

$headers = [
'Authorization: Bearer ' . $this->accessToken,
'Content-Type: application/json'
];

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);

if ($data !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$decodedResponse = json_decode($response, true);

if ($httpCode >= 400) {
throw new Exception('API Error: ' . $decodedResponse['error']['message']);
}

return $decodedResponse;
}
}

// Usage
$client = new TranscriMedClient('your_access_token');

try {
$record = $client->generateFromAudio('consultation.wav', [
'template_id' => 'cardiology-consultation',
'language' => 'en',
'patient_id' => 'patient_123',
'metadata' => [
'provider' => 'Dr. Smith',
'appointment_type' => 'follow-up'
]
]);

echo "Generated Medical Record:\n";
echo "Title: " . $record['title'] . "\n";
echo "Content: " . $record['content'] . "\n";

} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
?>

Async Processing with Job Monitoring​

Handle long-running operations with job tracking.

JavaScript​

class AsyncProcessor {
constructor(client) {
this.client = client;
}

async processWithJobTracking(audioData, options = {}) {
try {
// Start async processing
const response = await fetch(`${this.client.baseUrl}/api/v1/medical-records/generate`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.client.accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
audio: audioData,
mode: 'async',
...options
})
});

const result = await response.json();
const jobId = result.data.job_id;

console.log(`Job ${jobId} started. Monitoring progress...`);

// Monitor job progress
return this.pollJobStatus(jobId);
} catch (error) {
console.error('Failed to start async processing:', error);
throw error;
}
}

async pollJobStatus(jobId, maxAttempts = 60, interval = 2000) {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
const response = await fetch(
`${this.client.baseUrl}/api/v1/jobs/${jobId}`,
{
headers: { 'Authorization': `Bearer ${this.client.accessToken}` }
}
);

const result = await response.json();
const job = result.data.job;

console.log(`Job ${jobId}: ${job.status} (${job.progress}%)`);

if (job.status === 'completed') {
// Get the result
return this.getJobResult(jobId);
} else if (job.status === 'failed') {
throw new Error(`Job failed: ${JSON.stringify(job.error_data)}`);
} else if (job.status === 'cancelled') {
throw new Error('Job was cancelled');
}

// Wait before next poll
await new Promise(resolve => setTimeout(resolve, interval));
} catch (error) {
console.error(`Polling attempt ${attempt + 1} failed:`, error);
if (attempt === maxAttempts - 1) throw error;
}
}

throw new Error('Job polling timeout');
}

async getJobResult(jobId) {
const response = await fetch(
`${this.client.baseUrl}/api/v1/jobs/${jobId}/result`,
{
headers: { 'Authorization': `Bearer ${this.client.accessToken}` }
}
);

const result = await response.json();
return result.data;
}

async getJobLogs(jobId) {
const response = await fetch(
`${this.client.baseUrl}/api/v1/jobs/${jobId}/logs`,
{
headers: { 'Authorization': `Bearer ${this.client.accessToken}` }
}
);

const result = await response.json();
return result.data.logs;
}
}

// Usage
const client = new TranscriMedClient('your_access_token');
const processor = new AsyncProcessor(client);

// Process large audio file asynchronously
const audioBuffer = fs.readFileSync('./long-consultation.wav');
const audioBase64 = audioBuffer.toString('base64');

processor.processWithJobTracking(audioBase64, {
template_id: 'general-consultation',
language: 'en',
patient_id: 'patient_456'
}).then(result => {
console.log('Processing completed!');
console.log('Medical Record ID:', result.medical_record_id);
}).catch(error => {
console.error('Processing failed:', error);
});

Python​

import time
import logging
from typing import Dict, Any, Optional

class AsyncProcessor:
def __init__(self, client: TranscriMedClient):
self.client = client
self.logger = logging.getLogger(__name__)

def process_with_job_tracking(
self,
audio_base64: str,
options: Dict[str, Any] = {},
max_attempts: int = 60,
poll_interval: int = 2
) -> Dict[str, Any]:
"""Process audio asynchronously with job tracking."""

# Start async processing
payload = {
'audio': audio_base64,
'mode': 'async',
**options
}

response = self.client.session.post(
f'{self.client.base_url}/api/v1/medical-records/generate',
json=payload
)

if not response.ok:
error_data = response.json()
raise Exception(f"Failed to start job: {error_data['error']['message']}")

result = response.json()
job_id = result['data']['job_id']

self.logger.info(f"Job {job_id} started. Monitoring progress...")

# Monitor job progress
return self.poll_job_status(job_id, max_attempts, poll_interval)

def poll_job_status(self, job_id: str, max_attempts: int = 60, interval: int = 2) -> Dict[str, Any]:
"""Poll job status until completion."""

for attempt in range(max_attempts):
try:
response = self.client.session.get(f'{self.client.base_url}/api/v1/jobs/{job_id}')

if not response.ok:
self.logger.warning(f"Polling attempt {attempt + 1} failed with status {response.status_code}")
continue

result = response.json()
job = result['data']['job']

self.logger.info(f"Job {job_id}: {job['status']} ({job['progress']}%)")

if job['status'] == 'completed':
return self.get_job_result(job_id)
elif job['status'] == 'failed':
error_msg = f"Job failed: {job.get('error_data', 'Unknown error')}"
raise Exception(error_msg)
elif job['status'] == 'cancelled':
raise Exception("Job was cancelled")

time.sleep(interval)

except Exception as e:
if attempt == max_attempts - 1:
raise e
self.logger.warning(f"Polling attempt {attempt + 1} failed: {e}")
time.sleep(interval)

raise Exception("Job polling timeout")

def get_job_result(self, job_id: str) -> Dict[str, Any]:
"""Get job result after completion."""
response = self.client.session.get(f'{self.client.base_url}/api/v1/jobs/{job_id}/result')

if not response.ok:
error_data = response.json()
raise Exception(f"Failed to get job result: {error_data['error']['message']}")

return response.json()['data']

def get_job_logs(self, job_id: str) -> list:
"""Get job execution logs."""
response = self.client.session.get(f'{self.client.base_url}/api/v1/jobs/{job_id}/logs')

if not response.ok:
error_data = response.json()
raise Exception(f"Failed to get job logs: {error_data['error']['message']}")

return response.json()['data']['logs']

# Usage example
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)

client = TranscriMedClient('your_access_token')
processor = AsyncProcessor(client)

# Read large audio file
with open('long-consultation.wav', 'rb') as f:
audio_content = f.read()
audio_base64 = base64.b64encode(audio_content).decode('utf-8')

try:
result = processor.process_with_job_tracking(
audio_base64,
options={
'template_id': 'general-consultation',
'language': 'en',
'patient_id': 'patient_456'
}
)

print("Processing completed!")
print(f"Medical Record ID: {result['medical_record_id']}")

except Exception as e:
print(f"Processing failed: {e}")

Advanced Integration Patterns​

Batch Processing Multiple Files​

Process multiple audio files efficiently.

class BatchProcessor {
constructor(client, maxConcurrent = 3) {
this.client = client;
this.maxConcurrent = maxConcurrent;
}

async processBatch(audioFiles, options = {}) {
const results = [];
const errors = [];

// Process files in batches to avoid overwhelming the API
for (let i = 0; i < audioFiles.length; i += this.maxConcurrent) {
const batch = audioFiles.slice(i, i + this.maxConcurrent);

const batchPromises = batch.map(async (file, index) => {
try {
console.log(`Processing file ${i + index + 1}/${audioFiles.length}: ${file.name}`);

const audioBuffer = fs.readFileSync(file.path);
const audioBase64 = audioBuffer.toString('base64');

const record = await this.client.generateFromAudio(audioBase64, {
...options,
patient_id: file.patientId || `patient_${i + index + 1}`,
metadata: {
...options.metadata,
filename: file.name,
batch_id: Date.now().toString()
}
});

return { success: true, file: file.name, record };
} catch (error) {
console.error(`Failed to process ${file.name}:`, error);
return { success: false, file: file.name, error: error.message };
}
});

const batchResults = await Promise.all(batchPromises);

batchResults.forEach(result => {
if (result.success) {
results.push(result);
} else {
errors.push(result);
}
});

// Add delay between batches to respect rate limits
if (i + this.maxConcurrent < audioFiles.length) {
console.log('Waiting before next batch...');
await new Promise(resolve => setTimeout(resolve, 2000));
}
}

return { results, errors };
}
}

// Usage
const batchProcessor = new BatchProcessor(client, 3);

const audioFiles = [
{ name: 'consultation1.wav', path: './audio/consultation1.wav', patientId: 'patient_001' },
{ name: 'consultation2.wav', path: './audio/consultation2.wav', patientId: 'patient_002' },
{ name: 'consultation3.wav', path: './audio/consultation3.wav', patientId: 'patient_003' },
];

batchProcessor.processBatch(audioFiles, {
template_id: 'general-consultation',
language: 'en',
mode: 'async'
}).then(({ results, errors }) => {
console.log(`Batch processing completed. ${results.length} successes, ${errors.length} failures.`);

results.forEach(result => {
console.log(`Success ${result.file}: ${result.record.title}`);
});

errors.forEach(error => {
console.log(`Error ${error.file}: ${error.error}`);
});
});

Real-time Audio Processing with Streaming (coming soon)​

Streaming examples will be provided when the streaming endpoints and SDK helpers are available. For now, use file or base64 payloads as shown above.

Error Handling and Retry Logic​

Implement robust error handling with exponential backoff.

class RobustClient {
constructor(accessToken, options = {}) {
this.accessToken = accessToken;
this.baseUrl = options.baseUrl || 'https://api.transcrimed.com.br';
this.maxRetries = options.maxRetries || 3;
this.baseDelay = options.baseDelay || 1000;
}

async makeRequest(endpoint, options = {}, retryCount = 0) {
try {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
...options,
headers: {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'application/json',
...options.headers
}
});

if (!response.ok) {
throw new APIError(response.status, await response.json());
}

return response.json();
} catch (error) {
if (this.shouldRetry(error, retryCount)) {
const delay = this.calculateDelay(retryCount);
console.log(`Request failed, retrying in ${delay}ms... (attempt ${retryCount + 1}/${this.maxRetries})`);

await new Promise(resolve => setTimeout(resolve, delay));
return this.makeRequest(endpoint, options, retryCount + 1);
}

throw error;
}
}

shouldRetry(error, retryCount) {
if (retryCount >= this.maxRetries) return false;

// Retry on network errors or 5xx status codes
if (error instanceof TypeError || // Network error
(error instanceof APIError && error.status >= 500)) {
return true;
}

// Retry on rate limiting
if (error instanceof APIError && error.status === 429) {
return true;
}

return false;
}

calculateDelay(retryCount) {
// Exponential backoff with jitter
const delay = this.baseDelay * Math.pow(2, retryCount);
const jitter = Math.random() * 0.1 * delay;
return delay + jitter;
}

async generateMedicalRecord(payload) {
return this.makeRequest('/api/v1/medical-records/generate', {
method: 'POST',
body: JSON.stringify(payload)
});
}
}

class APIError extends Error {
constructor(status, errorData) {
super(errorData.error.message);
this.status = status;
this.code = errorData.error.code;
this.details = errorData.error.details;
}
}

// Usage with comprehensive error handling
const robustClient = new RobustClient('your_access_token', {
maxRetries: 3,
baseDelay: 1000
});

async function generateWithErrorHandling(audioData, options) {
try {
const result = await robustClient.generateMedicalRecord({
audio: audioData,
...options
});

return result.data.medical_record;
} catch (error) {
if (error instanceof APIError) {
switch (error.code) {
case 'INVALID_AUDIO':
throw new Error('Please provide a valid audio file in WAV, MP3, or M4A format');
case 'INSUFFICIENT_CREDITS':
throw new Error('You have insufficient credits. Please upgrade your plan.');
case 'RATE_LIMIT_EXCEEDED':
throw new Error('Too many requests. Please wait a moment and try again.');
case 'INVALID_TOKEN':
throw new Error('Your session has expired. Please log in again.');
default:
throw new Error(`API Error: ${error.message}`);
}
} else {
throw new Error(`Network error: ${error.message}`);
}
}
}

React Component​

import React, { useState, useCallback } from 'react';
import { TranscriMedClient } from './transcrimed-client';

const MedicalRecordGenerator = ({ accessToken }) => {
const [isProcessing, setIsProcessing] = useState(false);
const [record, setRecord] = useState(null);
const [error, setError] = useState(null);
const [progress, setProgress] = useState(0);

const client = new TranscriMedClient(accessToken);

const handleFileUpload = useCallback(async (event) => {
const file = event.target.files[0];
if (!file) return;

setIsProcessing(true);
setError(null);
setProgress(0);

try {
// Convert file to base64
const audioBase64 = await fileToBase64(file);

// Start processing
const result = await client.generateFromAudio(audioBase64, {
template_id: 'general-consultation',
language: 'en',
mode: 'async'
});

if (result.job_id) {
// Monitor job progress
await monitorJob(result.job_id);
} else {
// Sync result
setRecord(result.medical_record);
}
} catch (err) {
setError(err.message);
} finally {
setIsProcessing(false);
}
}, [client]);

const monitorJob = async (jobId) => {
const pollInterval = setInterval(async () => {
try {
const jobData = await client.getJobStatus(jobId);
setProgress(jobData.progress);

if (jobData.status === 'completed') {
clearInterval(pollInterval);
const result = await client.getJobResult(jobId);
setRecord(result.medical_record);
} else if (jobData.status === 'failed') {
clearInterval(pollInterval);
throw new Error('Processing failed');
}
} catch (err) {
clearInterval(pollInterval);
setError(err.message);
}
}, 2000);
};

const fileToBase64 = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result.split(',')[1]);
reader.onerror = error => reject(error);
});
};

return (
<div className="medical-record-generator">
<h2>Generate Medical Record</h2>

<div className="upload-section">
<input
type="file"
accept="audio/*"
onChange={handleFileUpload}
disabled={isProcessing}
/>
</div>

{isProcessing && (
<div className="processing-status">
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${progress}%` }}
/>
</div>
<p>Processing... {progress}%</p>
</div>
)}

{error && (
<div className="error-message">
<p>Error: {error}</p>
</div>
)}

{record && (
<div className="generated-record">
<h3>{record.title}</h3>
<div className="record-content">
<pre>{record.content}</pre>
</div>
<div className="record-metadata">
<p>Created: {new Date(record.created_at).toLocaleString()}</p>
<p>Patient ID: {record.patient_id}</p>
</div>
</div>
)}
</div>
);
};

export default MedicalRecordGenerator;

Express.js Middleware​

const express = require('express');
const multer = require('multer');
const { TranscriMedClient } = require('./transcrimed-client');

const app = express();
const upload = multer({ storage: multer.memoryStorage() });

// Middleware to validate access token
const validateToken = async (req, res, next) => {
const authHeader = req.headers.authorization;

if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing or invalid authorization header' });
}

req.accessToken = authHeader.substring(7);
next();
};

// Route to generate medical record
app.post('/api/generate-record', validateToken, upload.single('audio'), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: 'Audio file is required' });
}

const client = new TranscriMedClient(req.accessToken);
const audioBase64 = req.file.buffer.toString('base64');

const options = {
template_id: req.body.template_id || 'general-consultation',
language: req.body.language || 'en',
mode: req.body.mode || 'sync',
patient_id: req.body.patient_id,
metadata: req.body.metadata ? JSON.parse(req.body.metadata) : {}
};

const record = await client.generateFromAudio(audioBase64, options);

res.json({
success: true,
data: { medical_record: record }
});

} catch (error) {
console.error('Record generation failed:', error);
res.status(500).json({
success: false,
error: error.message
});
}
});

// Route to check job status
app.get('/api/jobs/:jobId', validateToken, async (req, res) => {
try {
const client = new TranscriMedClient(req.accessToken);
const jobData = await client.getJobStatus(req.params.jobId);

res.json({
success: true,
data: { job: jobData }
});

} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
});

app.listen(3000, () => {
console.log('Server running on port 3000');
});

Best Practices​

1. Token Management​

class TokenManager {
constructor(clientId, clientSecret) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.accessToken = null;
this.refreshToken = null;
this.expiresAt = null;
}

async getValidToken() {
if (this.isTokenValid()) {
return this.accessToken;
}

if (this.refreshToken) {
try {
await this.refreshAccessToken();
return this.accessToken;
} catch (error) {
console.error('Token refresh failed:', error);
// Fall back to re-authentication
}
}

throw new Error('No valid token available. Re-authentication required.');
}

isTokenValid() {
return this.accessToken &&
this.expiresAt &&
Date.now() < this.expiresAt - 60000; // 1 minute buffer
}

async refreshAccessToken() {
const response = await fetch('https://api.transcrimed.com.br/api/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: this.refreshToken,
client_id: this.clientId,
client_secret: this.clientSecret
})
});

if (!response.ok) {
throw new Error('Token refresh failed');
}

const tokens = await response.json();
this.setTokens(tokens);
}

setTokens(tokens) {
this.accessToken = tokens.access_token;
this.refreshToken = tokens.refresh_token || this.refreshToken;
this.expiresAt = Date.now() + (tokens.expires_in * 1000);
}
}

2. Audio File Validation​

const validateAudioFile = (file) => {
const supportedTypes = [
'audio/wav',
'audio/mp3',
'audio/mpeg',
'audio/m4a',
'audio/flac',
'audio/ogg'
];

if (!supportedTypes.includes(file.type)) {
throw new Error(`Unsupported audio format: ${file.type}. Supported formats: ${supportedTypes.join(', ')}`);
}

const maxSize = 100 * 1024 * 1024; // 100MB
if (file.size > maxSize) {
throw new Error(`File too large: ${Math.round(file.size / (1024 * 1024))}MB. Maximum size: 100MB`);
}

return true;
};

3. Rate Limiting​

class RateLimiter {
constructor(requestsPerMinute = 60) {
this.requests = [];
this.maxRequests = requestsPerMinute;
}

async waitForSlot() {
const now = Date.now();
const oneMinuteAgo = now - 60000;

// Remove old requests
this.requests = this.requests.filter(time => time > oneMinuteAgo);

if (this.requests.length >= this.maxRequests) {
const oldestRequest = Math.min(...this.requests);
const waitTime = 60000 - (now - oldestRequest);

console.log(`Rate limit reached. Waiting ${waitTime}ms...`);
await new Promise(resolve => setTimeout(resolve, waitTime));

return this.waitForSlot();
}

this.requests.push(now);
}
}

Worklist Integration Examples​

Send Worklist Items from RIS/PACS​

JavaScript/Node.js​

class WorklistIntegration {
constructor(accessToken) {
this.accessToken = accessToken;
this.baseUrl = 'https://api.transcrimed.com.br';
}

async sendWorklistItems(items) {
const idempotencyKey = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;

try {
const response = await fetch(`${this.baseUrl}/api/v1/worklists/ingest`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey
},
body: JSON.stringify(items)
});

if (!response.ok) {
const error = await response.json();
throw new Error(`Worklist ingestion failed: ${error.error.message}`);
}

const result = await response.json();
console.log(`Successfully processed: ${result.data.inserted} inserted, ${result.data.updated} updated`);

if (result.data.errors.length > 0) {
console.warn('Some items had errors:', result.data.errors);
}

return result;
} catch (error) {
console.error('Worklist ingestion error:', error);
throw error;
}
}

async updateItemStatus(itemId, status, reason) {
try {
const response = await fetch(`${this.baseUrl}/api/v1/worklists/items/${itemId}`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ status, reason })
});

if (!response.ok) {
const error = await response.json();
throw new Error(`Status update failed: ${error.error.message}`);
}

return await response.json();
} catch (error) {
console.error('Status update error:', error);
throw error;
}
}
}

// Usage example
const worklist = new WorklistIntegration('your_access_token');

const examData = [
{
accession_number: "2024-001234",
patient_id: "PAT-456789",
patient_name: "John Smith",
patient_sex: "M",
patient_birth_date: "1980-05-15",
modality: "CR",
exam_datetime: "2024-01-15T10:30:00Z",
exam_room: "Room 1",
exam_description: "Chest X-Ray PA/Lateral",
study_uid: "1.2.826.0.1.3680043.6.15372.14625.20240115103000",
referring_physician: "Dr. Maria Garcia",
hospital_name: "Central Hospital",
location: "Radiology Department"
}
];

// Send worklist items
worklist.sendWorklistItems(examData)
.then(result => console.log('Items sent successfully'))
.catch(error => console.error('Failed to send items:', error));

Python​

import requests
import json
import uuid
from datetime import datetime
from typing import List, Dict, Any

class WorklistIntegration:
def __init__(self, access_token: str):
self.access_token = access_token
self.base_url = 'https://api.transcrimed.com.br'
self.session = requests.Session()
self.session.headers.update({
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json'
})

def send_worklist_items(self, items: List[Dict[str, Any]]) -> Dict[str, Any]:
"""Send worklist items to TranscriMed"""
idempotency_key = f"{int(datetime.now().timestamp())}-{str(uuid.uuid4())[:8]}"

headers = {
'Idempotency-Key': idempotency_key
}

try:
response = self.session.post(
f'{self.base_url}/api/v1/worklists/ingest',
json=items,
headers=headers
)

response.raise_for_status()
result = response.json()

print(f"Successfully processed: {result['data']['inserted']} inserted, "
f"{result['data']['updated']} updated")

if result['data']['errors']:
print(f"Errors: {result['data']['errors']}")

return result

except requests.exceptions.RequestException as e:
print(f"Worklist ingestion error: {e}")
raise

def update_item_status(self, item_id: str, status: str, reason: str = None) -> Dict[str, Any]:
"""Update worklist item status"""
payload = {'status': status}
if reason:
payload['reason'] = reason

try:
response = self.session.patch(
f'{self.base_url}/api/v1/worklists/items/{item_id}',
json=payload
)

response.raise_for_status()
return response.json()

except requests.exceptions.RequestException as e:
print(f"Status update error: {e}")
raise

def validate_worklist_item(self, item: Dict[str, Any]) -> List[str]:
"""Validate worklist item before sending"""
errors = []

# At least one identifier required
if not item.get('accession_number') and not item.get('study_uid'):
errors.append('Either accession_number or study_uid is required')

# Validate modality if present
valid_modalities = ['CR', 'CT', 'MR', 'US', 'XA', 'RF', 'DX', 'MG', 'PT', 'NM']
if item.get('modality') and item['modality'] not in valid_modalities:
errors.append(f"Invalid modality: {item['modality']}")

# Validate sex if present
valid_sex_values = ['M', 'F', 'O', 'U', 'Male', 'Female', 'MASCULINO', 'FEMININO']
if item.get('patient_sex') and item['patient_sex'] not in valid_sex_values:
errors.append(f"Invalid patient_sex: {item['patient_sex']}")

return errors

# Usage example
if __name__ == "__main__":
worklist = WorklistIntegration('your_access_token')

exam_data = [
{
"accession_number": "2024-001234",
"patient_id": "PAT-456789",
"patient_name": "João Silva",
"patient_sex": "M",
"patient_birth_date": "1980-05-15",
"modality": "CR",
"exam_datetime": "2024-01-15T10:30:00Z",
"exam_room": "Sala 1",
"exam_description": "Raio-X de Tórax PA/Perfil",
"study_uid": "1.2.826.0.1.3680043.6.15372.14625.20240115103000",
"referring_physician": "Dr. Maria Santos",
"hospital_name": "Hospital Central",
"location": "Ala Norte - Radiologia"
}
]

# Validate items before sending
for item in exam_data:
errors = worklist.validate_worklist_item(item)
if errors:
print(f"Validation errors for item {item.get('accession_number', 'unknown')}: {errors}")
continue

# Send worklist items
try:
result = worklist.send_worklist_items(exam_data)
print("Items sent successfully")
except Exception as e:
print(f"Failed to send items: {e}")

PHP​

<?php
class WorklistIntegration {
private $accessToken;
private $baseUrl = 'https://api.transcrimed.com.br';

public function __construct($accessToken) {
$this->accessToken = $accessToken;
}

public function sendWorklistItems($items) {
$idempotencyKey = time() . '-' . bin2hex(random_bytes(4));

$options = [
'http' => [
'header' => [
"Authorization: Bearer {$this->accessToken}",
"Content-Type: application/json",
"Idempotency-Key: {$idempotencyKey}"
],
'method' => 'POST',
'content' => json_encode($items)
]
];

$context = stream_context_create($options);
$response = file_get_contents("{$this->baseUrl}/api/v1/worklists/ingest", false, $context);

if ($response === FALSE) {
$error = error_get_last();
throw new Exception("Worklist ingestion failed: " . $error['message']);
}

$result = json_decode($response, true);

if (!$result['success']) {
throw new Exception("API error: " . $result['error']['message']);
}

echo "Successfully processed: {$result['data']['inserted']} inserted, {$result['data']['updated']} updated\n";

if (!empty($result['data']['errors'])) {
echo "Errors: " . json_encode($result['data']['errors']) . "\n";
}

return $result;
}

public function updateItemStatus($itemId, $status, $reason = null) {
$payload = ['status' => $status];
if ($reason) {
$payload['reason'] = $reason;
}

$options = [
'http' => [
'header' => [
"Authorization: Bearer {$this->accessToken}",
"Content-Type: application/json"
],
'method' => 'PATCH',
'content' => json_encode($payload)
]
];

$context = stream_context_create($options);
$response = file_get_contents("{$this->baseUrl}/api/v1/worklists/items/{$itemId}", false, $context);

if ($response === FALSE) {
$error = error_get_last();
throw new Exception("Status update failed: " . $error['message']);
}

return json_decode($response, true);
}

public function validateWorklistItem($item) {
$errors = [];

// At least one identifier required
if (empty($item['accession_number']) && empty($item['study_uid'])) {
$errors[] = 'Either accession_number or study_uid is required';
}

// Validate modality
$validModalities = ['CR', 'CT', 'MR', 'US', 'XA', 'RF', 'DX', 'MG', 'PT', 'NM'];
if (!empty($item['modality']) && !in_array($item['modality'], $validModalities)) {
$errors[] = "Invalid modality: {$item['modality']}";
}

return $errors;
}
}

// Usage example
try {
$worklist = new WorklistIntegration('your_access_token');

$examData = [
[
'accession_number' => '2024-001234',
'patient_id' => 'PAT-456789',
'patient_name' => 'João Silva',
'patient_sex' => 'M',
'patient_birth_date' => '1980-05-15',
'modality' => 'CR',
'exam_datetime' => '2024-01-15T10:30:00Z',
'exam_room' => 'Sala 1',
'exam_description' => 'Raio-X de Tórax PA/Perfil',
'study_uid' => '1.2.826.0.1.3680043.6.15372.14625.20240115103000',
'referring_physician' => 'Dr. Maria Santos',
'hospital_name' => 'Hospital Central'
]
];

// Validate and send items
foreach ($examData as $item) {
$errors = $worklist->validateWorklistItem($item);
if (!empty($errors)) {
echo "Validation errors: " . implode(', ', $errors) . "\n";
continue;
}
}

$result = $worklist->sendWorklistItems($examData);
echo "Items sent successfully\n";

} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
?>

Webhook Handler for Document Delivery​

Node.js/Express​

const express = require('express');
const crypto = require('crypto');
const app = express();

app.use(express.raw({ type: 'application/json' }));

const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;

function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');

return signature === `sha256=${expectedSignature}`;
}

app.post('/webhooks/transcrimed', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const payload = req.body;

// Verify webhook signature
if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
console.error('Invalid webhook signature');
return res.status(401).send('Unauthorized');
}

try {
const data = JSON.parse(payload);

console.log('Received webhook:', {
event: data.event,
accession_number: data.accession_number,
patient_name: data.patient?.name,
document_id: data.document?.id
});

// Process completed document
if (data.event === 'document.completed') {
processCompletedDocument(data);
}

res.status(200).send('OK');

} catch (error) {
console.error('Webhook processing error:', error);
res.status(400).send('Bad Request');
}
});

async function processCompletedDocument(webhookData) {
const {
accession_number,
document,
patient,
metadata
} = webhookData;

try {
// Save document to your system
await saveDocumentToRIS({
accessionNumber: accession_number,
patientId: patient.id,
patientName: patient.name,
documentContent: document.content,
documentFormat: document.format,
createdAt: document.created_at,
processingTime: metadata.processing_duration_ms
});

console.log(`Document saved for accession ${accession_number}`);

} catch (error) {
console.error(`Failed to process document for ${accession_number}:`, error);
// Implement retry logic or dead letter queue
}
}

async function saveDocumentToRIS(documentData) {
// Implement your RIS/PACS integration here
// This could be saving to database, calling another API, etc.
console.log('Saving document to RIS:', documentData.accessionNumber);
}

app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});

Next Steps​

Ready to implement these patterns? Check out:


Need more examples? Contact our developer team for custom integration assistance.