Schedule icon
Request icon
Return icon
Switch icon
Log icon
SlackIncomingWebhook icon

Orthanc DICOM Transfer Job Sentinel

Check Orthanc DICOM transfer jobs on a schedule, change nothing, and alert Slack when studies fail to reach the PACS or archive, or transfers are stuck.

Categories
BusinessData

Imaging departments use Orthanc as a router: scans arrive from the modalities and Orthanc forwards them to the PACS, a CAD or triage service, a teleradiology partner or an archive. When a destination is down, rejects the association, or a network link drops, the transfer job fails or sits in Retry, and nothing pages anyone. The study is simply missing from the radiologist's worklist until someone asks where it is. This blueprint turns two read-only Orthanc REST calls into a scheduled image routing gate: /system proves the server is answering, /jobs?expand returns every job with its state, destination and error, a single jq query counts failed and stuck transfers, and the result routes to a quiet log, an informational Slack note or a Slack page.

How it works

  1. read_system (io.kestra.plugin.core.http.Request) reads /system with basic authentication. Only an answer with an Orthanc Version counts as a working server. Every request is a GET with a timeout, three retries, allowFailed: true and allowFailure: true, so an outage never aborts the flow.
  2. fetch_jobs reads /jobs?expand: every job Orthanc still remembers, with State (Pending, Running, Success, Failure, Paused, Retry), Type, CreationTime, ErrorDescription and, for DICOM transfers, the destination in Content.RemoteAet. A runIf skips it when /system did not answer.
  3. classify_routing (io.kestra.plugin.core.debug.Return) checks both responses (a missing response, a non-200 code or a body that is not JSON is UNREACHABLE), then counts in one jq query the Failure jobs created in the last lookback_minutes and the jobs still Pending, Running, Paused or Retry after stuck_minutes. It names the failing destinations with their counts, the most common error, and the oldest stuck job. At least critical_failed failures, or a job stuck past critical_stuck_minutes, is CRITICAL; any failure or stuck job is WARNING; otherwise HEALTHY.
  4. route_by_routing_status (io.kestra.plugin.core.flow.Switch) logs HEALTHY quietly, posts a Slack note for WARNING, pages Slack for CRITICAL and UNREACHABLE, and logs any unexpected value in defaults.
  5. log_audit_result writes one audit line per run with both HTTP codes and the classification. The flow-level errors block alerts Slack if the flow fails outright.
  6. The routing_check trigger runs every 15 minutes and ships disabled: true.

What you get

  • A scheduled, read-only routing check for any Orthanc server, using only its REST API.
  • A status output (HEALTHY / WARNING / CRITICAL / UNREACHABLE) other flows can gate on, and a routing JSON output with jobs, failed, stuck, failing_destinations, top_error, oldest_stuck and orthanc_version.
  • Slack messages that name the destination and the error, for example Failing destinations: PACS (3). Most common error: Error in the network protocol.
  • An audit log line on every run, so routing history survives a Slack outage.

Who it's for

  • Radiology and imaging IT teams that route studies through Orthanc to a PACS, CAD or triage services, teleradiology or archives.
  • Hospitals and clinics running Orthanc as a mini-PACS or research archive.
  • Data platform teams already using Kestra who want the same alerting for their imaging infrastructure.

Why orchestrate this with Kestra

Orthanc records every job and its error, but only someone looking at the jobs page sees them. Kestra runs the same read-only check on a schedule, classifies all routing in one typed status, keeps every run in the execution history, and routes it to the right channel, while the Switch branches are ready to grow into follow-up (a ticket for the PACS team, a C-ECHO test of the failing destination, a resend after an approval) without changing the check.

Prerequisites

  • An Orthanc server whose REST API is reachable from Kestra, with authentication enabled.
  • An Orthanc user for the monitoring flow. Raise JobsHistorySize in the Orthanc configuration (the default keeps only the last 10 finished jobs) so the lookback window really contains every failure.
  • A Slack incoming webhook for the WARNING, CRITICAL, UNREACHABLE and flow-failure alerts.

Secrets

  • ORTHANC_USERNAME and ORTHANC_PASSWORD: credentials of the monitoring user (HTTP basic authentication).
  • SLACK_WEBHOOK: Slack incoming webhook used for every alert branch and the flow-failure alert.

In Kestra OSS (no Enterprise secrets backend), secrets are supplied as environment variables prefixed SECRET_ and base64-encoded, then read in flows with {{ secret('NAME') }}, for example SECRET_ORTHANC_PASSWORD=$(echo -n '<password>' | base64).

Quick start

  1. To try it without a production server, run Orthanc locally (the orthancteam/orthanc image, or the macOS and Windows packages from orthanc.uclouvain.be) with AuthenticationEnabled, a RegisteredUsers entry, and two DicomModalities: one that answers and one that does not (for example a port nothing listens on).
  2. Upload a few DICOM files, then send a study to each modality with POST /modalities/<name>/store and "Asynchronous": true. The transfer to the silent modality fails within seconds.
  3. Add ORTHANC_USERNAME, ORTHANC_PASSWORD and SLACK_WEBHOOK as secrets, set orthanc_url (for example http://localhost:8042), and run the flow. The status is WARNING and Slack names the failing destination and its error.
  4. Enable routing_check once pointed at the real server.

How to extend

  • Filter failing_destinations to the modalities that matter (the PACS and the triage service) and send each team its own channel.
  • Add a POST /modalities/<name>/echo C-ECHO step for each failing destination to tell a network problem from a rejected study.
  • Store status and failed in the KV store and alert only when the failure count grows.
  • Read /statistics in the same flow to warn when the storage disk approaches its limit.

Inputs

  • orthanc_url: type STRING, default "https://orthanc.example.org".
  • lookback_minutes: type INT, default 60.
  • stuck_minutes: type INT, default 30.
  • critical_failed: type INT, default 3.
  • critical_stuck_minutes: type INT, default 120.

Outputs

  • status: one of HEALTHY, WARNING, CRITICAL, UNREACHABLE.
  • routing: the full classification as JSON, for example {"orthanc_version":"1.13.0","jobs":3,"failed":1,"stuck":0,"failing_destinations":"PACS (1)","top_error":"Error in the network protocol","oldest_stuck":"none","status":"WARNING"}.

Links

Pitfalls

  • Orthanc forgets finished jobs beyond JobsHistorySize (10 by default) and, unless SaveJobs is on, after a restart. A failure that has already been pushed out of the history cannot be seen; raise the history size on busy servers.
  • Job times are in UTC in Orthanc's compact format (20261016T091653.093748). The query compares them with Kestra's clock in UTC, so keep the Kestra host's clock synchronised.
  • Orthanc users in RegisteredUsers have full access. Put the monitoring account behind the authorization plugin or a read-only reverse proxy rule if you need a read-only role; this flow only ever sends GET requests.
  • The check only reads. It never resends, cancels or deletes jobs.
See How

New to Kestra?

Use blueprints to kickstart your first workflows.