# SecuriThings Developer Center
> Complete documentation for Large Language Models
---
## Document: Welcome
URL: /welcome
# Welcome
import { Card, CardHeader, CardTitle, CardDescription } from "zudoku/ui/Card";
import { Link } from "zudoku/components";
The SecuriThings API enables developers and AI agents to access data, execute tasks, automate device
workflows, and integrate with third-party systems.
## What you can build
- **Monitoring and data retrieval.** Read device, site, and compliance data across your fleet, and
feed it into your own tools.
- **Automation and tasks.** Execute remediation tasks and automate device workflows programmatically.
- **Integrations with third-party systems.** Connect SecuriThings to the platforms and services your
team already runs on.
## Start here
Authentication
Get credentials and exchange them for a token.
API Fundamentals
Understand the conventions that apply before you call an endpoint.
Use CasesLearn what you can do with the API.API Reference
Find every endpoint, parameter, and response in one place.
## Contact
For questions and support, speak to our [support team](mailto:cs_engineers@securithings.com).
Want to learn more about the SecuriThings solution? Reach out to our
[sales team](mailto:info@securithings.com).
:::rocket
The SecuriThings Developer Center evolves based on your needs. We continuously add new API
endpoints, Documentation, and Use Cases driven by customer feedback.
:::
---
## Document: Changelog
URL: /changelog
# Changelog
## 8 October 2026 - New Compliance Fields and Filters Now Available
The SecuriThings API now includes additional compliance fields in the `GET /v1/devices` and `GET /v1/devices/{id}` endpoints,
giving you visibility into each device's vulnerability severity level, firmware version suggested by the management platform, replacement model suggested by the vendor, password expiry, and SSL and 802.1X certificate expiry and compliance status.
Corresponding query parameters were also added to `GET /v1/devices`, so you can filter devices by these fields. For more details, view the full API Reference.
## 27 September 2026 - Firmware Upgrade Task Type Now Available
The SecuriThings API now supports the Firmware Upgrade task type through the `POST /v1/tasks` endpoint,
letting you execute firmware upgrades on one or more devices directly through the API.
Two new endpoints support it: `GET /v1/devices/{id}/targetFirmware` returns the approved firmware versions a specific device can be upgraded to,
and `GET /v1/firmwarePaths` returns all firmware upgrade paths defined in the system and requires Admin permissions.
For more details, view the full API Reference.
## 11 August 2026 - New Device Fields and Filters Now Available
The SecuriThings API now includes additional fields in the `GET /v1/devices` and `GET /v1/devices/{id}` endpoints,
giving you more visibility into each device directly through the API: onboarding date, maintenance mode, last hardened, recording status, storage server, patched firmware version, and orphaned status.
Corresponding query parameters were also added to `GET /v1/devices`, so you can filter devices by these fields. For more details, view the full API Reference.
## 29 July 2026 - Onboard Task Type Now Available
The SecuriThings API now supports the Onboard task type through the `POST /v1/tasks` endpoint,
letting you onboard discovered devices into your managed inventory directly through the API.
For more details, view the full API Reference.
## 29 July 2026 - Get Discovered Devices Now Available
The SecuriThings API now includes a new `GET /v1/discoveredDevices` endpoint,
giving you visibility into all discovered devices and their onboarding status directly through the API.
For more details, view the full API Reference.
## 20 July 2026 - Execute Device Tasks Now Available
The SecuriThings API now lets you execute device tasks on one or more devices through the new `POST /v1/tasks endpoint`,
and track their execution status through the new `GET /v1/tasks/{id}` endpoint, giving you full control over device tasks directly through the API.
Three task types are currently supported: Restart, Password Rotation, and SSL Certificate Rotation.
Using the `POST /v1/tasks` endpoint requires Admin or Operator permissions. For more details, view the full API Reference.
## 19 May 2026 - Get Device Passwords Metadata Now Available
The SecuriThings API now includes a new endpoint under the Devices category - `GET /v1/devices/{id}/passwords`.
This endpoint returns password metadata for all users associated with a specific device. Note that actual password values are not included in the response.
For more details, view the full API Reference.
## 19 May 2026 - Get Device Certificates Now Available
The SecuriThings API now includes a new endpoint under the Devices category - `GET /v1/devices/{id}/certificates`.
This endpoint returns full certificate data, including the certificate itself, for all certificate types associated with a specific device, currently SSL and 802.1x, in a single response.
For more details, view the full API Reference.
## 30 April 2026 - Site Status Now Available in Get Sites and Get Site By ID
The SecuriThings API now includes a new `status` field in the `GET /v1/sites` and `GET /v1/sites/{id}` endpoints,
giving you visibility into the connectivity status of each site directly through the API.
You can also filter results by status using the new status query parameter.
## 16 Oct 2025 - Filter Devices by Available Task!
We’re excited to introduce new query parameters for the GET /v1/devices endpoint that let you filter devices by whether certain tasks are available.
This makes it easier to quickly find devices that can perform tasks like password rotation, firmware upgrades, device hardening, rotate SSL certificate and restart device.
You can now use the following query parameters in your requests:
GET `/v1/devices/?availableTask.passwordRotation=1` This request returns only devices where firmware upgrade is available.
## 8 Sep 2025 - Fetch Device Vulnerabilities with Our New Endpoint
We’ve just released a new endpoint that makes it easier to retrieve vulnerability data for any specific device.
With GET `/devices/{id}/vulnerabilities` you can fetch all known vulnerabilities tied to a device.
This endpoint helps you quickly assess security risks and integrate vulnerability insights into your own monitoring and reporting systems.
## 24 Jun 2025 - New Probe information available!
We are excited to announce that the SecuriThings API now provides additional information about probes, enhancing your ability to monitor and manage devices effectively.
This update includes the following new fields for probes:
- `probe.agent`: Indicates the status of the agent probe.
- `probe.deviceSource`: Indicates the status of the device source probe.
- `probe.edge`: Indicates the status of the edge probe.
- `probe.host`: Indicates the status of the host probe.
- `probe.app`: Indicates the status of the app probe.
You can also filter devices based on these probe statuses using the `probe.agent`, `probe.deviceSource`, `probe.edge`, `probe.host`, and `probe.app` query parameters. The possible values for these fields are `up`, `down`, `unknown`, or `unmonitored`.
These fields provide valuable insights into the connectivity and health of your devices, allowing you to make informed decisions and take proactive measures to ensure optimal performance.
---
## Document: Monitoring
URL: /use-cases/monitoring
# Monitoring
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "zudoku/ui/Accordion";
API endpoints used in this page
- [`GET /v1/devices`](/api/devices)
- [`GET /v1/devices/{id}`](/api/devices)
- [`GET /v1/sites`](/api/sites)
The API exposes the operational state of every device and site, so fleet health can be delivered to
the systems your teams already operate, on a schedule you define, and combined with data held
outside SecuriThings.
## Scenarios
- **NOC dashboards.** Forward device and site state into the dashboards and monitoring stack your
NOC already operates.
- **Custom alerting.** Evaluate conditions such as offline for longer than a set period, or a single
probe down across an entire site, and raise them in your own alerting system.
- **CMDB correlation.** Join device state with records held outside SecuriThings, such as asset
ownership, maintenance windows, or support contracts.
- **SIEM ingestion.** Deliver device state into your SIEM alongside your other security telemetry.
- **Availability reporting.** Responses describe current state, so recording them on a schedule
produces the availability and trend reporting your organization requires.
- **Automated response.** Pair a detection with an
[executed task](/use-cases/device-remediation) to remediate without manual intervention.
## Device connectivity
`status` reports the connectivity of a device, and `lastSeen` reports the last time the device was
not offline, that is, the last time it was `online` or `partial`. Filter server-side rather than
retrieving the full inventory:
```bash
curl "https://api.securithings.com/v1/devices?status=offline&limit=100" \
-H "Accept: application/json" \
-H "Authorization: Bearer "
```
```json
{
"name": "Headquarters camera1",
"status": "partial",
"lastSeen": "2026-07-09T11:20:00Z",
"maintenanceMode": false,
"probe": {
"agent": "unmonitored",
"deviceSource": "down",
"edge": "up",
"host": "up",
"app": "up"
}
}
```
A `status` of `partial` indicates that some, but not all, of the device's probes are reporting. The
`probe` fields identify which component is affected, and each one is also available as a filter.
:::info
`maintenanceMode` identifies devices under planned work. Exclude them when generating alerts to
avoid reporting expected downtime.
:::
## Site connectivity
Sites expose their own `status`, so connectivity per location can be retrieved without paging
through every device:
```bash
curl "https://api.securithings.com/v1/sites?status=offline&limit=100" \
-H "Accept: application/json" \
-H "Authorization: Bearer "
```
---
## Document: Automating Device Remediation
URL: /use-cases/device-remediation
# Automating Device Remediation
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "zudoku/ui/Accordion";
API endpoints used in this page
- [`GET /v1/devices`](/api/devices)
- [`POST /v1/tasks`](/api/tasks)
- [`GET /v1/tasks/{id}`](/api/tasks)
The API executes remediation tasks across one or more devices and reports the outcome of each, so
remediation can be driven from the systems and workflows your teams already operate, or built into
new workflows and automations of your own.
## Scenarios
- **Scheduled credential rotation.** Rotate device passwords across the fleet on a defined schedule
from your orchestration system.
- **Ticket-driven remediation.** Execute a task from your ITSM or ticketing tool and post the result
back to the ticket.
- **Certificate lifecycle automation.** Rotate SSL certificates ahead of expiry as part of your
certificate management process.
- **Response to monitoring.** Execute remediation automatically from a condition detected through
[Monitoring](/use-cases/monitoring).
## Check task availability
Not every device supports every task. The `availableTask` object on a device reports which tasks can
be executed on it, so filter the target devices before executing:
```json
"availableTask": {
"hardenDevice": false,
"restartDevice": true,
"rotate8021xCertificate": false,
"rotatePassword": true,
"rotateSslCertificate": true,
"upgradeFirmware": true
}
```
## Execute a task
A single request executes a task across any number of devices, and returns the `taskId` used to
track it:
```bash
curl -X POST "https://api.securithings.com/v1/tasks" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer " \
-d '{
"taskType": "restartDevice",
"devices": [
{ "deviceId": "9ff559ee-38b6-11f1-81cb-465864a35d0a" },
{ "deviceId": "dc7d4ef1-6bf9-11f1-b083-d27bafcacdf8" }
]
}'
```
```json
{ "taskId": "23bpozz1mpjjedtr" }
```
Tasks run asynchronously. The execution and polling pattern, the available task types, and the
per-task-type payloads are described in [Asynchronous Tasks](/guides/async-tasks).
## Verify the outcome
Poll `GET /v1/tasks/{id}` until every device task has a `status` of `completed`, then read the
`result` of each device task. Device tasks that did not succeed can be retried independently of the
rest.
```bash
curl "https://api.securithings.com/v1/tasks/23bpozz1mpjjedtr" \
-H "Accept: application/json" \
-H "Authorization: Bearer "
```
---
## Document: Manage Device Inventory
URL: /use-cases/device-inventory
# Manage Device Inventory
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "zudoku/ui/Accordion";
API endpoints used in this page
- [`GET /v1/discoveredDevices`](/api/discovered-devices)
- [`POST /v1/tasks`](/api/tasks)
- [`GET /v1/tasks/{id}`](/api/tasks)
- [`GET /v1/devices`](/api/devices)
The discovered devices inventory is populated from the device sources connected to SecuriThings, and
it is updated continuously as additional devices are discovered. A discovered device must be
onboarded before it can be managed through the SecuriThings platform.
This guide covers retrieving discovered devices and onboarding them into your managed inventory.
## Managed vs discovered
- **Managed devices** (`GET /v1/devices`) are managed through the SecuriThings platform.
- **Discovered devices** (`GET /v1/discoveredDevices`) come from the connected device sources and
are not managed until they are onboarded.
## Scenarios
- **Discovery monitoring.** Track newly reported devices and their onboarding status over time.
- **Bulk onboarding.** Onboard every device that is ready to onboard in a single task.
- **Onboarding backlog reporting.** Report devices that require enrichment before they can be
onboarded.
## List devices ready to onboard
Filter discovered devices by `onboardingStatus` to retrieve only the devices that can be onboarded.
Omit the filter to retrieve all discovered devices, or filter by `deviceSources.id`, `deviceType`,
`vendor`, `discoveredAfter`, or `discoveredBefore`.
```bash
curl "https://api.securithings.com/v1/discoveredDevices?onboardingStatus=readyToOnboard&limit=100" \
-H "Accept: application/json" \
-H "Authorization: Bearer "
```
## Onboard devices
Onboarding is an [asynchronous task](/guides/async-tasks). Collect the IDs of devices whose
`onboardingStatus` is `readyToOnboard`, then execute an `onboardDevice` task:
```bash
curl -X POST "https://api.securithings.com/v1/tasks" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer " \
-d '{
"taskType": "onboardDevice",
"devices": [
{ "deviceId": "9ff559ee-38b6-11f1-81cb-465864a35d0a" },
{ "deviceId": "dc7d4ef1-6bf9-11f1-b083-d27bafcacdf8" }
]
}'
```
## Verify the task succeeded
The outcome of the onboarding request is reported by the task. Poll `GET /v1/tasks/{id}` until every
device task has a `status` of `completed`, then read the `result` of each device task. A `result` of
`succeeded` means the device was onboarded. See [Asynchronous Tasks](/guides/async-tasks).
## Retrieve an onboarded device
An onboarded device is part of your device inventory, and can be retrieved from the devices endpoint
by its ID:
```bash
curl "https://api.securithings.com/v1/devices/9ff559ee-38b6-11f1-81cb-465864a35d0a" \
-H "Accept: application/json" \
-H "Authorization: Bearer "
```
---
## Document: Postman & Insomnia
URL: /resources/postman
# Postman & Insomnia
import { Button } from "zudoku/ui/Button";
The SecuriThings API Postman Collection includes the authentication setup.
To set up Postman or Insomnia to use the SecuriThings API, you can see an example of how to set up
the authentication in the following images.
## Postman
1. Open Postman and create a new request.
2. In the Authorization tab, select `OAuth 2.0` and set `Add Authorization Data to` to `Request Headers`.
3. Set the Access Token URL to `https://api.securithings.com/v1/auth/`.
4. Change the Grant Type to `Client Credentials` and fill in the fields with the credentials provided by the SecuriThings team.
5. Change Client Authentication to `Send as Basic Auth header`.
An example of the configuration can be seen in the following image:

## Insomnia
1. Open Insomnia and create a new request.
2. In the Authentication tab, select `OAuth 2.0` and set `Grant Type` to `Client Credentials`.
3. Set the Access Token URL to `https://api.securithings.com/v1/auth/`.
4. Fill in the fields with the credentials provided by the SecuriThings team.
5. Under Advanced Options, ensure `Credentials` is set to `As Basic Auth Header`. (should be default)
An example of the configuration can be seen in the following image:

---
## Document: Upgrade Firmware
URL: /recipes/upgrade-firmware
# Upgrade Firmware
import { Badge } from "zudoku/ui/Badge";
Automation
GET /v1/devices
GET /v1/devices/:id/targetFirmware
POST /v1/tasks
Upgrade the devices that have an approved firmware upgrade path to the version you're deploying, and track the task to completion.
:::tip
You can adjust the script to your needs. Change the conditions, variables, and values to
produce the output you want.
:::
\n new Promise((resolve) => setTimeout(resolve, ms));\n\nconst TARGET_FIRMWARE = "9.80.1";',
},
},
{
title: "Select candidate devices",
body: "`availableTask.upgradeFirmware` reports whether the task can be executed on the device.",
code: {
python:
'devices = requests.get(\n f"{BASE}/v1/devices",\n params={"status": "online", "limit": 100},\n headers=headers,\n).json()["devices"]\n\ncandidates = [\n device["id"]\n for device in devices\n if device["availableTask"]["upgradeFirmware"]\n]',
javascript:
'const query = new URLSearchParams({\n status: "online",\n limit: "100",\n});\nconst devicesResponse = await fetch(\n `${BASE}/v1/devices?${query}`,\n { headers },\n);\nconst { devices } = await devicesResponse.json();\n\nconst candidates = [];\nfor (const device of devices) {\n if (device.availableTask.upgradeFirmware) {\n candidates.push(device.id);\n }\n}',
},
},
{
title: "Check approved upgrade paths",
body: "Only upgrade devices with an approved path to `TARGET_FIRMWARE`; skip the rest.",
code: {
python:
'targets = []\nfor device_id in candidates:\n paths = requests.get(\n f"{BASE}/v1/devices/{device_id}/targetFirmware",\n headers=headers,\n ).json()["targetFirmware"]\n if TARGET_FIRMWARE in paths:\n targets.append(device_id)',
javascript:
'const targets = [];\nfor (const deviceId of candidates) {\n const pathsResponse = await fetch(\n `${BASE}/v1/devices/${deviceId}/targetFirmware`,\n { headers },\n );\n const { targetFirmware } = await pathsResponse.json();\n if (targetFirmware.includes(TARGET_FIRMWARE)) {\n targets.push(deviceId);\n }\n}',
},
},
{
title: "Execute the task",
body: "The payload field for `upgradeFirmware` is documented on the [Execute Task](/api/tasks) endpoint.",
code: {
python:
'response = requests.post(\n f"{BASE}/v1/tasks",\n json={\n "taskType": "upgradeFirmware",\n "defaultPayload": {"targetFirmware": TARGET_FIRMWARE},\n "devices": [\n {"deviceId": device_id} for device_id in targets\n ],\n },\n headers=headers,\n)\ntask_id = response.json()["taskId"]',
javascript:
'const taskResponse = await fetch(`${BASE}/v1/tasks`, {\n method: "POST",\n headers: {\n ...headers,\n "Content-Type": "application/json",\n },\n body: JSON.stringify({\n taskType: "upgradeFirmware",\n defaultPayload: { targetFirmware: TARGET_FIRMWARE },\n devices: targets.map((deviceId) => ({ deviceId })),\n }),\n});\nconst { taskId } = await taskResponse.json();',
},
},
{
title: "Poll for completion",
body: "The task is done once every device task reaches `completed`.",
code: {
python:
'while True:\n task = requests.get(\n f"{BASE}/v1/tasks/{task_id}",\n headers=headers,\n ).json()\n pending = [\n device_task\n for device_task in task["deviceTasks"]\n if device_task["status"] != "completed"\n ]\n if not pending:\n break\n time.sleep(30)',
javascript:
'let task;\nwhile (true) {\n const statusResponse = await fetch(\n `${BASE}/v1/tasks/${taskId}`,\n { headers },\n );\n task = await statusResponse.json();\n const pending = task.deviceTasks.filter(\n (deviceTask) => deviceTask.status !== "completed",\n );\n if (pending.length === 0) break;\n await sleep(30000);\n}',
},
},
{
title: "Build the result",
body: "A device task is `completed` whether or not it succeeded, so read `result`.",
code: {
python:
'result = {\n "taskId": task_id,\n "succeeded": [\n {\n "deviceId": device_task["deviceId"],\n "deviceName": device_task["deviceName"],\n }\n for device_task in task["deviceTasks"]\n if device_task["result"] == "succeeded"\n ],\n "failed": [\n {\n "deviceId": device_task["deviceId"],\n "deviceName": device_task["deviceName"],\n "result": device_task["result"],\n }\n for device_task in task["deviceTasks"]\n if device_task["result"] != "succeeded"\n ],\n}\n\nprint(json.dumps(result, indent=2))',
javascript:
'const succeeded = [];\nconst failed = [];\n\nfor (const deviceTask of task.deviceTasks) {\n const entry = {\n deviceId: deviceTask.deviceId,\n deviceName: deviceTask.deviceName,\n };\n if (deviceTask.result === "succeeded") {\n succeeded.push(entry);\n } else {\n failed.push({\n ...entry,\n result: deviceTask.result,\n });\n }\n}\n\nconst result = { taskId, succeeded, failed };\n\nconsole.log(JSON.stringify(result, null, 2));',
},
},
]}
response={`{
"taskId": "19rs171mrlx9j3s",
"succeeded": [
{
"deviceId": "9ff559ee-38b6-11f1-81cb-465864a35d0a",
"deviceName": "192.168.2.53 - Unit"
}
],
"failed": [
{
"deviceId": "dc7d4ef1-6bf9-11f1-b083-d27bafcacdf8",
"deviceName": "192.168.2.70 - Unit",
"result": "rolledBack"
}
]
}`}
/>
---
## Document: Rotate SSL Certificates
URL: /recipes/rotate-ssl-certificates
# Rotate SSL Certificates
import { Badge } from "zudoku/ui/Badge";
Automation
GET /v1/devices
POST /v1/tasks
Rotate the SSL certificates on the devices that support the task, and track the task to completion.
:::tip
You can adjust the script to your needs. Change the conditions, variables, and values to
produce the output you want.
:::
\n new Promise((resolve) => setTimeout(resolve, ms));',
},
},
{
title: "Select target devices",
body: "`availableTask.rotateSslCertificate` reports whether the task can be executed on the device.",
code: {
python:
'devices = requests.get(\n f"{BASE}/v1/devices",\n params={"status": "online", "limit": 100},\n headers=headers,\n).json()["devices"]\n\ntargets = [\n device["id"]\n for device in devices\n if device["availableTask"]["rotateSslCertificate"]\n]',
javascript:
'const query = new URLSearchParams({\n status: "online",\n limit: "100",\n});\nconst devicesResponse = await fetch(\n `${BASE}/v1/devices?${query}`,\n { headers },\n);\nconst { devices } = await devicesResponse.json();\n\nconst targets = [];\nfor (const device of devices) {\n if (device.availableTask.rotateSslCertificate) {\n targets.push(device.id);\n }\n}',
},
},
{
title: "Execute the task",
body: "The payload fields for `rotateSslCertificate` are documented on the [Execute Task](/api/tasks) endpoint.",
code: {
python:
'response = requests.post(\n f"{BASE}/v1/tasks",\n json={\n "taskType": "rotateSslCertificate",\n "devices": [\n {"deviceId": device_id}\n for device_id in targets\n ],\n },\n headers=headers,\n)\ntask_id = response.json()["taskId"]',
javascript:
'const taskResponse = await fetch(`${BASE}/v1/tasks`, {\n method: "POST",\n headers: {\n ...headers,\n "Content-Type": "application/json",\n },\n body: JSON.stringify({\n taskType: "rotateSslCertificate",\n devices: targets.map((deviceId) => ({\n deviceId,\n })),\n }),\n});\nconst { taskId } = await taskResponse.json();',
},
},
{
title: "Poll for completion",
body: "The task is done once every device task reaches `completed`.",
code: {
python:
'while True:\n task = requests.get(\n f"{BASE}/v1/tasks/{task_id}",\n headers=headers,\n ).json()\n pending = [\n device_task\n for device_task in task["deviceTasks"]\n if device_task["status"] != "completed"\n ]\n if not pending:\n break\n time.sleep(30)',
javascript:
'let task;\nwhile (true) {\n const statusResponse = await fetch(\n `${BASE}/v1/tasks/${taskId}`,\n { headers },\n );\n task = await statusResponse.json();\n const pending = task.deviceTasks.filter(\n (deviceTask) => deviceTask.status !== "completed",\n );\n if (pending.length === 0) break;\n await sleep(30000);\n}',
},
},
{
title: "Build the result",
body: "A device task is `completed` whether or not it succeeded, so read `result`.",
code: {
python:
'result = {\n "taskId": task_id,\n "succeeded": [\n {\n "deviceId": device_task["deviceId"],\n "deviceName": device_task["deviceName"],\n }\n for device_task in task["deviceTasks"]\n if device_task["result"] == "succeeded"\n ],\n "failed": [\n {\n "deviceId": device_task["deviceId"],\n "deviceName": device_task["deviceName"],\n "result": device_task["result"],\n }\n for device_task in task["deviceTasks"]\n if device_task["result"] != "succeeded"\n ],\n}\n\nprint(json.dumps(result, indent=2))',
javascript:
'const succeeded = [];\nconst failed = [];\n\nfor (const deviceTask of task.deviceTasks) {\n const entry = {\n deviceId: deviceTask.deviceId,\n deviceName: deviceTask.deviceName,\n };\n if (deviceTask.result === "succeeded") {\n succeeded.push(entry);\n } else {\n failed.push({\n ...entry,\n result: deviceTask.result,\n });\n }\n}\n\nconst result = { taskId, succeeded, failed };\n\nconsole.log(JSON.stringify(result, null, 2));',
},
},
]}
response={`{
"taskId": "19rs171mrlx9j3s",
"succeeded": [
{
"deviceId": "dccfe2c1-7f92-11f1-a1d3-da3df9dc2a6d",
"deviceName": "192.168.2.69 - Unit"
}
],
"failed": [
{
"deviceId": "dc7d4ef1-6bf9-11f1-b083-d27bafcacdf8",
"deviceName": "192.168.2.70 - Unit",
"result": "rolledBack"
}
]
}`}
/>
---
## Document: Rotate Device Passwords
URL: /recipes/rotate-passwords
# Rotate Device Passwords
import { Badge } from "zudoku/ui/Badge";
Automation
GET /v1/devices
POST /v1/tasks
Rotate the passwords on the devices that support the task, and track the task to completion.
:::tip
You can adjust the script to your needs. Change the conditions, variables, and values to
produce the output you want.
:::
\n new Promise((resolve) => setTimeout(resolve, ms));',
},
},
{
title: "Select target devices",
body: "`availableTask.rotatePassword` reports whether the task can be executed on the device.",
code: {
python:
'devices = requests.get(\n f"{BASE}/v1/devices",\n params={"status": "online", "limit": 100},\n headers=headers,\n).json()["devices"]\n\ntargets = [\n device["id"]\n for device in devices\n if device["availableTask"]["rotatePassword"]\n]',
javascript:
'const query = new URLSearchParams({\n status: "online",\n limit: "100",\n});\nconst devicesResponse = await fetch(\n `${BASE}/v1/devices?${query}`,\n { headers },\n);\nconst { devices } = await devicesResponse.json();\n\nconst targets = [];\nfor (const device of devices) {\n if (device.availableTask.rotatePassword) {\n targets.push(device.id);\n }\n}',
},
},
{
title: "Build the rotation payload",
body: "`defaultPayload` applies the same configuration to every device in the task.",
code: {
python:
'default_payload = {\n "autoGeneratePassword": True,\n "changePasswordByRole": True,\n "userRoles": ["admin"],\n "passwordExpiryDays": 90,\n "enforceHttps": True,\n "passwordLength": 12,\n "includeUppercase": True,\n "includeLowercase": True,\n "includeNumbers": True,\n "includeSymbols": False,\n}',
javascript:
'const defaultPayload = {\n autoGeneratePassword: true,\n changePasswordByRole: true,\n userRoles: ["admin"],\n passwordExpiryDays: 90,\n enforceHttps: true,\n passwordLength: 12,\n includeUppercase: true,\n includeLowercase: true,\n includeNumbers: true,\n includeSymbols: false,\n};',
},
},
{
title: "Execute the task",
body: "One request covers every target device and returns a `taskId`.",
code: {
python:
'response = requests.post(\n f"{BASE}/v1/tasks",\n json={\n "taskType": "rotatePassword",\n "defaultPayload": default_payload,\n "devices": [\n {"deviceId": device_id}\n for device_id in targets\n ],\n },\n headers=headers,\n)\ntask_id = response.json()["taskId"]',
javascript:
'const taskResponse = await fetch(`${BASE}/v1/tasks`, {\n method: "POST",\n headers: {\n ...headers,\n "Content-Type": "application/json",\n },\n body: JSON.stringify({\n taskType: "rotatePassword",\n defaultPayload,\n devices: targets.map((deviceId) => ({\n deviceId,\n })),\n }),\n});\nconst { taskId } = await taskResponse.json();',
},
},
{
title: "Poll for completion",
body: "The task is done once every device task reaches `completed`.",
code: {
python:
'while True:\n task = requests.get(\n f"{BASE}/v1/tasks/{task_id}",\n headers=headers,\n ).json()\n pending = [\n device_task\n for device_task in task["deviceTasks"]\n if device_task["status"] != "completed"\n ]\n if not pending:\n break\n time.sleep(30)',
javascript:
'let task;\nwhile (true) {\n const statusResponse = await fetch(\n `${BASE}/v1/tasks/${taskId}`,\n { headers },\n );\n task = await statusResponse.json();\n const pending = task.deviceTasks.filter(\n (deviceTask) => deviceTask.status !== "completed",\n );\n if (pending.length === 0) break;\n await sleep(30000);\n}',
},
},
{
title: "Build the result",
body: "A device task is `completed` whether or not it succeeded, so read `result`.",
code: {
python:
'result = {\n "taskId": task_id,\n "succeeded": [\n {\n "deviceId": device_task["deviceId"],\n "deviceName": device_task["deviceName"],\n }\n for device_task in task["deviceTasks"]\n if device_task["result"] == "succeeded"\n ],\n "failed": [\n {\n "deviceId": device_task["deviceId"],\n "deviceName": device_task["deviceName"],\n "result": device_task["result"],\n }\n for device_task in task["deviceTasks"]\n if device_task["result"] != "succeeded"\n ],\n}\n\nprint(json.dumps(result, indent=2))',
javascript:
'const succeeded = [];\nconst failed = [];\n\nfor (const deviceTask of task.deviceTasks) {\n const entry = {\n deviceId: deviceTask.deviceId,\n deviceName: deviceTask.deviceName,\n };\n if (deviceTask.result === "succeeded") {\n succeeded.push(entry);\n } else {\n failed.push({\n ...entry,\n result: deviceTask.result,\n });\n }\n}\n\nconst result = { taskId, succeeded, failed };\n\nconsole.log(JSON.stringify(result, null, 2));',
},
},
]}
response={`{
"taskId": "23bpozz1mpjjedtr",
"succeeded": [
{
"deviceId": "9ff559ee-38b6-11f1-81cb-465864a35d0a",
"deviceName": "192.168.2.69 - Unit"
}
],
"failed": [
{
"deviceId": "dc7d4ef1-6bf9-11f1-b083-d27bafcacdf8",
"deviceName": "192.168.2.70 - Unit",
"result": "failed"
}
]
}`}
/>
---
## Document: Retrieve Offline Devices
URL: /recipes/retrieve-offline-devices
# Retrieve Offline Devices
import { Badge } from "zudoku/ui/Badge";
Monitoring
GET /v1/devices
Retrieve every device whose `status` is `offline`, paging through the full result set.
:::tip
You can adjust the script to your needs. Change the conditions, variables, and values to
produce the output you want.
:::
\n new Promise((resolve) => setTimeout(resolve, ms));',
},
},
{
title: "Request the first page",
body: "Filter on `status` and request the maximum page size.",
code: {
python:
'params = {\n "status": "offline",\n "limit": 100,\n "offset": 0,\n}\npage = requests.get(\n f"{BASE}/v1/devices",\n params=params,\n headers=headers,\n).json()\ndevices = page["devices"]',
javascript:
'const params = {\n status: "offline",\n limit: 100,\n offset: 0,\n};\n\nconst fetchPage = async () => {\n const query = new URLSearchParams(params);\n const response = await fetch(\n `${BASE}/v1/devices?${query}`,\n { headers },\n );\n return response.json();\n};\n\nlet page = await fetchPage();\nconst devices = [...page.devices];',
},
},
{
title: "Page through the results",
body: "Increase `offset` by `limit` until `total` devices have been retrieved.",
code: {
python:
'while len(devices) < page["total"]:\n params["offset"] += params["limit"]\n time.sleep(1) # 1 request per second\n page = requests.get(\n f"{BASE}/v1/devices",\n params=params,\n headers=headers,\n ).json()\n devices += page["devices"]',
javascript:
"while (devices.length < page.total) {\n params.offset += params.limit;\n await sleep(1000); // 1 request per second\n page = await fetchPage();\n devices.push(...page.devices);\n}",
},
},
{
title: "Build the result",
body: "`lastSeen` reports the last time each device was not offline.",
code: {
python:
'result = {\n "total": page["total"],\n "devices": [\n {\n "id": device["id"],\n "name": device["name"],\n "lastSeen": device["lastSeen"],\n }\n for device in devices\n ],\n}\n\nprint(json.dumps(result, indent=2))',
javascript:
"const result = {\n total: page.total,\n devices: devices.map((device) => ({\n id: device.id,\n name: device.name,\n lastSeen: device.lastSeen,\n })),\n};\n\nconsole.log(JSON.stringify(result, null, 2));",
},
},
]}
response={`{
"total": 1,
"devices": [
{
"id": "9ff559ee-38b6-11f1-81cb-465864a35d0a",
"name": "Headquarters camera1",
"lastSeen": "2026-07-09T11:20:00Z"
}
]
}`}
/>
---
## Document: Restart Devices
URL: /recipes/restart-devices
# Restart Devices
import { Badge } from "zudoku/ui/Badge";
Automation
GET /v1/devices
POST /v1/tasks
Restart the devices that support the task, and confirm they return online.
:::tip
You can adjust the script to your needs. Change the conditions, variables, and values to
produce the output you want.
:::
\n new Promise((resolve) => setTimeout(resolve, ms));',
},
},
{
title: "Select target devices",
body: "Narrow the fleet with any filter the [devices endpoint](/api/devices) accepts. `availableTask.restartDevice` reports whether the task can be executed on the device.",
code: {
python:
'devices = requests.get(\n f"{BASE}/v1/devices",\n params={"limit": 100},\n headers=headers,\n).json()["devices"]\n\ntargets = [\n device["id"]\n for device in devices\n if device["availableTask"]["restartDevice"]\n]',
javascript:
'const query = new URLSearchParams({ limit: "100" });\nconst devicesResponse = await fetch(\n `${BASE}/v1/devices?${query}`,\n { headers },\n);\nconst { devices } = await devicesResponse.json();\n\nconst targets = [];\nfor (const device of devices) {\n if (device.availableTask.restartDevice) {\n targets.push(device.id);\n }\n}',
},
},
{
title: "Execute the task",
body: "`restartDevice` requires no configuration, so no payload is sent.",
code: {
python:
'response = requests.post(\n f"{BASE}/v1/tasks",\n json={\n "taskType": "restartDevice",\n "devices": [\n {"deviceId": device_id}\n for device_id in targets\n ],\n },\n headers=headers,\n)\ntask_id = response.json()["taskId"]',
javascript:
'const taskResponse = await fetch(`${BASE}/v1/tasks`, {\n method: "POST",\n headers: {\n ...headers,\n "Content-Type": "application/json",\n },\n body: JSON.stringify({\n taskType: "restartDevice",\n devices: targets.map((deviceId) => ({\n deviceId,\n })),\n }),\n});\nconst { taskId } = await taskResponse.json();',
},
},
{
title: "Poll for completion",
body: "The task is done once every device task reaches `completed`.",
code: {
python:
'while True:\n task = requests.get(\n f"{BASE}/v1/tasks/{task_id}",\n headers=headers,\n ).json()\n pending = [\n device_task\n for device_task in task["deviceTasks"]\n if device_task["status"] != "completed"\n ]\n if not pending:\n break\n time.sleep(30)',
javascript:
'let task;\nwhile (true) {\n const statusResponse = await fetch(\n `${BASE}/v1/tasks/${taskId}`,\n { headers },\n );\n task = await statusResponse.json();\n const pending = task.deviceTasks.filter(\n (deviceTask) => deviceTask.status !== "completed",\n );\n if (pending.length === 0) break;\n await sleep(30000);\n}',
},
},
{
title: "Build the result",
body: "Read each device back to check its `status` after the restart.",
code: {
python:
'result = {"taskId": task_id, "devices": []}\n\nfor device_id in targets:\n device = requests.get(\n f"{BASE}/v1/devices/{device_id}",\n headers=headers,\n ).json()\n result["devices"].append(\n {\n "id": device["id"],\n "name": device["name"],\n "status": device["status"],\n }\n )\n time.sleep(1) # 1 request per second\n\nprint(json.dumps(result, indent=2))',
javascript:
"const result = { taskId, devices: [] };\n\nfor (const deviceId of targets) {\n const deviceResponse = await fetch(\n `${BASE}/v1/devices/${deviceId}`,\n { headers },\n );\n const device = await deviceResponse.json();\n result.devices.push({\n id: device.id,\n name: device.name,\n status: device.status,\n });\n await sleep(1000); // 1 request per second\n}\n\nconsole.log(JSON.stringify(result, null, 2));",
},
},
]}
response={`{
"taskId": "23bpozz1mpjjedtr",
"devices": [
{
"id": "9ff559ee-38b6-11f1-81cb-465864a35d0a",
"name": "192.168.2.69 - Unit",
"status": "online"
},
{
"id": "dc7d4ef1-6bf9-11f1-b083-d27bafcacdf8",
"name": "192.168.2.70 - Unit",
"status": "offline"
}
]
}`}
/>
---
## Document: Onboard Discovered Devices
URL: /recipes/onboard-discovered-devices
# Onboard Discovered Devices
import { Badge } from "zudoku/ui/Badge";
Inventory
GET /v1/discoveredDevices
POST /v1/tasks
Onboard the discovered devices that are ready to onboard, and track the task to completion.
:::tip
You can adjust the script to your needs. Change the conditions, variables, and values to
produce the output you want.
:::
\n new Promise((resolve) => setTimeout(resolve, ms));',
},
},
{
title: "Select target devices",
body: "Filter discovered devices on `onboardingStatus`.",
code: {
python:
'discovered = requests.get(\n f"{BASE}/v1/discoveredDevices",\n params={\n "onboardingStatus": "readyToOnboard",\n "limit": 100,\n },\n headers=headers,\n).json()["discoveredDevices"]\n\ntargets = [device["id"] for device in discovered]',
javascript:
'const query = new URLSearchParams({\n onboardingStatus: "readyToOnboard",\n limit: "100",\n});\nconst discoveredResponse = await fetch(\n `${BASE}/v1/discoveredDevices?${query}`,\n { headers },\n);\nconst discovered = await discoveredResponse.json();\n\nconst targets = discovered.discoveredDevices.map(\n (device) => device.id,\n);',
},
},
{
title: "Execute the task",
body: "`onboardDevice` requires no configuration, so no payload is sent.",
code: {
python:
'response = requests.post(\n f"{BASE}/v1/tasks",\n json={\n "taskType": "onboardDevice",\n "devices": [\n {"deviceId": device_id}\n for device_id in targets\n ],\n },\n headers=headers,\n)\ntask_id = response.json()["taskId"]',
javascript:
'const taskResponse = await fetch(`${BASE}/v1/tasks`, {\n method: "POST",\n headers: {\n ...headers,\n "Content-Type": "application/json",\n },\n body: JSON.stringify({\n taskType: "onboardDevice",\n devices: targets.map((deviceId) => ({\n deviceId,\n })),\n }),\n});\nconst { taskId } = await taskResponse.json();',
},
},
{
title: "Poll for completion",
body: "The task is done once every device task reaches `completed`.",
code: {
python:
'while True:\n task = requests.get(\n f"{BASE}/v1/tasks/{task_id}",\n headers=headers,\n ).json()\n pending = [\n device_task\n for device_task in task["deviceTasks"]\n if device_task["status"] != "completed"\n ]\n if not pending:\n break\n time.sleep(30)',
javascript:
'let task;\nwhile (true) {\n const statusResponse = await fetch(\n `${BASE}/v1/tasks/${taskId}`,\n { headers },\n );\n task = await statusResponse.json();\n const pending = task.deviceTasks.filter(\n (deviceTask) => deviceTask.status !== "completed",\n );\n if (pending.length === 0) break;\n await sleep(30000);\n}',
},
},
{
title: "Build the result",
body: "A device task is `completed` whether or not it succeeded, so read `result`.",
code: {
python:
'result = {\n "taskId": task_id,\n "succeeded": [\n {\n "deviceId": device_task["deviceId"],\n "deviceName": device_task["deviceName"],\n }\n for device_task in task["deviceTasks"]\n if device_task["result"] == "succeeded"\n ],\n "failed": [\n {\n "deviceId": device_task["deviceId"],\n "deviceName": device_task["deviceName"],\n "result": device_task["result"],\n }\n for device_task in task["deviceTasks"]\n if device_task["result"] != "succeeded"\n ],\n}\n\nprint(json.dumps(result, indent=2))',
javascript:
'const succeeded = [];\nconst failed = [];\n\nfor (const deviceTask of task.deviceTasks) {\n const entry = {\n deviceId: deviceTask.deviceId,\n deviceName: deviceTask.deviceName,\n };\n if (deviceTask.result === "succeeded") {\n succeeded.push(entry);\n } else {\n failed.push({\n ...entry,\n result: deviceTask.result,\n });\n }\n}\n\nconst result = { taskId, succeeded, failed };\n\nconsole.log(JSON.stringify(result, null, 2));',
},
},
]}
response={`{
"taskId": "23bpozz1mpjjedtr",
"succeeded": [
{
"deviceId": "9ff559ee-38b6-11f1-81cb-465864a35d0a",
"deviceName": "Lobby camera 12"
}
],
"failed": [
{
"deviceId": "dc7d4ef1-6bf9-11f1-b083-d27bafcacdf8",
"deviceName": "Lobby camera 13",
"result": "failed"
}
]
}`}
/>
---
## Document: Authenticate
URL: /recipes/authentication
# Authenticate
import { Badge } from "zudoku/ui/Badge";
Getting Started
POST /v1/auth/:org
Exchange the client credentials for an access token, and build the request headers every other
recipe uses.
"\nCLIENT_ID = ""\nCLIENT_SECRET = ""',
javascript:
'const BASE = "https://api.securithings.com";\nconst ORGANIZATION = "";\nconst CLIENT_ID = "";\nconst CLIENT_SECRET = "";',
},
},
{
title: "Request a token",
body: "`client_id` and `client_secret` are sent using Basic Authentication.",
code: {
python:
'response = requests.post(\n f"{BASE}/v1/auth/{ORGANIZATION}",\n auth=(CLIENT_ID, CLIENT_SECRET),\n)\nresponse.raise_for_status()',
javascript:
'const credentials = Buffer.from(\n `${CLIENT_ID}:${CLIENT_SECRET}`,\n).toString("base64");\n\nconst response = await fetch(\n `${BASE}/v1/auth/${ORGANIZATION}`,\n {\n method: "POST",\n headers: {\n Authorization: `Basic ${credentials}`,\n },\n },\n);\nif (!response.ok) {\n throw new Error(response.statusText);\n}',
},
},
{
title: "Build the request headers",
body: "`headers` is the value passed to every request in the recipes that follow.",
code: {
python:
'body = response.json()\naccess_token = body["access_token"]\nheaders = {"Authorization": f"Bearer {access_token}"}',
javascript:
"const body = await response.json();\nconst accessToken = body.access_token;\nconst headers = {\n Authorization: `Bearer ${accessToken}`,\n};",
},
},
{
title: "Build the result",
body: "A token is valid for one hour by default. Request a new one after it expires.",
code: {
python:
'result = {\n "access_token": access_token,\n "expires_in": body["expires_in"],\n}\n\nprint(json.dumps(result, indent=2))',
javascript:
"const result = {\n access_token: accessToken,\n expires_in: body.expires_in,\n};\n\nconsole.log(JSON.stringify(result, null, 2));",
},
},
]}
response={`{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"expires_in": 3600
}`}
/>
---
## Document: Introduction
URL: /mcp/introduction
# Introduction
:::sparkles
**Coming soon.** The SecuriThings MCP Server is under active development.
:::
Extend SecuriThings into any AI assistant. Ask or act on your device fleet in plain language, or
combine SecuriThings with the rest of your MCP ecosystem in one request.
---
## Document: Requests & Responses
URL: /guides/requests-and-responses
# Requests & Responses
:::info
The SecuriThings API is a REST API. It uses standard HTTP methods, resource-oriented URLs, and JSON
for both requests and responses.
:::
## Base URL
All requests go to `https://api.securithings.com`. The API version is part of the path, and all
current endpoints are under `/v1`.
## Responses
- All responses are returned in `application/json` format.
- Successful responses will be 200, 201 or 204 (if there is no resource to return).
- Errors are communicated using HTTP status codes. See [Errors](/guides/errors).
- All timestamps are in UTC, in ISO 8601 format.
## Collections
Endpoints that return a collection of resources return a JSON object with a field named after the
resource, containing an array of resource objects. For devices the field is `devices`, for sites it
is `sites`, and so on.
Alongside it, `total` is the number of resources that match your query, which is not necessarily the
number returned in this response. Use it to page through results, as described in
[Pagination & Filtering](/guides/pagination-and-filtering).
For example, requesting the first two devices out of three that match:
```bash
curl "https://api.securithings.com/v1/devices?limit=2" \
-H "Accept: application/json" \
-H "Authorization: Bearer "
```
```json
{
"devices": [
{
"id": "1",
...
},
{
"id": "2",
...
}
],
"total": 3
}
```
The response contains two devices because `limit=2` was requested, while `total` reports that three
devices match the query overall.
---
## Document: Rate Limiting
URL: /guides/rate-limits
# Rate Limiting
Our platform remains stable and fair for everyone by subjecting the APIs we provide to rate limiting
and throttling. We ask developers to use industry-standard techniques for limiting calls, caching
results, and re-trying requests.
If you exceed the rate limiting, your requests will be rejected with an error code of
`429 Too Many Requests`.
## Limits & Quotas
The rate limits are as follows, with each endpoint rate limited separately:
| Domain | Rate Limit |
| ------------------- | ------------------------------------------------------------- |
| Authentication | 5 requests per minute per IP |
| All other endpoints | 1 request per second per IP AND 1 request per second per user |
:::note
If you require a higher rate limit, [contact our support team](mailto:cs_engineers@securithings.com).
:::
## Avoid Rate Limit Errors
Designing your application with best practices in mind is the best way to avoid throttling errors.
Much of the data returned by the API changes infrequently, so cache responses and only make requests
when necessary.
---
## Document: Pagination & Filtering
URL: /guides/pagination-and-filtering
# Pagination & Filtering
:::info
All endpoints that return collections of items support pagination, and most also support filtering.
:::
## Pagination
The API supports pagination for endpoints returning collections by supplying `limit` and `offset`
query parameters.
```shell
curl -X GET "https://api.securithings.com/v1/devices?limit=10&offset=0" \
-H "Authorization: Bearer "
```
:::caution
By default, if no `limit` and `offset` are provided, the API will return the first 100 items.
:::
Every collection response includes a `total` field with the number of items matching the request.
Increase `offset` by the value of `limit` until `total` items have been retrieved.
## Filtering
Filters are query parameters that restrict a collection to the items matching the specified
criteria. Filtering is applied server-side, so filter in the request rather than retrieving the full
collection and filtering client-side.
```shell
curl -X GET "https://api.securithings.com/v1/devices?status=offline" \
-H "Authorization: Bearer "
```
:::info
Each endpoint supports its own set of filters. They are listed under Query Parameters for that
endpoint in the [API Reference](/api), together with their accepted values.
:::
### Combining filters
Multiple filters can be combined in a single request. An item is returned only if it matches all of
them.
```shell
curl -X GET "https://api.securithings.com/v1/devices?status=offline&isFirmwareVulnerable=1&lifeCycleStatus=eos" \
-H "Authorization: Bearer "
```
This query returns all offline devices that are End of Support (EOS) and have known firmware
vulnerabilities.
Filters combine with pagination, so `total` reflects the filtered result set rather than the whole
collection.
---
## Document: Errors
URL: /guides/errors
# Errors
Errors are communicated using HTTP status codes.
## Status codes
| Code | Description |
| ---- | ----------------------------------------------------------- |
| 400 | Invalid/bad request |
| 401 | Unauthorized, credentials maybe missing, invalid or expired |
| 403 | No permissions to access the resource |
| 404 | Resource not found or no access to it |
| 429 | Rate limit exceeded |
| 500 | Internal server error |
| 502 | Bad gateway |
---
## Document: Authentication
URL: /guides/authentication
# Authentication
SecuriThings API uses the OAuth 2.0 protocol with JWT for authentication and authorization.
Each request made to our API requires a Bearer token in the Authorization header.
This token is a JWT containing information about the user and their permissions.
## Getting credentials for the API
Each integration with the SecuriThings API requires a set of credentials. These credentials are used to generate a JWT token that is used to authenticate the user across the API.
Credentials are provided manually by the SecuriThings team. To get your credentials, please [contact our support team](https://forms.gle/og4uornuwain2jhSA).
Credentials are composed of three parts:
- `organization`: The tenant that the client belongs to.
- `client_id`: A unique identifier for the client.
- `client_secret`: A secret key that is used to generate the JWT token.
## Getting a token
Using the credentials provided by the SecuriThings team, you can generate a JWT token by sending a POST request to the `/v1/auth/` endpoint.
`client_id` and `client_secret` should be provided using Basic Authentication.
:::caution
Running these commands will expose your credentials in the terminal history.
Make sure to clear the terminal history after running the commands or opt to use an API client.
:::
```bash
curl -X POST https://api.securithings.com/v1/auth/ \
-H 'Content-Type: application/json' \
-H 'Authorization : Basic '
# or
curl -X POST https://api.securithings.com/v1/auth/ \
-H 'Content-Type: application/json' \
-u ':'
```
A token is valid for one hour by default. Generate a new token after it expires.
:::note
A `refresh_token` is not provided by default. If you require one, please contact our
[support team](mailto:cs_engineers@securithings.com).
:::
## Using Postman or Insomnia
Setup instructions for both clients, and a downloadable Postman Collection that includes the
authentication setup, are on the [Postman & Insomnia](/resources/postman) page.
---
## Document: Asynchronous Tasks
URL: /guides/async-tasks
# Asynchronous Tasks
import { Stepper } from "zudoku/ui/Stepper";
Beyond reading data, the API can execute tasks on devices. Because tasks take time to complete on
physical devices, they run asynchronously. You execute a task, get a `taskId` back immediately, and
poll for completion.
A single task runs across one or more devices. The work on each individual device is a device task,
and the overall task is done once every device task is completed.
This page describes the pattern. The per-task-type payload fields are documented in the
[API Reference](/api).
:::lock
`POST /v1/tasks` requires Admin or Operator permissions. To enable it for your account, please
[contact our support team](mailto:cs_engineers@securithings.com).
:::
## The lifecycle
1. **Execute the task**
Send `POST /v1/tasks` with a `taskType` and the devices to run it on. For tasks that require
configuration, use `defaultPayload` to apply the same configuration to every device, or set a
`payload` per device.
```json
{
"taskType": "restartDevice",
"devices": [
{ "deviceId": "9ff559ee-38b6-11f1-81cb-465864a35d0a" },
{ "deviceId": "dc7d4ef1-6bf9-11f1-b083-d27bafcacdf8" }
]
}
```
1. **Take the task ID**
The response returns the ID of the created task.
```json
{ "taskId": "23bpozz1mpjjedtr" }
```
1. **Poll until every device task is completed**
`GET /v1/tasks/{id}` returns one entry per device task.
```json
{
"userName": "service-account-integrator_1",
"deviceTasks": [
{
"createDate": "2026-07-15T10:13:14.000Z",
"currentStepStatus": "runSucceeded",
"deviceHost": "192.168.2.69",
"deviceId": "9ff559ee-38b6-11f1-81cb-465864a35d0a",
"deviceModel": "P3265-LV",
"deviceName": "192.168.2.69 - Unit",
"deviceTaskId": "366717",
"result": "succeeded",
"status": "completed",
"taskId": "23bpozz1mpjjedtr",
"taskType": "restartDevice"
},
{
"createDate": "2026-07-15T10:13:14.000Z",
"currentStepStatus": "runSucceeded",
"deviceHost": "192.168.2.70",
"deviceId": "dc7d4ef1-6bf9-11f1-b083-d27bafcacdf8",
"deviceModel": "P3265-LV",
"deviceName": "192.168.2.70 - Unit",
"deviceTaskId": "366718",
"result": "succeeded",
"status": "completed",
"taskId": "23bpozz1mpjjedtr",
"taskType": "restartDevice"
}
],
"total": 2
}
```
:::info
`status` tells you whether a device task has finished (`pending`, `running`, `completed`), and
`result` tells you how it finished (`succeeded`, `failed`, `rolledBack`, `aborted`). `result` is
`null` until the device task completes, so always check both.
:::
:::zap
Add `includeSteps=true` to `GET /v1/tasks/{id}` to include the per-step breakdown of each device
task. Steps increase the response size considerably, so they are not returned by default. Request
them only when a device task fails and further information is required to investigate it.
:::
## Check availability first
Not every device supports every task. Device objects expose an `availableTask` map, so check it
before executing:
```json
"availableTask": {
"hardenDevice": false,
"restartDevice": true,
"rotate8021xCertificate": false,
"rotatePassword": true,
"rotateSslCertificate": true,
"upgradeFirmware": true
}
```
---
## Document: Recipes
URL: /recipes
# Recipes
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
} from "zudoku/ui/Card";
import { Badge } from "zudoku/ui/Badge";
import { Link } from "zudoku/components";
export const recipes = [
{
title: "Authenticate",
category: "Getting Started",
description: "Exchange the client credentials for an access token.",
endpoints: ["POST /v1/auth/:org"],
href: "/recipes/authentication",
},
{
title: "Retrieve Offline Devices",
category: "Monitoring",
description:
"Retrieve every device whose status is offline, paging through the full result set.",
endpoints: ["GET /v1/devices"],
href: "/recipes/retrieve-offline-devices",
},
{
title: "Onboard Discovered Devices",
category: "Inventory",
description:
"Onboard the discovered devices that are ready to onboard, and track the task to completion.",
endpoints: ["GET /v1/discoveredDevices", "POST /v1/tasks"],
href: "/recipes/onboard-discovered-devices",
},
{
title: "Restart Devices",
category: "Automation",
description:
"Restart the devices that support the task, and confirm they return online.",
endpoints: ["GET /v1/devices", "POST /v1/tasks"],
href: "/recipes/restart-devices",
},
{
title: "Rotate Device Passwords",
category: "Automation",
description:
"Rotate the passwords on the devices that support the task, and track the task to completion.",
endpoints: ["GET /v1/devices", "POST /v1/tasks"],
href: "/recipes/rotate-passwords",
},
{
title: "Rotate SSL Certificates",
category: "Automation",
description:
"Rotate the SSL certificates on the devices that support the task, and track the task to completion.",
endpoints: ["GET /v1/devices", "POST /v1/tasks"],
href: "/recipes/rotate-ssl-certificates",
},
{
title: "Upgrade Firmware",
category: "Automation",
description:
"Upgrade the devices that have an approved firmware path, and track the task to completion.",
endpoints: [
"GET /v1/devices",
"GET /v1/devices/:id/targetFirmware",
"POST /v1/tasks",
],
href: "/recipes/upgrade-firmware",
},
];
Recipes are runnable examples of common jobs. Each one combines several steps, and often
several endpoints, into a single script.