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}`);
}
}
}
Integration with Popular Frameworks
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:
- Tools - Development tools and resources
- API Reference - Complete endpoint documentation
- Authentication Guide - Detailed auth implementation
- Email Support - Get help with your integration
Need more examples? Contact our developer team for custom integration assistance.