Skip to main content

Worklist Integration

The TranscriMed Worklist API enables seamless bidirectional integration with RIS/PACS systems, allowing healthcare providers to streamline their medical documentation workflow.

Overview

TranscriMed's worklist integration provides:

  • Inbound Integration: Receive worklist items from your RIS/PACS system
  • Processing: Doctors view, select, and process exams in TranscriMed
  • Outbound Integration: Automatically send completed medical documents back to the source system

How It Works

graph LR
A[RIS/PACS System] -->|Send Worklist| B[TranscriMed API]
B --> C[Doctor Dashboard]
C -->|Select Exam| D[Audio Recording]
D -->|Process| E[Medical Document]
E -->|Auto-delivery| A
  1. Worklist Ingestion: Your RIS/PACS system sends exam data to TranscriMed
  2. Doctor Selection: Medical professionals see available exams and select relevant ones
  3. Documentation: Doctors record audio for selected exams
  4. Processing: TranscriMed generates structured medical documents
  5. Delivery: Completed documents are automatically sent back to your system

Data Format

We use DICOM-inspired field names in snake_case format for REST API compatibility:

Core Fields

FieldTypeDescriptionExample
accession_numberstringUnique exam identifier"2024-001234"
study_uidstringDICOM Study Instance UID"1.2.826.0..."
patient_idstringPatient identifier"PAT-456789"
patient_namestringPatient full name"John Smith"
modalitystringExam modality"CR", "CT", "MR"
exam_datetimestringExam date/time (ISO 8601)"2024-01-15T10:30:00Z"
exam_descriptionstringProcedure description"Chest X-Ray PA/Lateral"

Optional Fields

FieldTypeDescription
patient_sexstringPatient gender (M/F/O/U)
patient_birth_datestringBirth date (ISO 8601)
patient_agestringAge in free format
exam_roomstringRoom where exam is performed
referring_physicianstringRequesting physician
hospital_namestringInstitution name
locationstringDepartment/location within institution
procedure_idstringProcedure identifier
metadataobjectAdditional system-specific data

Authentication

1. Register Your Application

  1. Visit the Developer Portal
  2. Create an account or sign in
  3. Register your application with:
    • Application name
    • Company/organization name
    • Redirect URI (for OAuth2 flow)

2. Request Scopes

Your application needs these scopes:

  • worklists:write - Send worklist items to TranscriMed
  • worklists:manage - Update worklist item status (optional)

3. Obtain Access Token

Use the standard OAuth2 authorization code flow:

// Step 1: Redirect user to authorization URL
const authUrl = `https://api.transcrimed.com.br/api/oauth/authorize?` +
`client_id=${clientId}&` +
`response_type=code&` +
`scope=worklists:write&` +
`redirect_uri=${redirectUri}`;

// Step 2: Exchange code for access token
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: 'authorization_code',
client_id: clientId,
client_secret: clientSecret,
code: authorizationCode,
redirect_uri: redirectUri
})
});

const { access_token } = await response.json();

Sending Worklist Items

Basic Example

const worklistItems = [
{
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",
referring_physician: "Dr. Maria Garcia",
hospital_name: "Central Hospital"
}
];

const response = await fetch('https://api.transcrimed.com.br/api/v1/worklists/ingest', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'unique-request-id-12345'
},
body: JSON.stringify(worklistItems)
});

const result = await response.json();
console.log('Ingestion result:', result);

Response Format

{
"success": true,
"data": {
"batch_id": "batch_12345-67890",
"inserted": 1,
"updated": 0,
"deduped": 0,
"errors": []
},
"meta": {
"request_id": "req_abc123",
"timestamp": "2024-01-15T10:30:00Z"
}
}

Managing Worklist Status

You can optionally update the status of worklist items:

const response = await fetch(`https://api.transcrimed.com.br/api/v1/worklists/items/${itemId}`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
status: 'in_progress',
reason: 'Exam started in Room 1'
})
});

Available Status Values

  • new - Newly received item
  • in_progress - Currently being processed
  • completed - Processing completed
  • cancelled - Exam cancelled
  • archived - Item archived

Receiving Completed Documents

When a doctor completes a medical document for a worklist item, TranscriMed will automatically attempt to deliver it back to your system using the configured webhook endpoint.

Setting Up Webhooks

  1. In the Developer Portal, configure your webhook endpoint
  2. Set the endpoint URL where you want to receive completed documents
  3. Generate and securely store your webhook secret for signature verification

Webhook Payload Example

{
"event": "document.completed",
"worklist_item_id": "550e8400-e29b-41d4-a716-446655440000",
"accession_number": "2024-001234",
"document": {
"id": "doc-uuid-here",
"title": "Chest X-Ray Report",
"content": "<html>Full medical report content...</html>",
"format": "html",
"created_at": "2024-01-15T11:30:00Z"
},
"patient": {
"id": "PAT-456789",
"name": "John Smith"
},
"metadata": {
"processing_duration_ms": 45000,
"template_used": "radiology-report"
}
}

Webhook Security

All webhook requests are signed with HMAC-SHA256:

const crypto = require('crypto');

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

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

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

if (!verifyWebhookSignature(payload, signature, webhookSecret)) {
return res.status(401).send('Invalid signature');
}

// Process the webhook
console.log('Document completed:', req.body);
res.status(200).send('OK');
});

Best Practices

1. Idempotency

Always include an Idempotency-Key header to prevent duplicate processing:

const idempotencyKey = `${systemId}-${timestamp}-${accessionNumber}`;

fetch('/api/v1/worklists/ingest', {
headers: {
'Idempotency-Key': idempotencyKey
}
// ... other options
});

2. Error Handling

Implement proper error handling and retry logic:

async function ingestWorklistItems(items, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch('/api/v1/worklists/ingest', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'Idempotency-Key': generateIdempotencyKey()
},
body: JSON.stringify(items)
});

if (response.ok) {
return await response.json();
}

if (response.status === 401) {
// Refresh access token
await refreshAccessToken();
continue;
}

throw new Error(`HTTP ${response.status}: ${response.statusText}`);

} catch (error) {
console.error(`Attempt ${attempt} failed:`, error);

if (attempt === maxRetries) {
throw error;
}

// Exponential backoff
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 1000)
);
}
}
}

3. Data Validation

Validate your data before sending:

function validateWorklistItem(item) {
const errors = [];

// At least one identifier required
if (!item.accession_number && !item.study_uid) {
errors.push('Either accession_number or study_uid is required');
}

// Validate modality
const validModalities = ['CR', 'CT', 'MR', 'US', 'XA', 'RF', 'DX', 'MG'];
if (item.modality && !validModalities.includes(item.modality)) {
errors.push(`Invalid modality: ${item.modality}`);
}

// Validate date format
if (item.exam_datetime && !isValidISO8601(item.exam_datetime)) {
errors.push('exam_datetime must be in ISO 8601 format');
}

return errors;
}

4. Rate Limiting

Respect rate limits and implement backoff strategies:

class RateLimitedClient {
constructor(accessToken) {
this.accessToken = accessToken;
this.requestQueue = [];
this.processing = false;
}

async makeRequest(url, options) {
return new Promise((resolve, reject) => {
this.requestQueue.push({ url, options, resolve, reject });
this.processQueue();
});
}

async processQueue() {
if (this.processing || this.requestQueue.length === 0) return;

this.processing = true;

while (this.requestQueue.length > 0) {
const { url, options, resolve, reject } = this.requestQueue.shift();

try {
const response = await fetch(url, options);

if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
await new Promise(resolve =>
setTimeout(resolve, (retryAfter || 60) * 1000)
);
this.requestQueue.unshift({ url, options, resolve, reject });
continue;
}

resolve(await response.json());
} catch (error) {
reject(error);
}

// Small delay between requests
await new Promise(resolve => setTimeout(resolve, 100));
}

this.processing = false;
}
}

Testing

Test Mode

Use test credentials to experiment without affecting production:

  1. Request test credentials with test_tc_ prefix
  2. Use the same production endpoint
  3. The API automatically returns mock data for test clients
const testClient = {
client_id: 'test_tc_abc123',
client_secret: 'test_tcs_xyz789'
};

// This will automatically activate test mode
const response = await fetch('/api/v1/worklists/ingest', {
headers: {
'Authorization': `Bearer ${testAccessToken}`
},
body: JSON.stringify(testWorklistItems)
});

Sample Test Data

Use realistic test data:

const testWorklistItems = [
{
accession_number: "TEST-2024-001",
patient_id: "TEST-PAT-001",
patient_name: "Test Patient",
patient_sex: "M",
patient_birth_date: "1980-01-01",
modality: "CR",
exam_datetime: "2024-01-15T10:00:00Z",
exam_description: "Test Chest X-Ray",
referring_physician: "Dr. Test Physician",
hospital_name: "Test Hospital"
}
];

Troubleshooting

Common Issues

Authentication Errors (401)

  • Verify your access token is valid and not expired
  • Check that you have the required scopes (worklists:write)
  • Ensure you're using the correct client credentials

Validation Errors (400)

  • Verify all required fields are present
  • Check date formats are ISO 8601
  • Ensure modality codes are valid
  • At least one of accession_number or study_uid must be provided

Rate Limiting (429)

  • Implement exponential backoff
  • Respect the Retry-After header
  • Consider reducing request frequency

Webhook Delivery Issues

  • Verify your webhook endpoint is accessible
  • Check signature verification implementation
  • Ensure your endpoint responds with 200 status
  • Review webhook logs in the Developer Portal

Debug Mode

Enable detailed logging to troubleshoot issues:

const DEBUG = process.env.NODE_ENV === 'development';

async function debugRequest(url, options) {
if (DEBUG) {
console.log('Request:', { url, options });
}

const response = await fetch(url, options);
const data = await response.json();

if (DEBUG) {
console.log('Response:', {
status: response.status,
headers: Object.fromEntries(response.headers),
data
});
}

return { response, data };
}

Need Help?

Ready to integrate? Start with our Getting Started Guide or explore the API Reference.