diff --git a/docs/sphinx/using/backends/cloud/qbraid.rst b/docs/sphinx/using/backends/cloud/qbraid.rst
index dfa72e53913..bfb0d89cd44 100644
--- a/docs/sphinx/using/backends/cloud/qbraid.rst
+++ b/docs/sphinx/using/backends/cloud/qbraid.rst
@@ -5,9 +5,17 @@ qBraid
`qBraid `__ is a cloud platform that brokers access to
quantum simulators and hardware from multiple vendors through a single API.
-CUDA-Q can submit OpenQASM 2 jobs to any device exposed by the qBraid service.
-See the `qBraid device catalog `__ for the
-set of simulators and QPUs currently available.
+CUDA-Q submits OpenQASM 2 jobs to gate-based devices exposed by the qBraid
+service. See the `qBraid device catalog `__
+for the set of simulators and QPUs currently available.
+
+.. note::
+
+ Only gate-based (gate-model) devices are supported through this target.
+ qBraid also brokers analog devices, such as analog Hamiltonian simulation
+ (AHS) QPUs, which cannot execute the gate-based kernels CUDA-Q emits.
+ Selecting such a device (for example, ``aws:quera:qpu:aquila``) is rejected
+ when the target is configured.
Setting Credentials
```````````````````
diff --git a/python/tests/backends/test_qbraid.py b/python/tests/backends/test_qbraid.py
index e8e2a6f0ce0..81a2eb457d2 100644
--- a/python/tests/backends/test_qbraid.py
+++ b/python/tests/backends/test_qbraid.py
@@ -174,6 +174,28 @@ def test_qbraid_machine_alternative_device():
assert len(counts) >= 1
+def test_qbraid_rejects_analog_device():
+ """An analog/AHS device (paradigm != gate_model) is rejected up front.
+
+ The helper queries device metadata when the target is configured and
+ refuses a non-gate-model device, surfacing an actionable error at
+ set_target time instead of an opaque downstream job failure.
+ """
+ with pytest.raises(RuntimeError, match="gate-model"):
+ _set_qbraid_target(machine="aws:quera:qpu:aquila")
+
+
+def test_qbraid_accepts_gate_model_device():
+ """A gate-model device passes the paradigm check and executes normally."""
+ _set_qbraid_target(machine="aws:aws:sim:sv1")
+ kernel = cudaq.make_kernel()
+ qubit = kernel.qalloc()
+ kernel.h(qubit)
+ kernel.mz(qubit)
+ counts = cudaq.sample(kernel)
+ assert len(counts) >= 1
+
+
def _arm_result_status(code: int):
"""Force the next /result call on the mock to return the given HTTP code.
diff --git a/python/tests/utils/mock_qpu/qbraid/__init__.py b/python/tests/utils/mock_qpu/qbraid/__init__.py
index 7fea100bec5..fa9061c31e6 100644
--- a/python/tests/utils/mock_qpu/qbraid/__init__.py
+++ b/python/tests/utils/mock_qpu/qbraid/__init__.py
@@ -229,6 +229,49 @@ async def resetTestState():
return {"reset": True}
+def _paradigm_for_device(device_qrn: str) -> str:
+ """Infer a device paradigm from its QRN for testing.
+
+ Mirrors qBraid's `paradigm` enum (gate_model | analog | annealing | other).
+ Analog/AHS devices (e.g. QuEra Aquila, Pasqal Fresnel) report "analog";
+ everything else defaults to "gate_model".
+ """
+ qrn = device_qrn.lower()
+ if any(tok in qrn for tok in ("quera", "aquila", "pasqal", "fresnel")):
+ return "analog"
+ return "gate_model"
+
+
+# v2 API: GET /devices/{device_qrn}
+@app.get("/devices/{device_qrn}")
+async def getDevice(
+ device_qrn: str = Path(...),
+ x_api_key: Optional[str] = Header(None, alias="X-API-KEY"),
+):
+ """Retrieve device metadata (v2 API).
+
+ The helper queries this at target-initialization time to confirm the
+ selected device can run gate-based programs before submitting any job.
+ """
+ if x_api_key is None:
+ raise HTTPException(status_code=401, detail="API key is required")
+
+ paradigm = _paradigm_for_device(device_qrn)
+ return {
+ "success": True,
+ "data": {
+ "qrn": device_qrn,
+ "name": device_qrn,
+ "vendor": "qbraid",
+ "deviceType": "SIMULATOR" if "sim" in device_qrn.lower() else "QPU",
+ "paradigm": paradigm,
+ "status": "ONLINE",
+ "numberQubits": 30,
+ "runInputTypes": ["qasm2"],
+ },
+ }
+
+
# v2 API: GET /jobs/{job_qrn}
@app.get("/jobs/{job_id}")
async def getJob(
diff --git a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp
index c981c055b4a..7bd53ab889a 100644
--- a/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp
+++ b/runtime/cudaq/platform/default/rest/helpers/qbraid/QbraidServerHelper.cpp
@@ -74,6 +74,12 @@ class QbraidServerHelper : public ServerHelper {
parseConfigForCommonParams(config);
+ // qBraid brokers multiple paradigms under one target, so reject devices
+ // that can't run gate-based (QASM/QIR) programs. Skipped under emulation.
+ const bool emulate = getValueOrDefault(config, "emulate", "") == "true";
+ if (!emulate && !backendConfig["api_key"].empty())
+ validateDeviceParadigm(backendConfig["device_id"]);
+
cudaq::info("qBraid configuration initialized:");
for (const auto &[key, value] : backendConfig) {
if (key == "api_key") {
@@ -362,6 +368,44 @@ class QbraidServerHelper : public ServerHelper {
return headers;
}
+ /// @brief Throws if @p deviceId is not a gate-model device, per its qBraid
+ /// metadata (GET /devices/{qrn}). Lookup failures are non-fatal: we log and
+ /// defer to the backend, which rejects incompatible programs at submission.
+ void validateDeviceParadigm(const std::string &deviceId) {
+ nlohmann::json deviceJson;
+ try {
+ RestClient client;
+ auto headers = getHeaders();
+ const auto devicePath = backendConfig.at("url") + "/devices/" + deviceId;
+ cudaq::info("Verifying device paradigm via {}", devicePath);
+ deviceJson = client.get("", devicePath, headers, true);
+ } catch (const std::exception &e) {
+ cudaq::info("Could not verify paradigm for device '{}' ({}); deferring "
+ "compatibility check to job submission.",
+ deviceId, e.what());
+ return;
+ }
+
+ // qBraid v2 wraps the device document under a `data` envelope.
+ if (!deviceJson.contains("data") ||
+ !deviceJson["data"].contains("paradigm") ||
+ !deviceJson["data"]["paradigm"].is_string()) {
+ cudaq::info("Device metadata for '{}' contained no paradigm field; "
+ "skipping paradigm validation.",
+ deviceId);
+ return;
+ }
+
+ const auto paradigm = deviceJson["data"]["paradigm"].get();
+ if (paradigm != "gate_model") {
+ throw std::runtime_error(
+ "qBraid device '" + deviceId + "' has paradigm '" + paradigm +
+ "', which cannot run gate-based CUDA-Q kernels. The 'qbraid' target "
+ "supports only gate-model devices.");
+ }
+ cudaq::info("Device '{}' paradigm is gate_model.", deviceId);
+ }
+
/// @brief Helper method to retrieve the value of an environment variable.
std::string getEnvVar(const std::string &key, const std::string &defaultVal,
const bool isRequired) const {
diff --git a/unittests/nvqpp/backends/qbraid/QbraidTester.cpp b/unittests/nvqpp/backends/qbraid/QbraidTester.cpp
index 9ce3e6d49c6..08c6f052bb0 100644
--- a/unittests/nvqpp/backends/qbraid/QbraidTester.cpp
+++ b/unittests/nvqpp/backends/qbraid/QbraidTester.cpp
@@ -9,6 +9,7 @@
#include "CUDAQTestUtils.h"
#include "common/FmtCore.h"
#include "common/RestClient.h"
+#include "common/ServerHelper.h"
#include "cudaq/algorithm.h"
#include
#include
@@ -302,6 +303,23 @@ CUDAQ_TEST(QbraidTester, checkResultServerErrorRetries) {
EXPECT_GE(counts.size(), 1u);
}
+// An analog/AHS device must be rejected at initialize() time, mirroring the
+// Python test_qbraid_rejects_analog_device. Driven through the registry so the
+// non-default machine can be supplied directly (the shared target config used
+// by the other tests pins the default gate-model device).
+CUDAQ_TEST(QbraidTester, checkAnalogDeviceRejected) {
+ auto helper = cudaq::registry::get("qbraid");
+ ASSERT_TRUE(helper);
+ cudaq::BackendConfig config;
+ config["emulate"] = "false";
+ config["url"] = "http://localhost:" + mockPort;
+ config["machine"] = "aws:quera:qpu:aquila";
+ config["api_key"] = "00000000000000000000000000000000";
+ EXPECT_TRUE(
+ throwsWithMessage([&]() { helper->initialize(config); },
+ "device 'aws:quera:qpu:aquila' has paradigm 'analog'"));
+}
+
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
auto ret = RUN_ALL_TESTS();