diff --git a/src/backend/common/misc/PciTopology.h b/src/backend/common/misc/PciTopology.h
index dcfacb201..4f362bc4a 100644
--- a/src/backend/common/misc/PciTopology.h
+++ b/src/backend/common/misc/PciTopology.h
@@ -27,7 +27,7 @@
 #define XMRIG_PCITOPOLOGY_H
 
 
-#include <stdio.h>
+#include <cstdio>
 
 
 #include "base/tools/String.h"
@@ -40,19 +40,29 @@ class PciTopology
 {
 public:
     PciTopology() = default;
-    PciTopology(uint32_t bus, uint32_t device, uint32_t function) : bus(bus), device(device), function(function) {}
+    PciTopology(uint32_t bus, uint32_t device, uint32_t function) : m_bus(bus), m_device(device), m_function(function) {}
 
-    uint32_t bus        = 0;
-    uint32_t device     = 0;
-    uint32_t function   = 0;
+    inline bool isValid() const         { return m_bus >= 0; }
+    inline uint32_t bus() const         { return isValid() ? m_bus : 0; }
+    inline uint32_t device() const      { return m_device; }
+    inline uint32_t function() const    { return m_function; }
 
     String toString() const
     {
+        if (!isValid()) {
+            return "n/a";
+        }
+
         char *buf = new char[8]();
-        snprintf(buf, 8, "%02x:%02x.%01x", bus, device, function);
+        snprintf(buf, 8, "%02x:%02x.%01x", bus(), device(), function());
 
         return buf;
     }
+
+private:
+    int32_t m_bus         = -1;
+    uint32_t m_device     = 0;
+    uint32_t m_function   = 0;
 };
 
 
diff --git a/src/backend/opencl/OclBackend.cpp b/src/backend/opencl/OclBackend.cpp
index 65c3ded9c..4820ce086 100644
--- a/src/backend/opencl/OclBackend.cpp
+++ b/src/backend/opencl/OclBackend.cpp
@@ -150,7 +150,7 @@ public:
         for (const OclDevice &device : devices) {
             Log::print(GREEN_BOLD(" * ") WHITE_BOLD("%-13s") CYAN_BOLD("#%zu") YELLOW(" %s") " %s " WHITE_BOLD("%uMHz") " cu:" WHITE_BOLD("%u") " mem:" CYAN("%zu/%zu") " MB", "OPENCL GPU",
                        device.index(),
-                       device.hasTopology() ? device.topology().toString().data() : "n/a",
+                       device.topology().toString().data(),
                        device.printableName().data(),
                        device.clock(),
                        device.computeUnits(),
@@ -177,7 +177,7 @@ public:
                        CYAN_BOLD("%3u") " |" CYAN_BOLD("%3s") " |" CYAN_BOLD("%3u") " |" CYAN("%5zu") " | %s",
                        i,
                        data.thread.index(),
-                       data.device.hasTopology() ? data.device.topology().toString().data() : "n/a",
+                       data.device.topology().toString().data(),
                        data.thread.intensity(),
                        data.thread.worksize(),
                        data.thread.stridedIndex(),
@@ -285,7 +285,7 @@ void xmrig::OclBackend::printHashrate(bool details)
                     Hashrate::format(hashrate()->calc(i, Hashrate::MediumInterval), num + 8,     sizeof num / 3),
                     Hashrate::format(hashrate()->calc(i, Hashrate::LargeInterval),  num + 8 * 2, sizeof num / 3),
                     data.device.index(),
-                    data.device.hasTopology() ? data.device.topology().toString().data() : "n/a",
+                    data.device.topology().toString().data(),
                     data.device.printableName().data()
                     );
 
@@ -401,6 +401,8 @@ rapidjson::Value xmrig::OclBackend::toJSON(rapidjson::Document &doc) const
         thread.AddMember("affinity", data.affinity, allocator);
         thread.AddMember("hashrate", hashrate()->toJSON(i, doc), allocator);
 
+        data.device.toJSON(thread, doc);
+
         i++;
         threads.PushBack(thread, allocator);
     }
diff --git a/src/backend/opencl/wrappers/OclDevice.cpp b/src/backend/opencl/wrappers/OclDevice.cpp
index f63387173..f94954033 100644
--- a/src/backend/opencl/wrappers/OclDevice.cpp
+++ b/src/backend/opencl/wrappers/OclDevice.cpp
@@ -142,16 +142,14 @@ xmrig::OclDevice::OclDevice(uint32_t index, cl_device_id id, cl_platform_id plat
         topology_amd topology;
 
         if (OclLib::getDeviceInfo(id, 0x4037 /* CL_DEVICE_TOPOLOGY_AMD */, sizeof(topology), &topology, nullptr) == CL_SUCCESS && topology.raw.type == 1) {
-            m_topology    = true;
-            m_pciTopology = PciTopology(static_cast<uint32_t>(topology.pcie.bus), static_cast<uint32_t>(topology.pcie.device), static_cast<uint32_t>(topology.pcie.function));
+            m_topology = PciTopology(static_cast<uint32_t>(topology.pcie.bus), static_cast<uint32_t>(topology.pcie.device), static_cast<uint32_t>(topology.pcie.function));
         }
     }
     else if (m_vendorId == OCL_VENDOR_NVIDIA) {
         cl_uint bus = 0;
         if (OclLib::getDeviceInfo(id, 0x4008 /* CL_DEVICE_PCI_BUS_ID_NV */, sizeof (bus), &bus, nullptr) == CL_SUCCESS) {
-            m_topology    = true;
             cl_uint slot  = OclLib::getUint(id, 0x4009 /* CL_DEVICE_PCI_SLOT_ID_NV */);
-            m_pciTopology = PciTopology(bus, (slot >> 3) & 0xff, slot & 7);
+            m_topology = PciTopology(bus, (slot >> 3) & 0xff, slot & 7);
         }
     }
 }
@@ -205,3 +203,18 @@ void xmrig::OclDevice::generate(const Algorithm &algorithm, OclThreads &threads)
         }
     }
 }
+
+
+#ifdef XMRIG_FEATURE_API
+void xmrig::OclDevice::toJSON(rapidjson::Value &out, rapidjson::Document &doc) const
+{
+    using namespace rapidjson;
+    auto &allocator = doc.GetAllocator();
+
+    out.AddMember("board",       board().toJSON(doc), allocator);
+    out.AddMember("name",        name().toJSON(doc), allocator);
+    out.AddMember("bus-id",      topology().toString().toJSON(doc), allocator);
+    out.AddMember("cu",          computeUnits(), allocator);
+    out.AddMember("global-mem",  globalMemSize(), allocator);
+}
+#endif
diff --git a/src/backend/opencl/wrappers/OclDevice.h b/src/backend/opencl/wrappers/OclDevice.h
index 517fa2bdb..3be58ed22 100644
--- a/src/backend/opencl/wrappers/OclDevice.h
+++ b/src/backend/opencl/wrappers/OclDevice.h
@@ -69,10 +69,9 @@ public:
     uint32_t clock() const;
     void generate(const Algorithm &algorithm, OclThreads &threads) const;
 
-    inline bool hasTopology() const             { return m_topology; }
     inline bool isValid() const                 { return m_id != nullptr && m_platform != nullptr; }
     inline cl_device_id id() const              { return m_id; }
-    inline const PciTopology &topology() const  { return m_pciTopology; }
+    inline const PciTopology &topology() const  { return m_topology; }
     inline const String &board() const          { return m_board.isNull() ? m_name : m_board; }
     inline const String &name() const           { return m_name; }
     inline const String &vendor() const         { return m_vendor; }
@@ -81,8 +80,11 @@ public:
     inline uint32_t computeUnits() const        { return m_computeUnits; }
     inline uint32_t index() const               { return m_index; }
 
+#   ifdef XMRIG_FEATURE_API
+    void toJSON(rapidjson::Value &out, rapidjson::Document &doc) const;
+#   endif
+
 private:
-    bool m_topology                 = false;
     cl_device_id m_id               = nullptr;
     cl_platform_id m_platform       = nullptr;
     const String m_board;
@@ -91,7 +93,7 @@ private:
     const uint32_t m_computeUnits   = 1;
     const uint32_t m_index          = 0;
     OclVendor m_vendorId            = OCL_VENDOR_UNKNOWN;
-    PciTopology m_pciTopology;
+    PciTopology m_topology;
     Type m_type                     = Unknown;
 };