changeset 6411:98964246e23e

ErrorPayload in OrthancException
author Alain Mazy <am@orthanc.team>
date Fri, 14 Nov 2025 15:12:16 +0100
parents 200edfa98911
children 700e04a72465
files NEWS OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake OrthancFramework/Sources/DicomFormat/DicomMap.cpp OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp OrthancFramework/Sources/DicomNetworking/DimseErrorPayload.cpp OrthancFramework/Sources/DicomNetworking/DimseErrorPayload.h OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp OrthancFramework/Sources/JobsEngine/IJob.h OrthancFramework/Sources/JobsEngine/JobInfo.cpp OrthancFramework/Sources/JobsEngine/JobStatus.cpp OrthancFramework/Sources/JobsEngine/JobStatus.h OrthancFramework/Sources/JobsEngine/JobStepResult.cpp OrthancFramework/Sources/JobsEngine/JobStepResult.h OrthancFramework/Sources/JobsEngine/JobsEngine.cpp OrthancFramework/Sources/JobsEngine/JobsRegistry.cpp OrthancFramework/Sources/JobsEngine/JobsRegistry.h OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.h OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h OrthancFramework/Sources/OrthancException.cpp OrthancFramework/Sources/OrthancException.h OrthancFramework/Sources/SystemToolbox.cpp OrthancFramework/UnitTestsSources/JobsTests.cpp OrthancServer/Plugins/Engine/OrthancPlugins.cpp OrthancServer/Plugins/Engine/PluginsJob.h OrthancServer/Sources/ServerJobs/ArchiveJob.h OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.cpp OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.h OrthancServer/Sources/ServerJobs/ThreadedSetOfInstancesJob.h OrthancServer/Sources/main.cpp OrthancServer/UnitTestsSources/ServerJobsTests.cpp
diffstat 32 files changed, 305 insertions(+), 136 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Thu Nov 13 06:32:48 2025 +0100
+++ b/NEWS	Fri Nov 14 15:12:16 2025 +0100
@@ -13,12 +13,11 @@
 
 * Fix: C-Get SCU jobs or HTTP responses were always successful even if the C-Get operation
   actually failed.
-* C-Store, C-Move and C-Get jobs and HTTP responses now include a new "DimseErrorStatus"
-  field (ONLY if the operation fails).
+* When an error occurs while Orthanc handles an HTTP request, it may now include a new
+  "ErrorPayload" in the HTTP response with details about the failed operation.
+  This is currently implemented for C-Store, C-Move and C-Get operations.
 * C-Move and C-Get jobs and HTTP responses now include a new "Details" field with the "DimseErrorStatus"
-  of each operation and the list of "RetrievedInstancesIds".  Note that, if you have multiple
-  resources to retrieve and one of them fails, you'll only get those details if you have set
-  "Permissive" to true to allow other operations to continue after the failure.
+  of each operation and the list of "RetrievedInstancesIds".
 
 Maintenance
 -----------
--- a/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake	Fri Nov 14 15:12:16 2025 +0100
@@ -574,6 +574,7 @@
       ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/DicomControlUserConnection.cpp
       ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/DicomServer.cpp
       ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/DicomStoreUserConnection.cpp
+      ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/DimseErrorPayload.cpp
       ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/Internals/CommandDispatcher.cpp
       ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/Internals/FindScp.cpp
       ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/Internals/MoveScp.cpp
--- a/OrthancFramework/Sources/DicomFormat/DicomMap.cpp	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancFramework/Sources/DicomFormat/DicomMap.cpp	Fri Nov 14 15:12:16 2025 +0100
@@ -255,7 +255,7 @@
       
       if (existingLevelTags.find(tag) != existingLevelTags.end())
       {
-        throw OrthancException(ErrorCode_MainDicomTagsMultiplyDefined, tag.Format() + " is already defined", false);
+        throw OrthancException(ErrorCode_MainDicomTagsMultiplyDefined, tag.Format() + " is already defined", LogException_No);
       }
 
       if (level == ResourceType_Study) // all patients main dicom tags are also copied at study level
@@ -264,7 +264,7 @@
         
         if (patientLevelTags.find(tag) != patientLevelTags.end())
         {
-          throw OrthancException(ErrorCode_MainDicomTagsMultiplyDefined, tag.Format() + " is already defined", false);
+          throw OrthancException(ErrorCode_MainDicomTagsMultiplyDefined, tag.Format() + " is already defined", LogException_No);
         }
       }
 
--- a/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp	Fri Nov 14 15:12:16 2025 +0100
@@ -24,6 +24,7 @@
 
 #include "../PrecompiledHeaders.h"
 #include "DicomControlUserConnection.h"
+#include "DimseErrorPayload.h"
 
 #include "../Compatibility.h"
 #include "../DicomFormat/DicomArray.h"
@@ -393,13 +394,15 @@
                                "C-FIND SCU to AET \"" +
                                parameters_.GetRemoteModality().GetApplicationEntityTitle() +
                                "\" has failed with DIMSE status " + DimseToHexString(response.DimseStatus) +
-                               " (unable to process - invalid query ?)");
+                               " (unable to process - invalid query ?)",
+                               MakeDimseErrorStatusPayload(response.DimseStatus));
       }
       else
       {
         throw OrthancException(ErrorCode_NetworkProtocol, "C-FIND SCU to AET \"" +
                                parameters_.GetRemoteModality().GetApplicationEntityTitle() +
-                               "\" has failed with DIMSE status " + DimseToHexString(response.DimseStatus));
+                               "\" has failed with DIMSE status " + DimseToHexString(response.DimseStatus),
+                               MakeDimseErrorStatusPayload(response.DimseStatus));
       }
     }
   }
@@ -522,14 +525,14 @@
                                parameters_.GetRemoteModality().GetApplicationEntityTitle() +
                                "\" has failed with DIMSE status " + DimseToHexString(response.DimseStatus) +
                                " (unable to process - resource not found ?)",
-                               response.DimseStatus);
+                               MakeDimseErrorStatusPayload(response.DimseStatus)); 
       }
       else
       {
         throw OrthancException(ErrorCode_NetworkProtocol, "C-MOVE SCU to AET \"" +
                                parameters_.GetRemoteModality().GetApplicationEntityTitle() +
                                "\" has failed with DIMSE status " + DimseToHexString(response.DimseStatus),
-                               response.DimseStatus);
+                               MakeDimseErrorStatusPayload(response.DimseStatus));
       }
     }
   }
@@ -665,7 +668,7 @@
                                    "C-GET SCU to AET \"" +
                                    parameters_.GetRemoteModality().GetApplicationEntityTitle() +
                                    "\" has failed with DIMSE status " + DimseToHexString(rsp.msg.CGetRSP.DimseStatus),
-                                   rsp.msg.CGetRSP.DimseStatus);
+                                   MakeDimseErrorStatusPayload(rsp.msg.CGetRSP.DimseStatus));
           }
         }
         // Handle C-STORE Request
--- a/OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp	Fri Nov 14 15:12:16 2025 +0100
@@ -24,6 +24,7 @@
 
 #include "../PrecompiledHeaders.h"
 #include "DicomStoreUserConnection.h"
+#include "DimseErrorPayload.h"
 
 #include "../DicomParsing/FromDcmtkBridge.h"
 #include "../DicomParsing/ParsedDicomFile.h"
@@ -464,7 +465,7 @@
                              "C-STORE SCU to AET \"" +
                              GetParameters().GetRemoteModality().GetApplicationEntityTitle() +
                              "\" has failed with DIMSE status " + DimseToHexString(response.DimseStatus),
-                             response.DimseStatus);
+                             MakeDimseErrorStatusPayload(response.DimseStatus));
     }
   }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Sources/DicomNetworking/DimseErrorPayload.cpp	Fri Nov 14 15:12:16 2025 +0100
@@ -0,0 +1,57 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "DimseErrorPayload.h"
+#include <assert.h>
+
+namespace Orthanc
+{
+  static const char* const DIMSE_ERROR_PAYLOAD_TYPE = "DimseErrorPayload";
+  static const char* const DIMSE_ERROR_STATUS = "DimseErrorStatus";         // uint16_t
+
+  Json::Value MakeDimseErrorStatusPayload(uint16_t dimseErrorStatus)
+  {
+    Json::Value payload;
+    payload[ERROR_PAYLOAD_TYPE] = DIMSE_ERROR_PAYLOAD_TYPE;
+    payload[DIMSE_ERROR_STATUS] = dimseErrorStatus;
+    return payload;
+  }
+
+  bool IsDimseErrorStatusPayload(const Json::Value& payload)
+  {
+    assert(payload.isMember(ERROR_PAYLOAD_TYPE));
+    
+    return payload[ERROR_PAYLOAD_TYPE].asString() == DIMSE_ERROR_PAYLOAD_TYPE;
+  }
+
+  uint16_t GetDimseErrorStatusFromPayload(const Json::Value& payload)
+  {
+    assert(IsDimseErrorStatusPayload(payload));
+    assert(payload.isMember(DIMSE_ERROR_STATUS));
+
+    return static_cast<uint16_t>(payload[DIMSE_ERROR_STATUS].asUInt());
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Sources/DicomNetworking/DimseErrorPayload.h	Fri Nov 14 15:12:16 2025 +0100
@@ -0,0 +1,42 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_ENABLE_DCMTK_NETWORKING != 1
+#  error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be set to 1
+#endif
+
+#include "../OrthancException.h"
+
+
+namespace Orthanc
+{
+  Json::Value MakeDimseErrorStatusPayload(uint16_t dimseErrorStatus);
+
+  bool IsDimseErrorStatusPayload(const Json::Value& payload);
+
+  uint16_t GetDimseErrorStatusFromPayload(const Json::Value& payload);
+
+}
--- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Fri Nov 14 15:12:16 2025 +0100
@@ -1409,7 +1409,7 @@
       }
 
       CLOG(INFO, DICOM) << "Unknown DICOM tag: \"" << name << "\"";
-      throw OrthancException(ErrorCode_UnknownDicomTag, name, false);
+      throw OrthancException(ErrorCode_UnknownDicomTag, name, LogException_No);
     }
 #endif
   }
--- a/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp	Fri Nov 14 15:12:16 2025 +0100
@@ -966,12 +966,12 @@
     {
       throw OrthancException(ErrorCode_NotImplemented,
                              "The built-in DCMTK decoder cannot decode some DICOM instance "
-                             "whose transfer syntax is: " + std::string(GetTransferSyntaxUid(s)), false /* don't log here*/);
+                             "whose transfer syntax is: " + std::string(GetTransferSyntaxUid(s)), LogException_No);
     }
     else
     {
       throw OrthancException(ErrorCode_NotImplemented,
-                             "The built-in DCMTK decoder cannot decode some DICOM instance", false /* don't log here*/);
+                             "The built-in DCMTK decoder cannot decode some DICOM instance", LogException_No);
     }
   }
 
--- a/OrthancFramework/Sources/JobsEngine/IJob.h	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancFramework/Sources/JobsEngine/IJob.h	Fri Nov 14 15:12:16 2025 +0100
@@ -81,5 +81,7 @@
     virtual bool GetUserData(Json::Value& userData) const = 0;
 
     virtual void SetUserData(const Json::Value& userData) = 0;
+
+    virtual bool LookupErrorPayload(Json::Value& payload) const = 0;
   };
 }
--- a/OrthancFramework/Sources/JobsEngine/JobInfo.cpp	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancFramework/Sources/JobsEngine/JobInfo.cpp	Fri Nov 14 15:12:16 2025 +0100
@@ -196,9 +196,9 @@
       target["UserData"] = status_.GetUserData();
     }
 
-    if (status_.HasDimseErrorStatus())
+    if (status_.HasErrorPayload())
     {
-      target["DimseErrorStatus"] = status_.GetDimseErrorStatus();
+      target["ErrorPayload"] = status_.GetErrorPayload();
     }
 
     target["Type"] = status_.GetJobType();
--- a/OrthancFramework/Sources/JobsEngine/JobStatus.cpp	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancFramework/Sources/JobsEngine/JobStatus.cpp	Fri Nov 14 15:12:16 2025 +0100
@@ -34,9 +34,7 @@
     progress_(0),
     jobType_("Invalid"),
     publicContent_(Json::objectValue),
-    hasSerialized_(false),
-    hasDimseErrorStatus_(false),
-    dimseErrorStatus_(0x0000)
+    hasSerialized_(false)
   {
   }
 
@@ -62,14 +60,13 @@
   JobStatus::JobStatus(ErrorCode code,
                        const std::string& details,
                        const IJob& job,
-                       uint16_t dimseErrorStatus) :
+                       const Json::Value& errorPayload) :
     errorCode_(code),
     progress_(job.GetProgress()),
     publicContent_(Json::objectValue),
     hasSerialized_(false),
     details_(details),
-    hasDimseErrorStatus_(true),
-    dimseErrorStatus_(dimseErrorStatus)
+    errorPayload_(errorPayload)
   {
     InitInternal(job);
   }
@@ -82,9 +79,7 @@
     progress_(job.GetProgress()),
     publicContent_(Json::objectValue),
     hasSerialized_(false),
-    details_(details),
-    hasDimseErrorStatus_(false),
-    dimseErrorStatus_(0x0000)
+    details_(details)
   {
     InitInternal(job);
   }
--- a/OrthancFramework/Sources/JobsEngine/JobStatus.h	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancFramework/Sources/JobsEngine/JobStatus.h	Fri Nov 14 15:12:16 2025 +0100
@@ -26,6 +26,7 @@
 
 #include "IJob.h"
 #include "../OrthancException.h"
+#include <boost/shared_ptr.hpp>
 
 namespace Orthanc
 {
@@ -39,8 +40,7 @@
     Json::Value    serialized_;
     bool           hasSerialized_;
     std::string    details_;
-    bool           hasDimseErrorStatus_;
-    uint16_t       dimseErrorStatus_;
+    Json::Value    errorPayload_;
     Json::Value    userData_;
 
     void InitInternal(const IJob& job);
@@ -51,7 +51,7 @@
     JobStatus(ErrorCode code,
               const std::string& details,
               const IJob& job,
-              uint16_t dimseErrorStatus);
+              const Json::Value& errorPayload);
 
     JobStatus(ErrorCode code,
               const std::string& details,
@@ -109,19 +109,19 @@
       return userData_;
     }
 
-    bool HasDimseErrorStatus() const
+    bool HasErrorPayload() const
     {
-      return hasDimseErrorStatus_;
+      return !errorPayload_.isNull();
     }
 
-    uint16_t GetDimseErrorStatus() const
+    const Json::Value& GetErrorPayload() const
     {
-      if (!hasDimseErrorStatus_)
+      if (!HasErrorPayload())
       {
         throw OrthancException(ErrorCode_BadSequenceOfCalls);
       }
 
-      return dimseErrorStatus_;
+      return errorPayload_;
     }
   };
 }
--- a/OrthancFramework/Sources/JobsEngine/JobStepResult.cpp	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancFramework/Sources/JobsEngine/JobStepResult.cpp	Fri Nov 14 15:12:16 2025 +0100
@@ -32,9 +32,7 @@
   JobStepResult::JobStepResult() :
     code_(JobStepCode_Failure),
     timeout_(0),
-    error_(ErrorCode_InternalError),
-    hasDimseErrorStatus_(false),
-    dimseErrorStatus_(0x0000)
+    error_(ErrorCode_InternalError)
   {
   }
 
@@ -72,12 +70,10 @@
 
   JobStepResult JobStepResult::Failure(const ErrorCode& error,
                                        const char* details,
-                                       uint16_t dimseErrorStatus)
+                                       const Json::Value& errorPayload)
   {
     JobStepResult result = Failure(error, details);
-
-    result.hasDimseErrorStatus_ = true;
-    result.dimseErrorStatus_ = dimseErrorStatus;
+    result.errorPayload_ = errorPayload;
 
     return result;
   }
@@ -85,11 +81,11 @@
 
   JobStepResult JobStepResult::Failure(const OrthancException& exception)
   {
-    if (exception.HasDimseErrorStatus())
+    if (exception.HasPayload())
     {
       return Failure(exception.GetErrorCode(),
                      exception.HasDetails() ? exception.GetDetails() : NULL,
-                     exception.GetDimseErrorStatus());
+                     exception.GetPayload());
     }
     else
     {
@@ -142,19 +138,19 @@
     }
   }
 
-  bool JobStepResult::HasDimseErrorStatus() const
+  bool JobStepResult::HasErrorPayload() const
   {
-    return hasDimseErrorStatus_;
+    return !errorPayload_.isNull();
   }
 
-  uint16_t JobStepResult::GetDimseErrorStatus() const
+  const Json::Value& JobStepResult::GetErrorPayload() const
   {
-    if (!hasDimseErrorStatus_)
+    if (!HasErrorPayload())
     {
       throw OrthancException(ErrorCode_BadSequenceOfCalls);
     }
 
-    return dimseErrorStatus_;
+    return errorPayload_;
   }
 
 }
--- a/OrthancFramework/Sources/JobsEngine/JobStepResult.h	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancFramework/Sources/JobsEngine/JobStepResult.h	Fri Nov 14 15:12:16 2025 +0100
@@ -25,13 +25,14 @@
 #pragma once
 
 #include "../Enumerations.h"
-
+#include <boost/shared_ptr.hpp>
 #include <json/value.h>
 #include <stdint.h>
 
 namespace Orthanc
 {
   class OrthancException;
+  class IExceptionPayload;
   
   class ORTHANC_PUBLIC JobStepResult
   {
@@ -40,16 +41,12 @@
     unsigned int  timeout_;
     ErrorCode     error_;
     std::string   failureDetails_;
-    bool          hasDimseErrorStatus_;
-    uint16_t      dimseErrorStatus_;
-
+    Json::Value   errorPayload_;
     
     explicit JobStepResult(JobStepCode code) :
       code_(code),
       timeout_(0),
-      error_(ErrorCode_Success),
-      hasDimseErrorStatus_(false),
-      dimseErrorStatus_(0x0000)
+      error_(ErrorCode_Success)
     {
     }
 
@@ -67,7 +64,7 @@
 
     static JobStepResult Failure(const ErrorCode& error,
                                  const char* details,
-                                 uint16_t dimseErrorStatus);
+                                 const Json::Value& errorPayload);
 
     static JobStepResult Failure(const OrthancException& exception);
 
@@ -79,8 +76,8 @@
 
     const std::string& GetFailureDetails() const;
 
-    bool HasDimseErrorStatus() const;
+    bool HasErrorPayload() const;
 
-    uint16_t GetDimseErrorStatus() const;
+    const Json::Value& GetErrorPayload() const;
   };
 }
--- a/OrthancFramework/Sources/JobsEngine/JobsEngine.cpp	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancFramework/Sources/JobsEngine/JobsEngine.cpp	Fri Nov 14 15:12:16 2025 +0100
@@ -87,9 +87,9 @@
 
       case JobStepCode_Failure:
         running.GetJob().Stop(JobStopReason_Failure);
-        if (result.HasDimseErrorStatus())
+        if (result.HasErrorPayload())
         {
-          running.UpdateStatus(result.GetFailureCode(), result.GetFailureDetails(), result.GetDimseErrorStatus());
+          running.UpdateStatus(result.GetFailureCode(), result.GetFailureDetails(), result.GetErrorPayload());
         }
         else
         {
--- a/OrthancFramework/Sources/JobsEngine/JobsRegistry.cpp	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancFramework/Sources/JobsEngine/JobsRegistry.cpp	Fri Nov 14 15:12:16 2025 +0100
@@ -44,7 +44,7 @@
   static const char* ERROR_CODE = "ErrorCode";
   static const char* ERROR_DETAILS = "ErrorDetails";
   static const char* USER_DATA = "UserData";
-  static const char* DIMSE_ERROR_STATUS = "DimseErrorStatus";
+  static const char* ERROR_PAYLOAD = "ErrorPayload";
 
 
   class JobsRegistry::JobHandler : public boost::noncopyable
@@ -305,9 +305,9 @@
           target[USER_DATA] = userData;
         }
         
-        if (lastStatus_.HasDimseErrorStatus())
+        if (lastStatus_.HasErrorPayload())
         {
-          target[DIMSE_ERROR_STATUS] = lastStatus_.GetDimseErrorStatus();
+          target[ERROR_PAYLOAD] = lastStatus_.GetErrorPayload();
         }
 
         return true;
@@ -361,9 +361,9 @@
         job_->SetUserData(serialized[USER_DATA]);
       }
 
-      if (serialized.isMember(DIMSE_ERROR_STATUS))
+      if (serialized.isMember(ERROR_PAYLOAD))
       {
-        lastStatus_ = JobStatus(errorCode, details, *job_, static_cast<uint16_t>(serialized[DIMSE_ERROR_STATUS].asUInt()));
+        lastStatus_ = JobStatus(errorCode, details, *job_, serialized[ERROR_PAYLOAD]);
       }
       else
       {
@@ -896,9 +896,15 @@
             ErrorCode code = it->second->GetLastStatus().GetErrorCode();
             const std::string& details = it->second->GetLastStatus().GetDetails();
             
-            if (it->second->GetLastStatus().HasDimseErrorStatus())
+            // Prefer the error payload from the job level if there is one since it should contain more information than the step error payload.
+            Json::Value jobErrorPayload;
+            if (it->second->GetJob().LookupErrorPayload(jobErrorPayload))
             {
-              throw OrthancException(code, details, it->second->GetLastStatus().GetDimseErrorStatus());
+              throw OrthancException(code, details, jobErrorPayload);
+            }
+            else if (it->second->GetLastStatus().HasErrorPayload())
+            {
+              throw OrthancException(code, details, it->second->GetLastStatus().GetErrorPayload());
             }
             else if (!details.empty())
             {
@@ -1465,7 +1471,7 @@
 
   void JobsRegistry::RunningJob::UpdateStatus(ErrorCode code,
                                               const std::string& details,
-                                              uint16_t dimseErrorStatus)
+                                              const Json::Value& errorPayload)
   {
     if (!IsValid())
     {
@@ -1473,7 +1479,7 @@
     }
     else
     {
-      JobStatus status(code, details, *job_, dimseErrorStatus);
+      JobStatus status(code, details, *job_, errorPayload);
 
       boost::mutex::scoped_lock lock(registry_.mutex_);
       registry_.CheckInvariants();
--- a/OrthancFramework/Sources/JobsEngine/JobsRegistry.h	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancFramework/Sources/JobsEngine/JobsRegistry.h	Fri Nov 14 15:12:16 2025 +0100
@@ -246,7 +246,7 @@
 
       void UpdateStatus(ErrorCode code,
                         const std::string& details,
-                        uint16_t dimseErrorStatus);
+                        const Json::Value& errorPayload);
     };
   };
 }
--- a/OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.h	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.h	Fri Nov 14 15:12:16 2025 +0100
@@ -150,5 +150,10 @@
     {
       return false;
     }
+
+    virtual bool LookupErrorPayload(Json::Value& payload) const ORTHANC_OVERRIDE
+    {
+      return false;
+    }
   };
 }
--- a/OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h	Fri Nov 14 15:12:16 2025 +0100
@@ -133,5 +133,10 @@
       return false;
     }
 
+    virtual bool LookupErrorPayload(Json::Value& payload) const ORTHANC_OVERRIDE
+    {
+      return false;
+    }
+
   };
 }
--- a/OrthancFramework/Sources/OrthancException.cpp	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancFramework/Sources/OrthancException.cpp	Fri Nov 14 15:12:16 2025 +0100
@@ -33,86 +33,87 @@
   OrthancException::OrthancException(const OrthancException& other) : 
     errorCode_(other.errorCode_),
     httpStatus_(other.httpStatus_),
-    logged_(false),
-    hasDimseErrorStatus_(other.hasDimseErrorStatus_),
-    dimseErrorStatus_(other.dimseErrorStatus_)
+    logged_(false)
   {
     if (other.details_.get() != NULL)
     {
       details_.reset(new std::string(*other.details_));
     }
+
+    // TODO: shouldn't we avoid copies of OrthancException completely ?
+    if (other.HasPayload())
+    {
+      payload_.reset(new Json::Value(other.GetPayload()));
+    }
   }
 
   OrthancException::OrthancException(ErrorCode errorCode) : 
     errorCode_(errorCode),
     httpStatus_(ConvertErrorCodeToHttpStatus(errorCode)),
-    logged_(false),
-    hasDimseErrorStatus_(false),
-    dimseErrorStatus_(0x0000)
+    logged_(false)
   {
   }
 
   OrthancException::OrthancException(ErrorCode errorCode,
                                      const std::string& details,
-                                     bool log) :
+                                     LogException log) :
     errorCode_(errorCode),
     httpStatus_(ConvertErrorCodeToHttpStatus(errorCode)),
-    logged_(log),
-    details_(new std::string(details)),
-    hasDimseErrorStatus_(false),
-    dimseErrorStatus_(0x0000)
+    logged_(log == LogException_Yes),
+    details_(new std::string(details))
   {
 #if ORTHANC_ENABLE_LOGGING == 1
-    if (log)
+    if (log == LogException_Yes)
     {
       LOG(ERROR) << EnumerationToString(errorCode_) << ": " << details;
     }
 #endif
   }
 
+
+  OrthancException::OrthancException(ErrorCode errorCode,
+                                     HttpStatus httpStatus) :
+    errorCode_(errorCode),
+    httpStatus_(httpStatus),
+    logged_(false)
+  {
+  }
+
+
   OrthancException::OrthancException(ErrorCode errorCode,
                                      const std::string& details,
-                                     uint16_t dimseErrorStatus,
-                                     bool log) :
+                                     const Json::Value& payload,
+                                     LogException log) :
     errorCode_(errorCode),
     httpStatus_(ConvertErrorCodeToHttpStatus(errorCode)),
-    logged_(log),
-    details_(new std::string(details)),
-    hasDimseErrorStatus_(true),
-    dimseErrorStatus_(dimseErrorStatus)
+    logged_(log == LogException_Yes),
+    details_(new std::string(details))
   {
+    payload_.reset(new Json::Value(payload));
+
 #if ORTHANC_ENABLE_LOGGING == 1
-    if (log)
+    if (log == LogException_Yes)
     {
       LOG(ERROR) << EnumerationToString(errorCode_) << ": " << details;
     }
 #endif
   }
 
-  OrthancException::OrthancException(ErrorCode errorCode,
-                                     HttpStatus httpStatus) :
-    errorCode_(errorCode),
-    httpStatus_(httpStatus),
-    logged_(false),
-    hasDimseErrorStatus_(false),
-    dimseErrorStatus_(0x0000)
-  {
-  }
 
   OrthancException::OrthancException(ErrorCode errorCode,
                                      HttpStatus httpStatus,
                                      const std::string& details,
-                                     uint16_t dimseErrorStatus,
-                                     bool log) :
+                                     const Json::Value& payload,
+                                     LogException log) :
     errorCode_(errorCode),
     httpStatus_(httpStatus),
-    logged_(log),
-    details_(new std::string(details)),
-    hasDimseErrorStatus_(true),
-    dimseErrorStatus_(dimseErrorStatus)
+    logged_(log == LogException_Yes),
+    details_(new std::string(details))
   {
+    payload_.reset(new Json::Value(payload));
+
 #if ORTHANC_ENABLE_LOGGING == 1
-    if (log)
+    if (log == LogException_Yes)
     {
       LOG(ERROR) << EnumerationToString(errorCode_) << ": " << details;
     }
@@ -122,16 +123,14 @@
   OrthancException::OrthancException(ErrorCode errorCode,
                                      HttpStatus httpStatus,
                                      const std::string& details,
-                                     bool log) :
+                                     LogException log) :
     errorCode_(errorCode),
     httpStatus_(httpStatus),
-    logged_(log),
-    details_(new std::string(details)),
-    hasDimseErrorStatus_(false),
-    dimseErrorStatus_(0x0000)
+    logged_(log == LogException_Yes),
+    details_(new std::string(details))
   {
 #if ORTHANC_ENABLE_LOGGING == 1
-    if (log)
+    if (log == LogException_Yes)
     {
       LOG(ERROR) << EnumerationToString(errorCode_) << ": " << details;
     }
@@ -170,19 +169,19 @@
     }
   }
 
-  bool OrthancException::HasDimseErrorStatus() const
+  bool OrthancException::HasPayload() const
   {
-    return hasDimseErrorStatus_;
+    return payload_.get() != NULL;
   }
 
-  uint16_t OrthancException::GetDimseErrorStatus() const
+  const Json::Value& OrthancException::GetPayload() const
   {
-    if (!hasDimseErrorStatus_)
+    if (payload_.get() == NULL)
     {
       throw OrthancException(ErrorCode_BadSequenceOfCalls);
     }
 
-    return dimseErrorStatus_;
+    return *payload_;
   }
 
   bool OrthancException::HasBeenLogged() const
--- a/OrthancFramework/Sources/OrthancException.h	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancFramework/Sources/OrthancException.h	Fri Nov 14 15:12:16 2025 +0100
@@ -27,11 +27,24 @@
 #include "Compatibility.h"  // For std::unique_ptr<>
 #include "Enumerations.h"
 #include "OrthancFramework.h"
-
+#include <json/value.h>
 #include <stdint.h>  // For uint16_t
 
 namespace Orthanc
 {
+  static const char* const ERROR_PAYLOAD_TYPE = "Type";
+
+  // From 1.12.10, we use this enumeration instead of a bool to avoid implicit
+  // conversions to bool in OrthancException constructors because any type is
+  // automaticaly casted to a bool without a single warning which led to wrong 
+  // constructor flavors being called.
+  enum ORTHANC_PUBLIC LogException
+    {
+      LogException_Yes,
+      LogException_No
+    };
+
+
   class ORTHANC_PUBLIC OrthancException
   {
   private:
@@ -47,8 +60,7 @@
     std::unique_ptr<std::string>  details_;
     
     // New in Orthanc 1.12.10
-    bool       hasDimseErrorStatus_;
-    uint16_t   dimseErrorStatus_;
+    std::unique_ptr<Json::Value>  payload_;
     
   public:
     OrthancException(const OrthancException& other);
@@ -57,12 +69,12 @@
 
     OrthancException(ErrorCode errorCode,
                      const std::string& details,
-                     bool log = true);
+                     LogException log = LogException_Yes);
 
     OrthancException(ErrorCode errorCode,
                      const std::string& details,
-                     uint16_t dimseErrorStatus,
-                     bool log = true);
+                     const Json::Value& payload,
+                     LogException log = LogException_Yes);
 
     OrthancException(ErrorCode errorCode,
                      HttpStatus httpStatus);
@@ -70,13 +82,14 @@
     OrthancException(ErrorCode errorCode,
                      HttpStatus httpStatus,
                      const std::string& details,
-                     bool log = true);
+                     LogException log = LogException_Yes);
+
 
     OrthancException(ErrorCode errorCode,
                      HttpStatus httpStatus,
                      const std::string& details,
-                     uint16_t dimseErrorStatus,
-                     bool log = true);
+                     const Json::Value& payload,
+                     LogException log = LogException_Yes);
 
     ErrorCode GetErrorCode() const;
 
@@ -90,8 +103,8 @@
 
     bool HasBeenLogged() const;
 
-    bool HasDimseErrorStatus() const;
-    
-    uint16_t GetDimseErrorStatus() const;
+    bool HasPayload() const;
+
+    const Json::Value& GetPayload() const;
   };
 }
--- a/OrthancFramework/Sources/SystemToolbox.cpp	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancFramework/Sources/SystemToolbox.cpp	Fri Nov 14 15:12:16 2025 +0100
@@ -229,7 +229,7 @@
     {
       throw OrthancException(ErrorCode_RegularFileExpected,
                              "The path does not point to a regular file: " + PathToUtf8(path),
-                             log);
+                             (log ? LogException_Yes : LogException_No));
     }
 
     try
@@ -239,7 +239,7 @@
       if (!f.good())
       {
         throw OrthancException(ErrorCode_InexistentFile, "File not found: " + PathToUtf8(path),
-                               log);
+                               (log ? LogException_Yes : LogException_No));
       }
 
       std::streamsize size = GetStreamSize(f);
--- a/OrthancFramework/UnitTestsSources/JobsTests.cpp	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancFramework/UnitTestsSources/JobsTests.cpp	Fri Nov 14 15:12:16 2025 +0100
@@ -147,6 +147,11 @@
     {
       return false;
     }
+
+    virtual bool LookupErrorPayload(Json::Value& payload) const ORTHANC_OVERRIDE
+    {
+      return false;
+    }
   };
 
 
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Fri Nov 14 15:12:16 2025 +0100
@@ -1455,7 +1455,7 @@
           {
             throw OrthancException(static_cast<ErrorCode>(error),
                                    GetErrorDetails(),
-                                   IsLogDetails());
+                                   (IsLogDetails() ? LogException_Yes : LogException_No));
           }
           else
           {
--- a/OrthancServer/Plugins/Engine/PluginsJob.h	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancServer/Plugins/Engine/PluginsJob.h	Fri Nov 14 15:12:16 2025 +0100
@@ -95,6 +95,11 @@
       return false;
     }
 
+    virtual bool LookupErrorPayload(Json::Value& payload) const ORTHANC_OVERRIDE
+    {
+      return false;
+    }
+
   };
 }
 
--- a/OrthancServer/Sources/ServerJobs/ArchiveJob.h	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancServer/Sources/ServerJobs/ArchiveJob.h	Fri Nov 14 15:12:16 2025 +0100
@@ -153,5 +153,10 @@
       }
       return false;
     }
+
+    virtual bool LookupErrorPayload(Json::Value& payload) const ORTHANC_OVERRIDE
+    {
+      return false;
+    }
   };
 }
--- a/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.cpp	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.cpp	Fri Nov 14 15:12:16 2025 +0100
@@ -24,6 +24,7 @@
 #include "DicomGetScuJob.h"
 
 #include "../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
+#include "../../../OrthancFramework/Sources/DicomNetworking/DimseErrorPayload.h"
 #include "../../../OrthancFramework/Sources/SerializationToolbox.h"
 #include "../ServerContext.h"
 #include <dcmtk/dcmnet/dimse.h>
@@ -100,8 +101,12 @@
     }
     catch (OrthancException& e)
     {
-      dimseErrorStatus_ = e.GetDimseErrorStatus();
-      throw e;
+      if (e.HasPayload() && IsDimseErrorStatusPayload(e.GetPayload()))
+      {
+        dimseErrorStatus_ = GetDimseErrorStatusFromPayload(e.GetPayload());
+      }
+
+      throw;
     }
     return true;
   }
@@ -368,4 +373,14 @@
       currentCommand_->AddReceivedInstance(instanceId);
     }
   }
+
+  bool DicomRetrieveScuBaseJob::LookupErrorPayload(Json::Value& payload) const
+  {
+    Json::Value publicContent;
+
+    GetPublicContent(publicContent);
+    payload = publicContent["Details"];
+    
+    return true;
+  }
 }
--- a/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.h	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.h	Fri Nov 14 15:12:16 2025 +0100
@@ -173,5 +173,8 @@
     static void AddReceivedInstanceFromCStore(uint16_t originatorMessageId, 
                                               const std::string& originatorAet, 
                                               const std::string& instanceId);
+
+
+    virtual bool LookupErrorPayload(Json::Value& payload) const ORTHANC_OVERRIDE;
   };
 }
--- a/OrthancServer/Sources/ServerJobs/ThreadedSetOfInstancesJob.h	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancServer/Sources/ServerJobs/ThreadedSetOfInstancesJob.h	Fri Nov 14 15:12:16 2025 +0100
@@ -174,5 +174,10 @@
     virtual void SetUserData(const Json::Value& userData) ORTHANC_OVERRIDE;
 
     virtual bool GetUserData(Json::Value& userData) const ORTHANC_OVERRIDE;
+
+    virtual bool LookupErrorPayload(Json::Value& payload) const ORTHANC_OVERRIDE
+    {
+      return false;
+    }
   };
 }
--- a/OrthancServer/Sources/main.cpp	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancServer/Sources/main.cpp	Fri Nov 14 15:12:16 2025 +0100
@@ -714,9 +714,9 @@
         message["Details"] = exception.GetDetails();
       }
 
-      if (exception.HasDimseErrorStatus())
+      if (exception.HasPayload())
       {
-        message["DimseErrorStatus"] = exception.GetDimseErrorStatus();
+        message["ErrorPayload"] = exception.GetPayload();
       }
 
       std::string info = message.toStyledString();
--- a/OrthancServer/UnitTestsSources/ServerJobsTests.cpp	Thu Nov 13 06:32:48 2025 +0100
+++ b/OrthancServer/UnitTestsSources/ServerJobsTests.cpp	Fri Nov 14 15:12:16 2025 +0100
@@ -151,6 +151,11 @@
     {
       return false;
     }
+
+    virtual bool LookupErrorPayload(Json::Value& payload) const ORTHANC_OVERRIDE
+    {
+      return false;
+    }
   };
 
 
@@ -226,6 +231,11 @@
     {
       return false;
     }
+
+    virtual bool LookupErrorPayload(Json::Value& payload) const ORTHANC_OVERRIDE
+    {
+      return false;
+    }
   };