--- title: Integration | Avara description: Integrate Viewer into your platform using our SDKs to automate study creation and management. This is for IT personnel of PACS infrastructure. --- ## Introduction This integration guide is specifically for the Viewer platform. It is not intended for AutoScribe or the Clinical Platform integrations. Studies can be fully created and managed using the Viewer dashboard, but automating and baking Viewer into your platform makes the entire process streamlined and more efficient. Additionally, any sort of study viewing requires integrations to provide seamless access to studies within your existing workflow. This guide covers the essential integration patterns for developers, walking you through the complete setup process from initial configuration to production deployment. For complete API documentation and all available operations, see the [API Reference](https://docs.avarasoftware.com/api). ### Integration Steps To integrate Viewer into your platform, follow these steps: 1. **Create API key** — Generate an API key in the Viewer dashboard with appropriate permissions and scopes for your integration needs. 2. **Configure webhooks** — Set up webhook endpoints to handle study image access requests (if using the viewer), ensuring secure signature verification. 3. **Automate creating studies** — Use our SDKs to programmatically create studies from your PACS, RIS, or other systems, enabling seamless workflow integration (manual creation in dashboard also supported). 4. **Invite your users** — Set up user access and authentication to allow your team members to access Viewer studies. ## API Configuration Before integrating Viewer into your platform, you need to create and configure an API key in the Viewer dashboard. ### Creating an API Key 1. After logging in, navigate to the sidebar and go to the **API Config** section 2. Click the **Create API Key** button 3. Configure the following values: - **Description**: A human-readable description to identify the key in the dashboard - **Study scope**: Choose between **Self** or **All** - **Self**: For teleradiology groups with multiple integrating PACS sources. This ensures each PACS can only access, edit, and manage their own studies - **All**: For customers with a unified source of studies or integrators themselves (PACS companies looking to integrate Viewer into their platform) - **User access**: Choose **None**, **Read**, or **Write** to determine if the API token can invite and manage users - **Express customer access**: Explicitly for integrators (PACS companies) looking to serve Viewer as a product to all of their customers. Otherwise, leave as **None** since it won’t be needed - **Study data webhook endpoint**: Your domain endpoint to generate study URLs for the viewer (leave blank if you are not going to use the viewer — can be edited later). Example: `https://my-domain.com/api/generate-viewer-study-urls` (see the [Study Image Access](#study-image-access) webhook section below) 4. Once the API key is created, **save it to your environment variables** — you will not be able to view it again ### Editing Webhook Endpoints To edit webhook endpoints after creating an API key, click the **Edit Endpoints** button in the API config row for your key. ![Edit Webhook Endpoints](/images/docs/viewer_editing_endpoints.png) ### Viewing Webhook Secret To view the webhook secret for webhook integrations (it is auto-generated), click the **View Webhook Secret** button in the API config row for your key. This secret can be viewed at any time. ![View Webhook Secret](/images/docs/view_jwt_secret.png) Save this secret to your environment variables as `AVARA_WEBHOOK_KEY` for use in webhook signature verification. ## Creating Studies The simplest integration pattern is creating studies programmatically using our SDKs. This allows you to automate study creation from your PACS, RIS, or other systems. Studies can also be fully created and managed in the dashboard if desired. ### TypeScript ``` import Avara from 'avara'; const avara = new Avara({ apiKey: process.env.AVARA_API_KEY }); const study = await avara.viewer.studies.create({ studyUid: '1.3.6.1.4.1.543245.54356543', studyDescription: 'Coronary CT Angiogram', severity: 'normal', }); ``` ### Python ``` import os from avara import Avara client = Avara( api_key=os.environ.get("AVARA_API_KEY"), ) study = client.viewer.studies.create( severity="normal", study_description="Brain MRI with Contrast", study_instance_uid="1.2.840.113619.2.55.3.604688119.868.1234567890.123", ) ``` ### Java ``` package com.avara.example; import com.avara.client.AvaraClient; import com.avara.client.okhttp.AvaraOkHttpClient; import com.avara.models.viewer.studies.StudyCreateParams; import com.avara.models.viewer.studies.StudyCreateResponse; public final class Main { private Main() {} public static void main(String[] args) { AvaraClient client = AvaraOkHttpClient.fromEnv(); StudyCreateParams params = StudyCreateParams.builder() .severity(StudyCreateParams.Severity.NORMAL) .studyDescription("Brain MRI with Contrast") .studyInstanceUid("1.2.840.113619.2.55.3.604688119.868.1234567890.123") .build(); StudyCreateResponse study = client.viewer().studies().create(params); } } ``` ## Temporary Access Views Temporary access views enable you to generate reroute URLs that provide secure, authenticated access to Viewer without requiring users to log in to the Avara Viewer dashboard. This feature is designed for advanced PACS company users desiring a fully embedded experience in their platform and is completely optional. ### How It Works The flow for temporary access views is straightforward: 1. **Authenticated users request access**: Users authenticated in your platform request access to view a study 2. **Server authorization**: Your servers authorize the request and generate a reroute URL using our SDK. The URL is based on the study ID 3. **Client-side redirect**: Serve the URL to the user and redirect them client-side to a new window where they get a secure connection to view the study without needing to log in to the Avara Viewer dashboard ### TypeScript ``` import Avara from 'avara'; const client = new Avara({ apiKey: process.env['AVARA_API_KEY'], }); const response = await client.viewer.studies.rerouteURL({ studyId: 'stu_1234567890abcdef1234567890abcdef', }); ``` ### Python ``` import os from avara import Avara client = Avara( api_key=os.environ.get("AVARA_API_KEY"), ) response = client.viewer.studies.reroute_url( study_id="stu_1234567890abcdef1234567890abcdef", ) ``` ### Java ``` package com.avara.example; import com.avara.client.AvaraClient; import com.avara.client.okhttp.AvaraOkHttpClient; import com.avara.models.viewer.studies.StudyRerouteUrlParams; import com.avara.models.viewer.studies.StudyRerouteUrlResponse; public final class Main { private Main() {} public static void main(String[] args) { AvaraClient client = AvaraOkHttpClient.fromEnv(); StudyRerouteUrlParams params = StudyRerouteUrlParams.builder() .studyId("stu_1234567890abcdef1234567890abcdef") .build(); StudyRerouteUrlResponse response = client.viewer().studies().rerouteUrl(params); } } ``` ## Webhooks Viewer uses webhooks to integrate with your systems for study image access. The Avara SDK handles webhook signature verification and event parsing automatically using the `unwrap()` method. ### Webhook Security All webhook requests from Avara are signed using HMAC-SHA256. The SDK’s `unwrap()` method automatically verifies signatures and returns typed webhook events. The SDK reads your `AVARA_WEBHOOK_KEY` environment variable for signature verification. You can use `unsafeUnwrap()` instead of `unwrap()` to skip signature verification, but this is not recommended for production as it bypasses security checks. #### TypeScript ``` import Avara from "avara"; import express, { Request, Response } from "express"; const client = new Avara(); // reads AVARA_WEBHOOK_KEY from env const app = express(); app.use(express.json()); app.post("/webhooks/avara", async (req: Request, res: Response) => { const event = client.webhooks.unwrap(req.body, req.headers); if (event.type === "study.access_requested") { // Handle study access request const { studyId, studyInstanceUid } = event.data; // ... } }); ``` #### Python ``` from avara import Avara from flask import Flask, request, jsonify client = Avara() # reads AVARA_WEBHOOK_KEY from env app = Flask(__name__) @app.route('/webhooks/avara', methods=['POST']) def handle_webhook(): event = client.webhooks.unwrap(request.data, request.headers) if event.type == 'study.access_requested': # Handle study access request study_id = event.data.study_id # ... ``` #### Java ``` import com.avara.client.AvaraClient; import com.avara.client.okhttp.AvaraOkHttpClient; import com.avara.models.webhooks.*; import org.springframework.web.bind.annotation.*; import org.springframework.http.ResponseEntity; import javax.servlet.http.HttpServletRequest; import java.util.*; @RestController @RequestMapping("/webhooks") public class WebhookController { private final AvaraClient client = AvaraOkHttpClient.fromEnv(); @PostMapping("/avara") public ResponseEntity handleWebhook( @RequestBody String body, HttpServletRequest request ) { WebhookEvent event = client.webhooks().unwrap(body, getHeaders(request)); if (event instanceof StudyAccessRequestedWebhookEvent e) { // Handle study access request String studyId = e.getData().getStudyId(); // ... } return ResponseEntity.ok().build(); } private Map> getHeaders(HttpServletRequest request) { Map> headers = new HashMap<>(); var headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); headers.put(name, Collections.list(request.getHeaders(name))); } return headers; } } ``` ### Study Image Access The `study.access_requested` webhook is sent when Avara needs presigned URLs for DICOM images. This is a **synchronous webhook** — you must respond with the URLs within the request timeout. This webhook is sent before a study can be viewed in the Avara interface. When Avara needs to access study images, it sends a POST request to your webhook endpoint. Your endpoint must respond with presigned URLs for the DICOM images. The SDK’s `unwrap()` method returns a typed `StudyAccessRequestedWebhookEvent` that you can use directly. #### TypeScript ``` import Avara from "avara"; import express, { Request, Response } from "express"; const client = new Avara(); const app = express(); app.use(express.json()); app.post("/webhooks/avara", async (req: Request, res: Response) => { const event = client.webhooks.unwrap(req.body, req.headers); if (event.type === "study.access_requested") { const { studyId, studyInstanceUid } = event.data; // This is your internal business logic const presignedUrls = await generatePresignedUrlsForStudy(studyInstanceUid); if (presignedUrls.length === 0) { return res.status(200).json({ authorized: false, error: "Study not found in PACS", }); } res.status(200).json({ authorized: true, urls: presignedUrls, }); } }); // This is your internal business logic async function generatePresignedUrlsForStudy( studyInstanceUid: string ): Promise { // Query your PACS/RIS system for the study // Generate presigned URLs for each DICOM image // Return a flat list of all image URLs return [ "https://storage.example.com/dicom/image1.dcm?token=abc123", "https://storage.example.com/dicom/image2.dcm?token=def456", ]; } ``` **Response Format** ``` interface StudyAccessRequestedWebhookResponse { authorized: boolean; urls?: string[]; // Presigned URLs for DICOM images error?: string; // Error message if authorization failed } ``` #### Python ``` from avara import Avara from flask import Flask, request, jsonify from typing import List client = Avara() app = Flask(__name__) @app.route('/webhooks/avara', methods=['POST']) def handle_webhook(): event = client.webhooks.unwrap(request.data, request.headers) if event.type == 'study.access_requested': study_id = event.data.study_id study_instance_uid = event.data.study_instance_uid # This is your internal business logic presigned_urls = generate_presigned_urls_for_study(study_instance_uid) if not presigned_urls: return jsonify({ 'authorized': False, 'error': 'Study not found in PACS' }), 200 return jsonify({ 'authorized': True, 'urls': presigned_urls }), 200 # This is your internal business logic def generate_presigned_urls_for_study(study_instance_uid: str) -> List[str]: # Query your PACS/RIS system for the study # Generate presigned URLs for each DICOM image # Return a flat list of all image URLs return [ 'https://storage.example.com/dicom/image1.dcm?token=abc123', 'https://storage.example.com/dicom/image2.dcm?token=def456', ] ``` **Response Format** ``` from typing import TypedDict, Optional, List class StudyAccessRequestedWebhookResponse(TypedDict, total=False): authorized: bool urls: Optional[List[str]] # Presigned URLs for DICOM images error: Optional[str] # Error message if authorization failed ``` #### Java ``` import com.avara.client.AvaraClient; import com.avara.client.okhttp.AvaraOkHttpClient; import com.avara.models.webhooks.*; import org.springframework.web.bind.annotation.*; import org.springframework.http.ResponseEntity; import javax.servlet.http.HttpServletRequest; import java.util.*; @RestController @RequestMapping("/webhooks") public class WebhookController { private final AvaraClient client = AvaraOkHttpClient.fromEnv(); @PostMapping("/avara") public ResponseEntity handleWebhook( @RequestBody String body, HttpServletRequest request ) { WebhookEvent event = client.webhooks().unwrap(body, getHeaders(request)); if (event instanceof StudyAccessRequestedWebhookEvent e) { String studyId = e.getData().getStudyId(); String studyInstanceUid = e.getData().getStudyInstanceUid(); // This is your internal business logic List presignedUrls = generatePresignedUrlsForStudy(studyInstanceUid); if (presignedUrls.isEmpty()) { return ResponseEntity.ok(Map.of( "authorized", false, "error", "Study not found in PACS" )); } return ResponseEntity.ok(Map.of( "authorized", true, "urls", presignedUrls )); } return ResponseEntity.ok().build(); } // This is your internal business logic private List generatePresignedUrlsForStudy(String studyInstanceUid) { // Query your PACS/RIS system for the study // Generate presigned URLs for each DICOM image // Return a flat list of all image URLs return List.of( "https://storage.example.com/dicom/image1.dcm?token=abc123", "https://storage.example.com/dicom/image2.dcm?token=def456" ); } private Map> getHeaders(HttpServletRequest request) { Map> headers = new HashMap<>(); var headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); headers.put(name, Collections.list(request.getHeaders(name))); } return headers; } } ``` **Response Format** ``` public class StudyAccessRequestedWebhookResponse { private boolean authorized; private List urls; // Presigned URLs for DICOM images private String error; // Error message if authorization failed } ``` ## API Reference For complete API documentation, including all available operations, webhooks, and advanced features, see the [API Reference](https://docs.avarasoftware.com/api).