New to Kestra?
Use blueprints to kickstart your first workflows.
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.
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.
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.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.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.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.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.routing_check trigger runs every 15 minutes and ships disabled: true.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.Failing destinations: PACS (3). Most common error: Error in the network protocol.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.
JobsHistorySize in the Orthanc configuration (the default keeps only the last 10 finished jobs) so the lookback window really contains every failure.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).
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).POST /modalities/<name>/store and "Asynchronous": true. The transfer to the silent modality fails within seconds.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.routing_check once pointed at the real server.failing_destinations to the modalities that matter (the PACS and the triage service) and send each team its own channel.POST /modalities/<name>/echo C-ECHO step for each failing destination to tell a network problem from a rejected study.status and failed in the KV store and alert only when the failure count grows./statistics in the same flow to warn when the storage disk approaches its limit.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.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"}.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.20261016T091653.093748). The query compares them with Kestra's clock in UTC, so keep the Kestra host's clock synchronised.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.