Skip to content

Documentation

REST API reference

Every REST endpoint with its method, path, scope and purpose, plus pagination, errors, rate limits and request bodies.

Overview

The REST API gives scripts and integrations access to assets, vulnerabilities, cloud resources, flows, reports and scans, plus device recognition and the Partner API. Requests and responses use JSON (CSV for exports, the report file for report downloads).

  • Base URL: https://<your DeviceQuest host>/api/v1
  • Authentication: an API key in the X-API-Key header. See Authentication.
  • Scope: decided by the HTTP method. See Scopes.
  • Tenant: taken from the API key. Every endpoint returns only your organisation’s data.

The Developer page in the app also lists these endpoints in its REST API Reference section, with a search box and the required scope of each endpoint.

Endpoints

Assets

MethodPathScopePurpose
GET/assetsreadList assets, newest lastSeen first. Paginated. Filters: type, search.
GET/assets/searchreadSearch assets with q. Paginated.
GET/assets/exportreadDownload all assets as CSV. Requires format=csv.
GET/assets/{id}readGet one asset.
GET/assets/{id}/exportreadAlias of /assets/export; returns the same all-assets CSV.

Query parameters:

ParameterEndpointDescription
type/assetsExact asset type: device, virtual_machine, hypervisor, cluster, datacenter, datastore, cloud_resource, ot_device, software, saas_account or user.
search/assetsCase-insensitive text matched anywhere in the asset ID, hostname, IP address or serial number.
q/assets/searchSame matching as search.
limit, offset/assets, /assets/searchSee Pagination.
format/assets/exportMust be csv.

Asset object:

{
  "id": "6f1c2b9e-4d3a-4f8e-9b7a-2c5d8e1f0a3b",
  "name": "web-01.example.local",
  "assetType": "device",
  "ipAddress": "10.20.0.15",
  "macAddress": "00:1b:21:3a:4f:5c",
  "os": "Ubuntu 22.04.4 LTS",
  "riskScore": 7.4,
  "lastSeen": "2026-10-13T05:42:10Z",
  "tags": ["production", "web"]
}

riskScore uses the same risk algorithm as the app; an asset with no open vulnerabilities scores 0. Empty strings mean the value was not collected.

curl -H "X-API-Key: $DEVICEQUEST_API_KEY" \
  "$DEVICEQUEST_BASE_URL/api/v1/assets?type=device&search=web&limit=25"

Vulnerabilities

MethodPathScopePurpose
GET/vulnerabilitiesreadList vulnerability findings (one per CVE and asset), highest CVSS first. Paginated.
GET/vulnerabilities/exportreadDownload findings as CSV. Requires format=csv.
GET/vulnerabilities/{id}readGet one finding.

Query parameters for /vulnerabilities:

ParameterDescription
severitycritical, high, medium, low or informational. Matches the finding’s effective severity, including any severity override set in the app.
cvss_min, cvss_maxInclusive CVSS score bounds, for example cvss_min=7.0.
fixedtrue lists resolved findings. Without it (or with any other value) the list contains open findings.
limit, offsetSee Pagination.

Vulnerability object:

{
  "id": "1b7e0c3d-9a52-4f1e-8c6d-3e2f1a0b9c87",
  "cveId": "CVE-2024-6387",
  "severity": "high",
  "cvssScore": 8.1,
  "description": "A signal handler race condition was found in OpenSSH's server (sshd)...",
  "assetId": "6f1c2b9e-4d3a-4f8e-9b7a-2c5d8e1f0a3b",
  "affectedAssets": 1
}
curl -H "X-API-Key: $DEVICEQUEST_API_KEY" \
  "$DEVICEQUEST_BASE_URL/api/v1/vulnerabilities?severity=critical&cvss_min=9&limit=50"

Cloud resources

MethodPathScopePurpose
GET/cloud-resourcesreadList discovered cloud resources across AWS, Azure and Google Cloud. Paginated.

Each item has id, provider (AWS, Azure or GCP), name, type (compute, database, storage, kubernetes or function), region and status. The response also carries meta.count (the total) and meta.tenant.

curl -H "X-API-Key: $DEVICEQUEST_API_KEY" \
  "$DEVICEQUEST_BASE_URL/api/v1/cloud-resources?limit=100"
{
  "data": [
    { "id": "5e2d7c1a-8b4f-4a3e-9d6c-0f1b2a3c4d5e", "provider": "AWS", "name": "db-prod-01", "type": "database", "region": "eu-central-1", "status": "available" }
  ],
  "meta": { "count": 1, "tenant": "00000000-0000-0000-0000-000000000001" }
}

Flows

MethodPathScopePurpose
GET/flowsreadList automation flows with their trigger type and last run time. Paginated.

Each item has id, name, description, enabled, triggerType and lastTriggeredAt (RFC 3339, or an empty string if the flow never ran). The response also carries meta.count and meta.tenant.

curl -H "X-API-Key: $DEVICEQUEST_API_KEY" "$DEVICEQUEST_BASE_URL/api/v1/flows"

Reports

MethodPathScopePurpose
GET/reportsreadList report definitions with the status of their latest generation. Paginated.
GET/reports/{id}readGet one report.
POST/reportswriteCreate a report and queue its first generation.
GET/reports/{id}/downloadreadDownload the latest generated file of a report.

Report object:

{
  "id": "4f6a2b8c-3d5e-4f6a-9b8c-7d6e5f4a3b2c",
  "name": "Weekly inventory",
  "type": "inventory_summary",
  "format": "pdf",
  "status": "generating",
  "createdAt": "2026-10-13T06:00:00Z"
}
statusMeaning
pendingThe report exists but has not been generated yet.
generatingA generation is queued or running.
availableThe latest generation succeeded. GET /reports/{id}/download returns the file.
failedThe latest generation failed.

Create a report

POST /reports body:

FieldTypeRequiredDescription
namestringYesReport name. Must not be blank.
typestringYesinventory_summary, compliance_report, asset_audit, software_license, topology, budget_forecast or custom.
formatstringNocsv (default), pdf, excel or xml. excel produces an .xlsx workbook.
asset_idsarray of stringsNoLimit the report to these asset UUIDs.
periodstringNoReporting period, stored with the report’s filter.
curl -X POST \
  -H "X-API-Key: $DEVICEQUEST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Weekly inventory", "type": "inventory_summary", "format": "pdf"}' \
  "$DEVICEQUEST_BASE_URL/api/v1/reports"

The response is 201 Created with the report object and "status": "generating". DeviceQuest generates the report in the background, usually within about a minute, and stores the result as a snapshot.

Download a report

GET /reports/{id}/download returns the latest snapshot as a file:

ResponseMeaning
200 OKThe most recent successful snapshot, as a file. Content-Type matches the format (application/pdf, text/csv, application/xml or the .xlsx type). Content-Disposition names the file report-<id>-<UTC timestamp>.<ext>. X-Report-Snapshot-Id and X-Report-Generated-At identify the snapshot.
409 Conflict, error: report_not_readyThe first generation is still running. The response has Retry-After: 60; try again after that many seconds.
404 Not Found, error: not_foundThe report doesn’t exist, or has never produced a file.
# Poll until the file is ready, then save it.
until curl -sf -o weekly-inventory.pdf \
  -H "X-API-Key: $DEVICEQUEST_API_KEY" \
  "$DEVICEQUEST_BASE_URL/api/v1/reports/4f6a2b8c-3d5e-4f6a-9b8c-7d6e5f4a3b2c/download"
do
  sleep 60
done

Scans

MethodPathScopePurpose
GET/scansreadList scan jobs, with their status. Paginated.
POST/scanswriteQueue a scan of an existing scan target.
DELETE/scans/{id}adminCancel a queued or running scan.

Scan object:

{
  "scanId": "2d0c8f4e-6b1a-4e7d-9c3f-5a8b7e6d4c21",
  "status": "pending",
  "startedAt": "",
  "message": "Scan job queued for target 10.20.0.0/24"
}

status is pending, running, completed, failed or cancelled. startedAt is empty until the scan starts. For failed scans, message holds the error.

Start a scan

POST /scans body:

FieldTypeRequiredDescription
typestringYesScan type: sweep, ssh, snmp, wmi, vmware or hypervisor (Proxmox). See Scan types.
targetstringYesThe UUID of an existing scan target, not a hostname or IP address. Create targets on the Targets tab (see Targets).

Rules:

  • The target must be runnable with the requested type. An IP range (CIDR) target needs sweep; the other types scan one host at a time.
  • Credentials come from the credentials configured in DeviceQuest for the target. The request has no credential fields.
  • DeviceQuest queues the job as pending; the scan scheduler picks it up and runs it.
curl -X POST \
  -H "X-API-Key: $DEVICEQUEST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "sweep", "target": "8a3e5c71-2f4b-4d9a-b6e8-1c0d7f2a9e45"}' \
  "$DEVICEQUEST_BASE_URL/api/v1/scans"

The response is 201 Created with the scan object. An unknown target returns 404; an unsupported type or a target/type mismatch returns 400 invalid_request with the reason.

Cancel a scan

curl -X DELETE -H "X-API-Key: $DEVICEQUEST_API_KEY" \
  "$DEVICEQUEST_BASE_URL/api/v1/scans/2d0c8f4e-6b1a-4e7d-9c3f-5a8b7e6d4c21"

The response is 200 OK with {"message": "Scan cancelled successfully"}. A scan that already finished (completed, failed or cancelled) returns 409 conflict.

Device recognition

The recognition endpoints identify a device type and vendor from network signals such as a MAC address, IP address or fingerprint. They evaluate the signals you send; they don’t look up your asset inventory (use GET /assets/search?q=<ip> for that).

All recognition endpoints need the recognition:read scope (or admin), whatever the method.

MethodPathScopePurpose
POST/recognition/recognizerecognition:readRecognise a device from any combination of signals.
POST/recognition/recognize/macrecognition:readRecognise a device from its MAC address.
POST/recognition/recognize/iprecognition:readRecognise a device from its IP address.
POST/recognition/recognize/fingerprintrecognition:readRecognise a device from fingerprint attributes.
GET/recognition/device-typesrecognition:readList the device types recognition can return.

Request bodies:

EndpointBody
/recognizeAny of mac_address, ip_address, vendor, model, hostname and fingerprints (object). At least one of mac_address, ip_address or fingerprints is required; vendor, model and hostname refine the result.
/recognize/mac{"mac_address": "00:1b:21:3a:4f:5c"} (required, must be a valid MAC address).
/recognize/ip{"ip_address": "10.20.0.1"} (required, must be a valid IPv4 or IPv6 address).
/recognize/fingerprint{"fingerprint": {...}} with at least one attribute. vendor, model and protocol keys are used as hints.
curl -X POST \
  -H "X-API-Key: $DEVICEQUEST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"mac_address": "00:00:0c:12:34:56", "vendor": "Cisco", "hostname": "core-sw-01"}' \
  "$DEVICEQUEST_BASE_URL/api/v1/recognition/recognize"

Response:

{
  "device_type": "switch",
  "vendor": "Cisco",
  "model": "",
  "confidence": 0.9,
  "matches": [
    { "device_type": "switch", "vendor": "Cisco", "model": "", "confidence": 0.3, "source": "mac_oui" }
  ],
  "source": "fallback"
}

confidence runs from 0 to 1; 0.8 and above is a high-confidence match. GET /recognition/device-types returns {"device_types": ["workstation", "server", "router", "switch", "firewall", "printer", "camera", "sensor", "gateway", "access_point", "load_balancer", "storage", "unknown"]}.

Partner API

The Partner API is for Builder Partners who provision and manage DeviceQuest for their customers. Send exactly one of these headers:

HeaderKeyActs as
X-Partner-KeyA partner key, pk_ followed by 64 hexadecimal characters.The partner the key belongs to, limited to the key’s scopes.
X-API-KeyA tenant API key with the admin scope.Your tenant: it manages the partners your tenant owns.

A request with both headers gets 400. A partner key that is unknown, revoked or expired, or whose partner has the API turned off, gets 401 with the message Invalid or expired credentials.

Getting started. With your tenant admin X-API-Key, create a partner (POST /partners) and issue its first partner key (POST /partners/{id}/keys). Give that key to the partner; from then on the partner uses X-Partner-Key.

Partner keys. A partner key is pk_ plus 32 random bytes in hex. DeviceQuest shows it once, in the api_key field of the create response, and stores only its SHA-256 hash and a short display prefix. Every key needs at least one scope.

What each key can reach.

  • Creating, listing and deleting partners needs the tenant admin X-API-Key; a partner key gets 403. GET /partners returns your tenant’s partners.

  • On the per-partner routes (/partners/{id}/...), a partner key works only on its own partner. Any other {id} returns 404 with partner not found. A tenant admin key works on the partners its tenant owns.

  • Partner key scopes:

    ScopeAllows
    partner:readView the partner, its keys and its quota.
    partner:writeCreate and revoke partner keys. Includes partner:read.
    webhook:readView the partner’s webhooks.
    webhook:writeRegister and delete the partner’s webhooks. Includes webhook:read.

    A partner key can issue new keys only with scopes it holds itself.

MethodPathPartner key scopePurpose
POST/partnersTenant admin keyCreate a partner owned by your tenant. Body: name, slug, email (required), quota_limit (default 10000 assets), rate_limit (default 100 requests per minute). A slug already in use returns 409.
GET/partnersTenant admin keyList your tenant’s partners. Query: limit (1 to 100, default 20), offset. Returns data, total, limit, offset.
GET/partners/{id}partner:readGet a partner.
DELETE/partners/{id}Tenant admin keyDelete a partner owned by your tenant.
POST/partners/{id}/keyspartner:writeCreate a partner key. Body: name, scopes (required, at least one), expires_in_days. Returns the key once in api_key.
GET/partners/{id}/keyspartner:readList the partner’s keys (display prefix only).
DELETE/partners/{id}/keys/{keyId}partner:writeRevoke a partner key.
POST/partners/{id}/webhookswebhook:writeRegister a partner webhook. Body: name, url, events (required), secret, headers.
GET/partners/{id}/webhookswebhook:readList the partner’s webhooks.
DELETE/partners/{id}/webhooks/{webhookId}webhook:writeDelete a partner webhook.
GET/partners/{id}/quota/{tenantId}partner:readQuota usage for the partner’s own tenant: asset_count, quota_limit, quota_used_pct, requests_today, rate_limit, reset_at. Any other tenant ID returns 404.

Partner and quota responses include tenant_id, the tenant that owns the partner.

# Tenant admin: create a partner, then issue its first partner key.
curl -X POST \
  -H "X-API-Key: $DEVICEQUEST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Acme MSP", "slug": "acme-msp", "email": "[email protected]"}' \
  "$DEVICEQUEST_BASE_URL/api/v1/partners"

curl -X POST \
  -H "X-API-Key: $DEVICEQUEST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Acme automation", "scopes": ["partner:write", "webhook:write"], "expires_in_days": 90}' \
  "$DEVICEQUEST_BASE_URL/api/v1/partners/<partner id>/keys"

# Partner: read its own partner record with the partner key.
curl -H "X-Partner-Key: $DEVICEQUEST_PARTNER_KEY" \
  "$DEVICEQUEST_BASE_URL/api/v1/partners/<partner id>"

Pagination

List endpoints accept limit and offset:

ParameterDefaultRules
limit501 to 1000. Values outside that range use the default.
offset00 or more. Number of items to skip.

Paginated responses wrap the items in data and describe the page in headers:

HeaderMeaning
X-Total-CountTotal number of items that match your filters.
X-Total-PagesNumber of pages at the current limit.
X-PageCurrent page number, starting at 1.
X-Per-PageThe limit in effect.
X-Next-Cursoroffset=<n> for the next page. Absent on the last page.

Read every page in a loop:

offset=0
while :; do
  headers=$(mktemp)
  curl -s -D "$headers" -o "assets-$offset.json" \
    -H "X-API-Key: $DEVICEQUEST_API_KEY" \
    "$DEVICEQUEST_BASE_URL/api/v1/assets?limit=500&offset=$offset"
  next=$(grep -i '^x-next-cursor:' "$headers" | sed 's/.*offset=//' | tr -d '\r')
  rm -f "$headers"
  [ -z "$next" ] && break
  offset=$next
done

Exports

/assets/export?format=csv and /vulnerabilities/export?format=csv return a CSV file (Content-Type: text/csv, Content-Disposition: attachment; filename=assets.csv or vulnerabilities.csv).

  • Any other format returns 400 with error: invalid_format.
  • An export holds at most 10,000 rows. When there are more, the response has the header X-Export-Truncated: true; page through the list endpoint to read everything.
  • Values that a spreadsheet would run as a formula (starting with =, +, -, @, a tab or a carriage return) are prefixed with an apostrophe, so files are safe to open in Excel or Google Sheets.
curl -H "X-API-Key: $DEVICEQUEST_API_KEY" -o assets.csv \
  "$DEVICEQUEST_BASE_URL/api/v1/assets/export?format=csv"

Errors

Errors return an HTTP status and a flat JSON body with a machine-readable error code and a readable message:

{
  "error": "invalid_request",
  "message": "'target' must be the UUID of an existing scan target"
}
StatuserrorMeaning
400invalid_requestThe body is not valid JSON, a required field is missing, or a value is not allowed. The message names the field.
400invalid_formatAn export was requested without format=csv.
401unauthorizedMissing, unknown, revoked or expired API key.
403forbiddenThe key lacks the scope this request needs.
404not_foundThe resource doesn’t exist in your organisation.
409conflictThe request conflicts with the resource’s state, for example cancelling a finished scan.
409report_not_readyThe report is still generating. Retry after the Retry-After seconds.
429rate_limit_exceededToo many requests. See Rate limits.
500internal_errorDeviceQuest could not complete the request. Retry later; if it persists, contact your administrator.

Rate limits

Each API key can make 100 requests per minute, counted across all REST endpoints. DeviceQuest counts requests by the X-API-Key header; requests without that header (for example Partner API requests that use X-Partner-Key) are counted by client IP address.

Every response carries the current state:

HeaderMeaning
X-RateLimit-LimitRequests allowed per window (100).
X-RateLimit-RemainingRequests left in the current window.
X-RateLimit-ResetWhen the window resets, as a Unix timestamp in seconds.

When you go over the limit, DeviceQuest answers 429 Too Many Requests:

{
  "error": "rate_limit_exceeded",
  "message": "Rate limit exceeded. Try again later.",
  "retry_after": 41.87
}

retry_after is the number of seconds to wait (may include a fraction). Slow down when X-RateLimit-Remaining gets low, and wait until X-RateLimit-Reset after a 429.