Duo Device API for Trusted Endpoints with Duo Desktop
Last updated:
Overview
Duo's Trusted Endpoints feature secures your sensitive applications by ensuring that only known devices can access Duo protected services. When a user authenticates via the Duo Prompt, we'll compare device identifiers collected by Duo Desktop installed on that endpoint with the identifiers of known Windows, macOS, and Linux devices stored in Duo. You can monitor access to your applications from trusted and untrusted devices, and optionally block access from devices not trusted by your organization.
Trusted Endpoints and the Device API are part of the Duo Premier, Duo Advantage, and Duo Essentials plans.
Before enabling the Trusted Endpoints policy on your applications, you'll need to create a device cache with the identifying information for your known devices in Duo's service using the Device API. Be sure to read the Trusted Endpoints Generic with Duo Desktop management integration instructions in full before using Device API.
Review the API Details to see how to construct your Device API request.
About the Device API
This API is automatically available to paying Duo Premier, Duo Advantage, and Duo Essentials customers who are using Trusted Endpoints Generic management integrations with Duo Desktop to establish trust.
The typical usage of this API will be to start by creating a new device cache. At this point, the device cache is in a pending status and devices added to the device cache are not trusted. If an existing active device cache exists, it is not affected. Next, add devices to the pending cache. This may require more than one operation depending on the number of devices to be added. When all devices have been added to the pending device cache, activate the device cache. Activating the pending device cache will replace any existing active device cache and the device identifiers contained within will be trusted.
You will need to obtain device IDs to import into your device cache. These identifiers are the Windows Machine GUID, the macOS hardware UUID, or the Linux product/system UUID. You may be able to export the identifiers from an existing endpoint management system, or you may need to collect identifiers from your endpoints directly. See Collect Device Identifiers in the Trusted Endpoints Generic with Duo Desktop management integration instructions to learn how to find the device identifiers for Windows, macOS, and Linux systems.
Documented properties will not be removed within a stable version of the API. Once a given API endpoint is documented to return a given property, a property with that name will always appear (although certain properties may only appear under certain conditions, like if the customer is using a specific edition). When Duo deprecates a property, the API continues to accept that property in requests, although it no longer has any effect.
Properties that enumerate choices may gain new values at any time. Duo may cease to return legacy values for properties as well. Duo will update our API documentation with changes to property values in a timely fashion, adding new property values or indicating changes to existing property values.
New, undocumented properties may also appear at any time. For instance, Duo may make available a beta feature involving extra information returned by an API endpoint. Until the property is documented here its format may change or it may even be entirely removed from our API.
First Steps
-
Log in to the Duo Admin Panel and navigate to Devices → Trusted Endpoints.
-
Create a Trusted Endpoints Generic with Duo Desktop integration that uses Duo Desktop to establish trust. Please see the instructions for creating a Generic Duo Desktop integration in the Generic Duo Desktop Trust documentation.
The API credentials for use with the Device API are provided within the context of that specific Trusted Endpoints integration.
-
Optionally specify which IP addresses or ranges are allowed to use this Device API application in Networks for API Access. If you do not specify any IP addresses or ranges, this Device API application may be accessed from any network.
The Device API performs the IP check after verifying the authentication signature in a request. If you restrict the allowed networks for API access and see logged events for blocked Device API requests from unrecognized IP addresses, this may indicate compromise of your Device API application's secret key.
API Clients
Duo Security has demonstration clients available on GitHub to call the Duo API methods.
- Python (duo_client_python)
- Java (duo_client_java)
- C# (duo_api_csharp)
- Go (duo_api_golang)
- Node (duo_api_nodejs)
- Ruby (duo_api_ruby)
- Perl (duo_api_perl)
- PHP (duo_api_php)
We also have a Postman collection available for testing Duo API functionality.
API Details
Base URL
All API methods use your API hostname,
https://api-XXXXXXXX.duosecurity.com. Obtain this value from the Duo Admin Panel and use it exactly as shown there.
Methods always use HTTPS. Unsecured HTTP is not supported.
Request Format
All requests must have "Authorization" and "Date" headers.
If the request method is GET or DELETE, URL-encode parameters and send them in the URL query string like this: /device/v1/management_systems/[mkey]/device_cache?realname=First%20Last&username=root. They still go on a separate line when creating the string to sign for an Authorization header.
The request body format for POST requests depends on the signing version you use:
- v5 signing (recommended): Send parameters as a JSON object in the request body. The header "
Content-Type: application/json" must be present. - v2 signing (legacy): Send parameters as URL-encoded key-value pairs in the request body (the same format used by browsers to submit form data). The header "
Content-Type: application/x-www-form-urlencoded" must be present.
When URL-encoding (for query strings or v2 request bodies), all bytes except ASCII letters, digits, underscore ("_"), period ("."), tilde ("~"), and hyphen ("-") are replaced by a percent sign ("%") followed by two hexadecimal digits containing the value of the byte. For example, a space is replaced with "%20" and an at-sign ("@") becomes "%40". Use only upper-case A through F for hexadecimal digits.
A request with parameters, as a complete URL, would look something like this: https://api-XXXXXXXX.duosecurity.com/admin/v1/users?realname=First%20Last&username=root (substituting the actual API method path and parameters used in your request).
Response Format
Responses are formatted as a JSON object with a top-level stat key.
Successful responses will have a stat value of "OK" and a
response key. The response will either
be a single object or a sequence of other JSON types, depending
on which endpoint is called.
{
"stat": "OK",
"response": {
"key": "value"
}
}
Values are returned as strings unless otherwise documented.
Unsuccessful responses will have a
stat value of "FAIL", an integer
code, and a
message key that further describes the failure.
A message_detail key may be present if additional information is available (like the specific parameter that caused the error).
{
"stat": "FAIL",
"code": 40002,
"message": "Invalid request parameters",
"message_detail": "username"
}
The HTTP response code will be the first three digits of the more
specific code found inside the JSON object. Each
endpoint's documentation lists HTTP response codes it can return.
Additionally, all API endpoints that require a signed request can
return the following HTTP response codes:
| Response | Meaning |
|---|---|
| 200 | The request completed successfully. |
| 401 | The "Authorization", "Date", and/or "Content-Type" headers were missing or invalid. |
| 403 |
This integration is not authorized for this endpoint or the ikey was created for a different integration type (for example, using an Auth API ikey with Admin API endpoints). |
| 405 | The request's HTTP verb is not valid for this endpoint (for example, POST when only GET is supported). |
| 429 | The account has made too many requests of this type recently. Try again later. |
Authentication
Duo API requests must be cryptographically signed using one of Duo’s supported signing specifications.
Duo currently supports two signing versions, v5 and v2. v5 signing is the preferred authentication method for Duo APIs and is the default used by all Duo API clients on GitHub.
Duo v5 signing offers improved security and several advantages over v2:
- Stronger hashing algorithm (SHA-512 vs SHA-1).
- Request body signing prevents tampering with POST data.
- Support for signed additional headers.
| Signing Version | Hash Algorithm | Supports Signed Headers |
|---|---|---|
| v5 (recommended) | SHA-512 | Yes |
| v2 (legacy) | SHA-1 | No |
All Duo API clients available on GitHub support v5 signing. We encourage use of our API clients whenever possible for easier implementation. A reference implementation of the v5 signing algorithm is available at duo_hmac_python. Duo API clients still support v2 signing for legacy purposes.
Generate the HTTP Password as an HMAC signature of the request. This will be different for each request and must be re-generated each time.
To construct the signature, build a canonical list using these string elements, joined by newline characters (`\n`):
| Component | Description | Example |
|---|---|---|
date
|
The current time, formatted as RFC 2822. This must be the same string as the "Date" header. |
Tue, 21 Aug 2012 17:29:18 -0000
|
method
|
The HTTP method, in uppercase. |
POST
|
host
|
Your API hostname (lowercase). |
api-xxxxxxxx.duosecurity.com
|
path
|
The specific API method's path. |
/device/v1/management_systems/[mkey]/device_cache
|
query string parameters
|
The URL-encoded list of If the request does not have any query string parameters, include a blank line. Do not encode unreserved characters. Use upper-case hexadecimal digits A through F in escape sequences. |
An example realname=First%20Last&username=root
|
body hash
|
The SHA-512 hash of the request body, represented as a lowercase hexadecimal string.
|
The 128-character SHA-512 hash of the empty string (for GET/DELETE): cf83e1357eefb8bd...27af927da3e (truncated for display)
The 128-character SHA-512 hash of 27c74670adb75075...4830763fd (truncated for display)
|
headers hash
|
The SHA-512 hash of the canonicalized additional headers, represented as a lowercase hexadecimal string. Only headers beginning with If including additional headers:
If no additional headers are included, compute the SHA-512 hash of the empty string and include the lowercase hex digest. |
The 128-character SHA-512 hash of the empty string (no additional headers): cf83e1357eefb8bd...27af927da3e (truncated for display)
|
Then concatenate these components with newlines:
[RFC 2822 Timestamp]\n
[HTTP Method]\n
[Host]\n
[Path]\n
[Query string parameters (or blank line if none)]\n
[SHA-512 hex digest of body (or of empty string for GET/DELETE)]\n
[SHA-512 hex digest of headers (or of empty string if none)]
For example, here is the canonical string for a POST to Device API with the parameters in the request body:
Tue, 21 Aug 2012 17:29:18 -0000
POST
api-xxxxxxxx.duosecurity.com
/device/v1/management_systems/DME0XUC77ATL3J05HSTB/device_cache
d257e21c24aa9d6680847a712acc1c07a36bfc2393bbb9bb13b5da5127d6b9afe03db7701fb6bd1fc4338a333ee67d8041f1547b38e83c57acd9fde61478e915
cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e
In this example:
- Line 5 is blank because there are no query string parameters (the parameters are in the JSON body instead).
- Line 6 is the SHA-512 hash of the JSON body.
- Line 7 is the SHA-512 hash of the empty string because no additional
X-Duo-*headers are included.
The JSON body for this request, before conversion to a hexadecimal SHA-512 hash, was:
{"status":"active"}
Here is the canonical string for a GET request with query string parameters:
Tue, 21 Aug 2012 17:29:18 -0000
GET
api-xxxxxxxx.duosecurity.com
/device/v1/management_systems/DME0XUC77ATL3J05HSTB/device_cache
status=active
cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e
cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e
In this example:
- Line 5 contains the query string parameters, lexicographically sorted.
- Line 6 is the SHA-512 hash of the empty string because GET requests have no body.
- Line 7 is the SHA-512 hash of the empty string because no additional
X-Duo-*headers are included.
Lastly, compute the HMAC-SHA512 of this canonical representation, using your Duo application's secret key as the HMAC key. Send this signature as hexadecimal ASCII (i.e. not raw binary data). Use HTTP Basic Authentication for the request, using your integration key as the username and the HMAC-SHA512 signature as the password.
signature = hex( HMAC-SHA512( skey, canonical_string ) )
For example, here are the headers for the above POST request to api-xxxxxxxx.duosecurity.com/device/v1/management_systems/DME0XUC77ATL3J05HSTB/device_cache, using DIWJ8X6AEYOR5OMC6TQ1 as the integration key and Zh5eGmUq9zpfQnyUIu5OL9iWoMMv5ZNmk3zLJ4Ep as the secret key:
POST /device/v1/management_systems/DME0XUC77ATL3J05HSTB/device_cache HTTP/1.1
Host: api-xxxxxxxx.duosecurity.com
Date: Tue, 21 Aug 2012 17:29:18 -0000
Content-Type: application/json
Authorization: Basic RElXSjhYNkFFWU9SNU9NQzZUUTE6NjhiMjQ0OThmYWM3Nzc1NmYzODI4YjJiY2NmZmM2MGE2OGQwNDBlODU2YmYzODJmOGI4ZTI0OTVjNTVmYjJjMTM4YWQwNTI5NGM2YmQyOTg5NjQxYzJlYThhM2RkYTYyNTM3NzA3NmNhMDQxMDEzY2FjZTJkMDM4MDkzMDM2Zjk=
Separate HTTP request header lines with CRLF newlines.
The following Python 3 function can be used to construct the "Authorization" and "Date" headers for v5 signature requests:
import base64, email.utils, hashlib, hmac, json, urllib.parse
def sign_v5(method, host, path, body_params, skey, ikey,
query_params=None, extra_headers=None):
"""
Return HTTP Basic Authentication ("Authorization" and "Date") headers.
method, host, path: strings from request
body_params: dict of body parameters (use {} for GET/DELETE)
skey: secret key
ikey: integration key
query_params: dict of query string parameters (optional)
extra_headers: dict of X-Duo-* headers to include (optional)
"""
now = email.utils.formatdate()
# query string: sorted key=value pairs
query_string = ""
if query_params:
query_string = "&".join(
"%s=%s" % (urllib.parse.quote(k, "~"), urllib.parse.quote(v, "~"))
for k, v in sorted(query_params.items())
)
# body hash
if method.upper() in ("GET", "DELETE"):
body_hash = hashlib.sha512(b"").hexdigest()
else:
body_bytes = json.dumps(body_params, separators=(",", ":"),
sort_keys=True).encode("utf-8")
body_hash = hashlib.sha512(body_bytes).hexdigest()
# headers hash
if extra_headers:
header_parts = []
for name in sorted(extra_headers.keys()):
header_parts.append(name.lower())
header_parts.append(extra_headers[name].strip())
headers_hash = hashlib.sha512(
"\x00".join(header_parts).encode("utf-8")
).hexdigest()
else:
headers_hash = hashlib.sha512(b"").hexdigest()
# canonical string
canon = "\n".join([now, method.upper(), host.lower(), path,
query_string, body_hash, headers_hash])
# sign canonical string
sig = hmac.new(skey.encode("utf-8"), canon.encode("utf-8"),
hashlib.sha512)
auth = "%s:%s" % (ikey, sig.hexdigest())
return {
"Date": now,
"Authorization": "Basic %s" % base64.b64encode(
auth.encode("utf-8")).decode(),
}
If you're building your own authentication request and encountering issues, use the Duo HMAC Debugger tool to verify your HMAC signature.
The API uses HTTP Basic Authentication to authenticate requests. Use your Duo application's integration key as the HTTP Username.
Generate the HTTP Password as an HMAC signature of the request. This will be different for each request and must be re-generated each time.
To construct the signature, first build an ASCII string from your request, using the following components:
| Component | Description | Example |
|---|---|---|
date
|
The current time, formatted as RFC 2822. This must be the same string as the "Date" header. |
Tue, 21 Aug 2012 17:29:18 -0000
|
method
|
The HTTP method (uppercase) |
POST
|
host
|
Your API hostname (lowercase) |
api-xxxxxxxx.duosecurity.com
|
path
|
The specific API method's path |
/device/v1/management_systems/[mkey]/device_cache
|
params
|
The URL-encoded list of If the request does not have any parameters one must still include a blank line in the string that is signed. Do not encode unreserved characters. Use upper-case hexadecimal digits A through F in escape sequences. |
An example realname=First%20Last&username=root
|
Then concatenate these components with (line feed) newlines. For example:
Tue, 21 Aug 2012 17:29:18 -0000
POST
api-xxxxxxxx.duosecurity.com
/device/v1/management_systems/DME0XUC77ATL3J05HSTB/device_cache
status=active
GET requests also use this five-line format:
Tue, 21 Aug 2012 17:29:18 -0000
GET
api-xxxxxxxx.duosecurity.com
/device/v1/management_systems/DME0XUC77ATL3J05HSTB/device_cache
Lastly, compute the HMAC-SHA1 of this canonical representation, using your Duo Device API application's secret key as the HMAC key. Send this signature as hexadecimal ASCII (i.e. not raw binary data). Use HTTP Basic Authentication for the request, using your integration key as the username and the HMAC-SHA1 signature as the password. Signature validation is case-insensitive, so the signature may be upper or lowercase.
For example, here are the headers for the above POST request to api-xxxxxxxx.duosecurity.com/device/v1/management_systems/[mkey]/device_cache, using DME0XUC77ATL3J05HSTB as the mkey, DIWJ8X6AEYOR5OMC6TQ1 as the integration key, and Zh5eGmUq9zpfQnyUIu5OL9iWoMMv5ZNmk3zLJ4Ep as the secret key:
Date: Tue, 21 Aug 2012 17:29:18 -0000
Authorization: Basic RElXSjhYNkFFWU9SNU9NQzZUUTE6OTU3YTRhOTJkYWRlOWUyYWYzYmEwNWQ0ZjE4YjI0ZmY1M2MyOTRmZQ==
Host: api-xxxxxxxx.duosecurity.com
Content-Length: 35
Content-Type: application/x-www-form-urlencoded
Separate HTTP request header lines with CRLF newlines.
The following Python function can be used to construct the "Authorization" and "Date" headers:
import base64, email.utils, hmac, hashlib, urllib
def sign(method, host, path, params, skey, ikey):
"""
Return HTTP Basic Authentication ("Authorization" and "Date") headers.
method, host, path: strings from request
params: dict of request parameters
skey: secret key
ikey: integration key
"""
# create canonical string
now = email.utils.formatdate()
canon = [now, method.upper(), host.lower(), path]
args = []
for key in sorted(params.keys()):
val = params[key].encode("utf-8")
args.append(
'%s=%s' % (urllib.parse.
quote(key, '~'), urllib.parse.quote(val, '~')))
canon.append('&'.join(args))
canon = '\n'.join(canon)
# sign canonical string
sig = hmac.new(bytes(skey, encoding='utf-8'),
bytes(canon, encoding='utf-8'),
hashlib.sha1)
auth = '%s:%s' % (ikey, sig.hexdigest())
# return headers
return {'Date': now, 'Authorization': 'Basic %s' % base64.b64encode(bytes(auth, encoding="utf-8")).decode()}
import base64, email, hmac, hashlib, urllib
def sign(method, host, path, params, skey, ikey):
"""
Return HTTP Basic Authentication ("Authorization" and "Date") headers.
method, host, path: strings from request
params: dict of request parameters
skey: secret key
ikey: integration key
"""
# create canonical string
now = email.Utils.formatdate()
canon = [now, method.upper(), host.lower(), path]
args = []
for key in sorted(params.keys()):
val = params[key]
if isinstance(val, unicode):
val = val.encode("utf-8")
args.append(
'%s=%s' % (urllib.quote(key, '~'), urllib.quote(val, '~')))
canon.append('&'.join(args))
canon = '\n'.join(canon)
# sign canonical string
sig = hmac.new(skey, canon, hashlib.sha1)
auth = '%s:%s' % (ikey, sig.hexdigest())
# return headers
return {'Date': now, 'Authorization': 'Basic %s' % base64.b64encode(auth)}
Device API Endpoints
Create New Device Cache
Create a new management system device cache in pending or active status.
POST /device/v1/management_systems/[mkey]/device_cache
By default, a pending cache will be made, however, if you wish to make an active cache directly, you can pass in the optional parameter “active” set to “True”.
Parameters
| Parameter | Required? | Description |
|---|---|---|
active
|
Optional |
Boolean string for if cache made is to be active. "True" |
Response Codes
| Response | Meaning |
|---|---|
| 200 |
Success. New device upload cache was created successfully. |
| 401 |
Invalid identity in request credentials. |
| 403 |
The target management system does not support creation of a cache. |
| 404 |
No management system was found with the given |
| 409 |
Cannot create new cache with existing pending or active cache. You may wish to DELETE the existing pending or active cache. |
| 500 |
Device cache creation failed. |
Response Format
| Key | Value |
|---|---|
cache_key
|
Device cache unique identifier. |
status
|
Status of the new device cache. Either “Active” or “Pending”. |
url
|
URL representing the new device cache. |
Example Response
{
"stat": "OK",
"response": {
"cache_key": "DCBWLKD6JO8U9A0N3G6Q",
"status": "Pending",
"url": "https://api-XXXXXXXX.duosecurity.com/device/v1/management_systems/DM2J8BJ480EK85TIMONU/device_cache/DCBWLKD6JO8U9A0N3G6Q"
}
}
Add Devices to a Device Cache
Add devices to an active or pending device cache.
POST /device/v1/management_systems/[mkey]/device_cache/[cache_key]/devices
Object limits
1,000 device IDs per request; 250,000 device IDs per cache.
Parameters
| Parameter | Required? | Description |
|---|---|---|
devices
|
Required |
List of devices to be added in JSON format. Example: [{"device_id": "<device_id>" }, |
Response Codes
| Response | Meaning |
|---|---|
| 200 |
Success. Devices added to the device cache. |
| 400 |
Invalid parameters. |
| 401 |
Invalid identity in request credentials. |
| 404 |
No cache key |
| 409 |
Cache is active. Devices could not be added because <count of device IDs> would exceed the limit of <max device IDs per cache>. |
| 413 |
<num devices> devices are more than the limit of <num devices limit> devices. |
| 500 |
Wrong method used or other internal error. |
Response Format
| Key | Value |
|---|---|
cache_key
|
Device cache unique identifier. |
date_created
|
Date and time of device cache creation. |
device_count
|
Total number of devices added to this cache. |
Example Response
{
"stat": "OK",
"response": {
"cache_key": "DCOIV2VVMX5IFX1OW8S2",
"date_created": "2022-02-15T17:02:22",
"device_count": 103
}
}
Retrieve Devices
Retrieve devices by device_id from a cache, or retrieve all devices from cache.
GET /device/v1/management_systems/[mkey]/device_cache/[cache_key]/devices
The same route is used to retrieve all devices paginated, or to search up to 40 devices in a single request by the device ID.
To retrieve all devices do not pass in the device_id parameter. When retrieving all devices, passing in the limit and offset for paging parameters is optional.
To retrieve devices by ID, pass in the device_id parameter as a list of device ID strings in uuid format. Retrieving devices by ID is not paginated.
Object limits
40 device IDs per request; 250,000 device IDs per cache.
Parameters
| Parameter | Required? | Description |
|---|---|---|
device_ids
|
Optional |
List of Device ID strings. Example: ["<device_id>", "<device_id>"] |
limit
|
Optional |
The maximum number of records returned. Default: |
offset
|
Optional |
The offset from 0 at which to start record retrieval. When used with "limit", the handler will return "limit" records starting at the n-th record, where n is the offset. Default: |
Response Codes
| Response | Meaning |
|---|---|
| 200 |
Success. Device(s) retrieved. |
| 400 |
Invalid parameters. |
| 401 |
Invalid identity in request credentials. |
| 404 |
No |
| 413 |
The number of devices specified in |
Response Format
| Key | Value |
|---|---|
date_added
|
Date and time the device was added. |
device_id
|
Identifier of the device that was retrieved. |
cache_key
|
Cache key that device(s) were retrieved from. |
devices_retrieved
|
The devices that were retrieved from the cache with their ID and the date that they were added to the cache. |
num_devices_retrieved
|
The number of devices retrieved from the cache. |
Example Response
Retrieve all devices in cache:
{
"response": {
"cache_key": "DCA8JB9UFGCANQ53EBRW",
"devices_retrieved": [
{
"date_added": "2023-08-09T14:13:17",
"device_id": "93ea2eac-0b93-4687-aeee-44e3ab95d657"
},
{
"date_added": "2023-08-09T14:13:17",
"device_id": "93ea2eac-0b93-4687-addd-44e3ab95d657"
},
{
"date_added": "2023-08-09T14:13:17",
"device_id": "93ea2eac-0b93-4687-aaaa-44e3ab95d657"
},
{
"date_added": "2023-08-09T14:13:17",
"device_id": "93ea2eac-0b93-4687-accc-44e3ab95d657"
},
{
"date_added": "2023-08-09T14:13:17",
"device_id": "93ea2eac-0b93-4687-abbb-44e3ab95d657"
}
],
"limit": 1000,
"num_devices_retrieved": 5,
"prev_offset": 0
},
"stat": "OK"
}
Search device(s) by ID:
{
"response": {
"cache_key": "DCA8JB9UFGCANQ53EBRW",
"devices_retrieved": [
{
"date_added": "2023-08-09T14:13:17",
"device_id": "93ea2eac-0b93-4687-addd-44e3ab95d657"
},
],
"num_devices_retrieved": 1
},
"stat": "OK"
}
Retrieve all devices using limit as 1 and offset as 4:
{
"response": {
"cache_key": "DCA8JB9UFGCANQ53EBRW",
"devices_retrieved": [
{
"date_added": "2023-08-09T14:13:17",
"device_id": "93ea2eac-0b93-4687-abbb-44e3ab95d657"
}
],
"limit": 1,
"next_offset": 5,
"num_devices_retrieved": 20,
"prev_offset": 3
},
"stat": "OK"
}
Delete Devices from a Device Cache
Delete devices from an active or pending device cache.
DELETE /device/v1/management_systems/[mkey]/device_cache/[cache_key]/devices
Object limits
40 device IDs per request; 250,000 device IDs per cache.
Parameters
| Parameter | Required? | Description |
|---|---|---|
devices
|
Required |
List of devices IDs to delete. Only deletes device IDs found in the given cache. Example: ["<device_id>", "<device_id>"] |
Response Codes
| Response | Meaning |
|---|---|
| 200 |
Success. Devices removed from the device cache. |
| 400 |
Invalid parameters. |
| 401 |
Invalid identity in request credentials. |
| 404 |
No cache key |
| 413 |
Devices could not be deleted because <num devices> devices are more than the limit of <num devices limit> devices. |
| 500 |
Wrong method used or other internal error. |
Response Format
| Key | Value |
|---|---|
cache_key
|
Device cache unique identifier. |
date_created
|
Date and time of device cache creation. |
device_count
|
Total number of devices remaining in the cache. |
Example Response
{
"stat": "OK",
"response": {
"cache_key": "DCOIV2VVMX5IFX1OW8S2",
"date_created": "2022-02-15T17:02:22",
"deleted_devices": [
"93ea2eac-0b93-4687-aeee-44e3ab95d657"
],
"device_count": 102
}
}
Activate a Device Cache
Delete the existing activated device cache and activate this device cache.
POST /device/v1/management_systems/[mkey]/device_cache/[cache_key]/activate
Parameters
None.
Response Codes
| Response | Meaning |
|---|---|
| 200 |
Success. Device cache activated successfully. |
| 401 |
Invalid identity in request credentials. |
| 404 |
No cache key |
| 409 |
Cannot activate already active cache |
| 500 |
Device cache activation failed. |
Response Format
Empty string.
Example Response
{
"stat": "OK",
"response": ""
}
Delete a Device Cache
Deletes a pending or active device cache.
DELETE /device/v1/management_systems/[mkey]/device_cache/[cache_key]
Parameters
None.
Response Codes
| Response | Meaning |
|---|---|
| 200 |
Success. Device cache deleted. |
| 401 |
Invalid identity in request credentials. |
| 403 |
The target management system does not support cache deletion. |
| 404 |
No cache key |
| 500 |
Device cache deletion failed. |
Response Format
| Key | Value |
|---|---|
cache_key
|
Device cache unique identifier. |
status
|
Status of the deleted device cache. Either "Active" or "Pending". |
Example Response
{
"response": {
"cache_key": "DCA8JB9UFGCANQ53EBRW",
"status": "Pending"
},
"stat": "OK"
}
Retrieve All Existing Caches
Retrieve all caches with provided status.
GET /device/v1/management_systems/[mkey]/device_cache
Query Parameters
| Parameter | Required? | Description |
|---|---|---|
status
|
Required | Device cache status as "active" or "pending". |
Response Codes
| Response | Meaning |
|---|---|
| 200 |
Success. Device cache retrieved. |
| 401 |
Invalid identity in request credentials. |
| 404 |
No management system was found with the given |
| 500 |
Failed to retrieve device cache. |
Response Format
| Key | Value |
|---|---|
cache_key
|
Device cache unique identifier. |
date_created
|
Date and time of device cache creation. |
device_count
|
Total number of devices added to this cache. |
status
|
Status of the cache as either "active" or "pending". |
url
|
Device cache URL. |
Example Response
{
"stat": "OK",
"response": [{
"cache_key": "DC91GTT7V1FE1PTFNBUW",
"date_created": "2022-02-14T16:48:08",
"device_count": 123,
"status": "active",
"url": "https://api-XXXXXXXX.duosecurity.com/device/v1/management_systems/DMYTA3HRS9YVB96CORJ6/device_cache/DC91GTT7V1FE1PTFNBUW"
},
{
"cache_key": "DCIRGVRCMUYLHBLII0OR",
"date_created": "2022-02-15T16:48:12",
"device_count": 125,
"status": "pending",
"url": "https://api-XXXXXXXX.duosecurity.com/device/v1/management_systems/DMYTA3HRS9YVB96CORJ6/device_cache/DCIRGVRCMUYLHBLII0OR"
}
]}
Retrieve Device Cache
Retrieve a device cache.
GET /device/v1/management_systems/[mkey]/device_cache/[cache_key]
Parameters
None.
Response Codes
| Response | Meaning |
|---|---|
| 200 |
Success. Device upload cache retrieved. |
| 401 |
Invalid identity in request credentials. |
| 404 |
No cache key |
| 500 |
Failed to retrieve device cache. |
Response Format
| Key | Value |
|---|---|
cache_key
|
Device cache unique identifier. |
date_created
|
Date and time of device cache creation. |
device_count
|
Total number of devices added to this cache. |
status
|
Status of the cache as either "active" or "pending". |
url
|
URL representing the new device cache. |
Example Response
{
"stat": "OK",
"response": {
"cache_key": "DC91GTT7V1FE1PTFNBUW",
"date_created": "2022-02-14T16:48:08",
"device_count": 123,
"status": "active",
"url": "https://api-XXXXXXXX.duosecurity.com/device/v1/management_systems/DMYTA3HRS9YVB96CORJ6/device_cache/DC91GTT7V1FE1PTFNBUW"
}
}
Troubleshooting
Need some help? Take a look at our Device API Knowledge Base articles or Community discussions. For further assistance, contact Support.
If you receive 401 error responses to your API requests, check the following:
All signing versions
- Is the
Authorizationheader correctly formatted? If not, you may receive a 40101 error. - Does your framework override the
Dateheader? The HTTPDate:header must be exactly the same string as was signed. This could result in a 40103 error. - Are the
Dateand time zone used RFC 2822 compliant? If not, you may get a 40104 or 40105 response. - Are the
Content-LengthandContent-Typeheaders correct? For v5,Content-Typemust beapplication/jsonfor POST requests. For v2, it must beapplication/x-www-form-urlencoded.
v5 signing
- Is the JSON body serialized identically when computing the SHA-512 hash and when sending the request? Differences in key order, whitespace, or encoding between the hashed body and the sent body will break the signature.
- Are both the body hash and headers hash lines present in the canonical string? These lines must always be included, even when the body is empty or no
X-Duo-*headers are sent. Use the SHA-512 hash of the empty string for those cases. - For GET or DELETE requests, is the body hash computed on the empty string (not
{})? - Are
X-Duo-*header names lowercased and lexicographically sorted before computing the headers hash? - Are query string parameters lexicographically sorted in the canonical string?
- Are SHA-512 hex digests lowercase? The body hash, headers hash, and HMAC signature must all use lowercase hexadecimal characters.
- Use the Duo HMAC Debugger tool to verify your HMAC signature against expected values.
v2 signing
- Are the parameters lexicographically sorted?
- Did you include a line for parameters when constructing the signature, even if you're not passing in any parameters?
- Are hex digits in URL-encoded escape sequences upper-case (e.g.
%2F, not%2f)?