Skip to content

Documentation

SDKs

Call the DeviceQuest REST API from Go or JavaScript/TypeScript with typed clients that handle authentication, pagination and errors.

Overview

DeviceQuest provides two client libraries for the REST API:

SDKPackageClient
Gogithub.com/devicequest/platform/sdk/godevicequest.NewClient
JavaScript / TypeScript@devicequest/sdkDeviceQuestClient

Both SDKs send your API key in the X-API-Key header, read the pagination headers for you, and turn error responses into typed errors. The API key needs the scopes of the endpoints you call. See Scopes.

Set the base URL to the address you use to open DeviceQuest, without /api/v1; the SDKs add the path:

export DEVICEQUEST_BASE_URL="https://devicequest.example.com"
export DEVICEQUEST_API_KEY="<your 64-character API key>"

Go

Install

go get github.com/devicequest/platform/sdk/go

The package name is devicequest.

Create a client

client, err := devicequest.NewClient(
	os.Getenv("DEVICEQUEST_API_KEY"),
	devicequest.WithBaseURL(os.Getenv("DEVICEQUEST_BASE_URL")),
	devicequest.WithTimeout(60*time.Second),
)
if err != nil {
	log.Fatal(err)
}
OptionDefaultDescription
WithBaseURL(url)http://localhost:8081Address of your DeviceQuest installation. Always set this.
WithTimeout(d)30 secondsTimeout for each request.
WithTransport(rt)Go defaultA custom http.RoundTripper, for example for a proxy or custom CA.

NewClient returns an error if the API key is empty. The client doesn’t follow redirects.

Resource accessors

AccessorMethodsREST endpoints
client.Assets()List(ctx, *AssetListOptions), Get(ctx, id), Search(ctx, query, *AssetListOptions), ExportCSV(ctx)/assets, /assets/{id}, /assets/search, /assets/export
client.Vulnerabilities()List(ctx, *VulnerabilityListOptions), Get(ctx, id), ExportCSV(ctx)/vulnerabilities, /vulnerabilities/{id}, /vulnerabilities/export
client.CloudResources()List(ctx, *ListOptions)/cloud-resources
client.Flows()List(ctx, *ListOptions)/flows
client.Reports()List(ctx, *ListOptions), Get(ctx, id), Generate(ctx, *GenerateReportInput)/reports, /reports/{id}, POST /reports
client.Scans()List(ctx, *ScanListOptions), Trigger(ctx, *ScanInput), Cancel(ctx, scanID)/scans, POST /scans, DELETE /scans/{id}
client.Recognition()Recognize(ctx, RecognitionRequest), RecognizeByMAC(ctx, mac), RecognizeByIP(ctx, ip), RecognizeByFingerprint(ctx, map[string]any), ListDeviceTypes(ctx)/recognition/*

List methods return the page of items and a *Pagination with Total, Page, PerPage, TotalPages and NextCursor, read from the X-Total-Count, X-Page, X-Per-Page, X-Total-Pages and X-Next-Cursor headers. Pass nil options for the first page at the default size; Limit and Offset page through the rest.

Example

package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"os"

	devicequest "github.com/devicequest/platform/sdk/go"
)

func main() {
	ctx := context.Background()

	client, err := devicequest.NewClient(
		os.Getenv("DEVICEQUEST_API_KEY"),
		devicequest.WithBaseURL(os.Getenv("DEVICEQUEST_BASE_URL")),
	)
	if err != nil {
		log.Fatal(err)
	}

	// Read every asset, 200 at a time.
	opts := &devicequest.AssetListOptions{Limit: 200}
	for {
		assets, page, err := client.Assets().List(ctx, opts)
		if err != nil {
			log.Fatal(err)
		}
		for _, a := range assets {
			fmt.Println(a.ID, a.Name, a.IPAddress, a.RiskScore)
		}
		if page.NextCursor == "" {
			break
		}
		opts.Offset += opts.Limit
	}

	// Queue a scan of an existing scan target (needs the write scope).
	scan, err := client.Scans().Trigger(ctx, &devicequest.ScanInput{
		Type:   "sweep",
		Target: "8a3e5c71-2f4b-4d9a-b6e8-1c0d7f2a9e45",
	})
	var apiErr *devicequest.APIError
	switch {
	case errors.As(err, &apiErr):
		log.Fatalf("scan rejected: %d %s: %s", apiErr.StatusCode, apiErr.Code, apiErr.Message)
	case err != nil:
		log.Fatal(err)
	}
	fmt.Println("queued scan", scan.ScanID, scan.Status)
}

Errors

Any response with status 300 or higher returns a *devicequest.APIError with StatusCode, Code (the error field, such as forbidden or not_found) and Message. Network failures and timeouts return the underlying Go error. See Errors for the codes.

JavaScript and TypeScript

Install

npm install @devicequest/sdk

The package ships CommonJS, ES module and TypeScript type definitions. It uses the standard fetch API, so it runs on Node.js 18 or later and in other runtimes with fetch.

Create a client

import { DeviceQuestClient } from '@devicequest/sdk';

const client = new DeviceQuestClient({
  baseUrl: process.env.DEVICEQUEST_BASE_URL!,
  apiKey: process.env.DEVICEQUEST_API_KEY!,
  timeout: 60_000,
});
OptionDefaultDescription
baseUrlhttp://localhost:8081Address of your DeviceQuest installation. Only the origin (scheme, host and port) is used.
apiKeyYour API key. Required.
timeout30000Timeout for each request, in milliseconds.
headers{}Extra headers to send with every request.

You can also write new DeviceQuestClient(apiKey, { baseUrl }) or createClient({ baseUrl, apiKey }). client.setApiKey(key) switches to another key, for example after a rotation.

Resource clients

PropertyMethodsREST endpoints
client.assetslist(options?), get(id), search(query, options?), exportCSV()/assets, /assets/{id}, /assets/search, /assets/export
client.vulnerabilitieslist(options?), get(id), exportCSV()/vulnerabilities, /vulnerabilities/{id}, /vulnerabilities/export
client.cloudResourceslist(options?)/cloud-resources
client.flowslist(options?)/flows
client.reportslist(options?), get(id), create(input)/reports, /reports/{id}, POST /reports
client.scanslist(options?), create(input), cancel(id)/scans, POST /scans, DELETE /scans/{id}
client.recognitionrecognize(input), recognizeByMAC(mac), recognizeByIP(ip), recognizeByFingerprint(fingerprint), listDeviceTypes()/recognition/*

options is { limit?, offset? }. List methods return a Page<T>: { data, total, page, perPage, totalPages, nextCursor? }.

Example

import { ApiError, DeviceQuestClient } from '@devicequest/sdk';

const client = new DeviceQuestClient({
  baseUrl: process.env.DEVICEQUEST_BASE_URL!,
  apiKey: process.env.DEVICEQUEST_API_KEY!,
});

async function main(): Promise<void> {
  // Read every asset, 200 at a time.
  let offset = 0;
  for (;;) {
    const page = await client.assets.list({ limit: 200, offset });
    for (const asset of page.data) {
      console.log(asset.id, asset.name, asset.ipAddress, asset.riskScore);
    }
    if (!page.nextCursor) break;
    offset += 200;
  }

  // Queue a report (needs the write scope) and check its status later.
  const report = await client.reports.create({
    name: 'Weekly inventory',
    type: 'inventory_summary',
    format: 'pdf',
  });
  console.log(report.id, report.status);
}

main().catch((err: unknown) => {
  if (err instanceof ApiError) {
    console.error(`DeviceQuest API error ${err.statusCode} ${err.code ?? ''}: ${err.message}`);
  } else {
    console.error(err);
  }
  process.exit(1);
});

Errors

A non-2xx response throws an ApiError with statusCode, code (the error field) and message. A request that exceeds the timeout is aborted and throws an AbortError.