Contents
The Accounts API allows a parent account to create, manage, and delete other Duo Security customer accounts.
The Accounts API allows Duo Premier, Advantage, and Essentials customers to create, manage, and delete additional related Duo Security customer accounts.
Overview
The Accounts API lets customers programmatically create, delete, and manage individual Duo customer accounts. New Duo accounts created using the Accounts API are subaccounts of the account where the Accounts API application exists, creating a "parent" and "child" account relationship. A Duo account can have multiple child accounts, but a child account may only have one parent and no child accounts of its own.
About Duo APIs
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).
Properties that enumerate choices may gain new values at any time, e.g. the device platform
value could return new device platforms that did not previously exist. Duo will update our API documentation with new values in a timely fashion.
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
Role required: Owner
Note that only administrators with the Owner role in the parent account may contact Duo Support to request access to the Accounts API application, or can create or modify an Accounts API application in the Duo Admin Panel.
-
Log in to the Duo Admin Panel and navigate to Applications → Protect an Application.
-
Locate the entry for Accounts API in the applications list. Click Protect to the far-right to configure the application and get your integration key, secret key, and API hostname. You'll need this information to complete your setup. See Protecting Applications for more information about protecting applications with Duo and additional application options.
Treat your secret key like a passwordThe security of your Duo application is tied to the security of your secret key (skey). Secure it as you would any sensitive credential. Don't share it with unauthorized individuals or email it to anyone under any circumstances! -
Optionally specify which IP addresses or ranges are allowed to use this Accounts API application in Networks for API Access. If you do not specify any IP addresses or ranges, this Accounts API application may be accessed from any network.
The Accounts API performs the IP check occurs after verifying the authentication signature in a request. If you restrict the allowed networks for API access and see logged events for blocked Accounts API requests from unrecognized IP addresses, this may indicate compromise of your Accounts API application's secret key.
This application communicates with Duo's service on SSL TCP port 443.
Firewall configurations that restrict outbound access to Duo's service with rules using destination IP addresses or IP address ranges aren't recommended, since these may change over time to maintain our service's high availability. If your organization requires IP-based rules, please review Duo Knowledge Base article 1337.
Effective June 30, 2023, Duo no longer supports TLS 1.0 or 1.1 connections or insecure TLS/SSL cipher suites. See Duo Knowledge Base article 7546 for additional guidance.
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)
API Methods
Retrieve Accounts
Returns a list of child accounts.
POST /accounts/v1/account/list
Parameters
None.
Response codes
Response | Meaning |
---|---|
200 | Success. Returns a list of accounts. |
Response format
Key | Value |
---|---|
account_id
|
The child customer account ID. This is a 20 character string, for example DA9VZOC5X63I2W72NRP9 .
|
name
|
The customer's name. |
api_hostname
|
Use this hostname instead of the parent account's when configuring the customer's applications. |
Example response
{
"stat": "OK",
"response": [{
"account_id": "DA9VZOC5X63I2W72NRP9",
"api_hostname": "api-abcd1234.duosecurity.com",
"name": "Acme Corp"
}]
}
Create Account
Create a new child account.
POST /accounts/v1/account/create
Parameters
Parameter | Required? | Description |
---|---|---|
name
|
Required | Name for the new customer. |
Response codes
Response | Meaning |
---|---|
200 | Success. Returns the newly created account. |
400 | Invalid or missing parameters. |
Response format
Same as Retrieve Accounts.
Example response
{
"stat": "OK",
"response": {
"account_id": "DA9VZOC5X63I2W72NRP9",
"api_hostname": "api-abcd1234.duosecurity.com",
"name": "Acme Corp"
}
}
Delete Account
Delete the account with ID account_id
from the system.
POST /accounts/v1/account/delete
Parameters
Parameter | Required? | Description |
---|---|---|
account_id
|
Required |
ID of the customer account to delete as returned by Retrieve Accounts. This is a 20 character string, for example DA9VZOC5X63I2W72NRP9 .
|
Response codes
Response | Meaning |
---|---|
200 | The account was deleted or did not exist. |
409 | The account is the parent of one or more child accounts. It cannot be deleted until the child accounts are deleted. |
Response format
Empty string.
Example response
{
"stat": "OK",
"response": ""
}
Using Accounts API with Admin API
Duo's Admin API supports programmatic administration of Duo account objects, like users, 2FA devices, integrations, logs, and more. You can use an Accounts API application created in a parent account to manage and query child accounts with Admin API by specifying the child account's API host in the request and the child account's ID in the list of API request parameters as account_id
.
There is no need to create separate Admin API applications in the child accounts. The Accounts API application created in the parent is automatically granted all Admin API permissions; these grants can't be modified.
Here's an example URL-encoded GET query string on the Integrations Admin API endpoint that retrieves a list of integrations in a child account specified with account_id
:
GET /admin/v1/integrations?account_id=DAAARXMAKLQ2ZKD571YC
Here's an example POST on the Create User Admin API endpoint that creates a new user in the child account specified with account_id
:
POST /admin/v1/users?account_id=DAAARXMAKLQ2ZKD571YC&email=narroway@example.com&realname=Norben%20Arroway&username=narroway
When making the request, ensure you do the following:
- Change the
host
value used to construct the signature from theapi_hostname
of the parent account to theapi_hostname
of the child account. - Send the request to the child account's API hostname as retrieved with Retrieve Accounts.
- Specify the subaccount
account_id
in the request path.
For example, if the following information was returned for a given child account via /accounts/v1/account/list
:
{
"stat": "OK",
"response": {
"account_id": "DA9VZOC5X63I2W72NRP9",
"api_hostname": "api-abcd1234.duosecurity.com",
"name": "Acme Corp"
}
}
Admin API requests via Accounts API to that child should use the api_hostname
value api-abcd1234.duosecurity.com
.
Additional Admin API Methods
These Admin API methods are not yet generally available with Accounts API. Please contact Duo Support to request access to these methods.
Get Edition
Returns the Duo edition information for a child account.
GET /admin/v1/billing/edition
Parameters
Parameter | Required? | Description |
---|---|---|
account_id
|
Required |
The child customer account ID as returned by Retrieve Accounts. This is a 20 character string, for example DA9VZOC5X63I2W72NRP9 .
|
Response codes
Response | Meaning |
---|---|
200 | Success. Returns the edition of the child account. |
400 | Invalid or missing parameters. |
401 | Invalid integration key in request credentials. |
Response format
Key | Value |
---|---|
edition
|
The edition. One of:
|
Example response
{
"stat": "OK",
"response": {"edition": "PLATFORM"}
}
Set Edition
Sets the effective Duo edition for a child account.
POST /admin/v1/billing/edition
Parameters
Parameter | Required? | Description |
---|---|---|
account_id
|
Required |
The child customer account ID as returned by Retrieve Accounts. This is a 20 character string, for example DA9VZOC5X63I2W72NRP9 .
|
edition
|
Required |
The edition to set. This should be one of:
|
Response codes
Response | Meaning |
---|---|
200 | Success. |
400 | Invalid or missing parameters. |
401 | Invalid integration key in request credentials. |
Response format
The response is empty.
Example response
{
"stat": "OK",
"response": ""
}
Get Telephony Credits
Returns the available telephony credits for a child account.
GET /admin/v1/billing/telephony_credits
Parameters
Parameter | Required? | Description |
---|---|---|
account_id
|
Required |
The child customer account ID as returned by Retrieve Accounts. This is a 20 character string, for example DA9VZOC5X63I2W72NRP9 .
|
Response codes
Response | Meaning |
---|---|
200 | Success. Returns the available telephony credits of the child account. |
400 | Invalid or missing parameters. |
401 | Invalid integration key in request credentials. |
Response format
Key | Value |
---|---|
credits
|
The available telephony credits. |
Example response
{
"stat": "OK",
"response": {"credits": 10}
}
Set Telephony Credits
Sets the telephony credits for a child account.
Any additional credits added to the child account are transferred from the parent account. For example, if the child account has 100 credits and it is then set to 300 credits, then 200 credits are deducted from the parent's balance and added to the child's balance.
POST /admin/v1/billing/telephony_credits
Parameters
Parameter | Required? | Description |
---|---|---|
account_id
|
Required |
The child customer account ID as returned by Retrieve Accounts. This is a 20 character string, for example DA9VZOC5X63I2W72NRP9 .
|
credits
|
Required | The total number of credits that the child account will have after transferring credits from the parent account. |
Response codes
Response | Meaning |
---|---|
200 | Success. |
400 | Invalid or missing parameters. |
401 | Invalid integration key in request credentials. |
Response format
Key | Value |
---|---|
credits_added
|
The number of telephony credits that were added to the child account and deducted from the parent account. |
Example response
{
"stat": "OK",
"response": {"credits_added":10}
}
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: ?realname=First%20Last&username=root
. They still go on a separate line when creating the string to sign for an Authorization header.
Send parameters for POST requests in the body as URL-encoded key-value pairs (the same request format used by browsers to submit form data).
The header "Content-Type: application/x-www-form-urlencoded
" must also be present.
When URL-encoding, 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
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 |
|
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
/accounts/v1/account/create
name=Acme%20Corp
GET requests also use this five-line format:
Tue, 21 Aug 2012 17:29:18 -0000
GET
api-xxxxxxxx.duosecurity.com
/admin/v1/billing/edition
account_id=DAQTM8P00LAII8ESDADE
Lastly, compute the HMAC-SHA1 of this canonical representation, using the secret key of the Duo Accounts API application you created in the parent account 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 Accounts API integration key as the username and the HMAC-SHA1 signature as the password.
For example, here are the headers for the above POST request to api-xxxxxxxx.duosecurity.com/accounts/v1/account/create
, using DIWJ8X6AEYOR5OMC6TQ1
as the integration key and Zh5eGmUq9zpfQnyUIu5OL9iWoMMv5ZNmk3zLJ4Ep
as the secret key:
Date: Tue, 21 Aug 2012 17:29:18 -0000
Authorization: Basic RElXSjhYNkFFWU9SNU9NQzZUUTE6ODEyZjdhMzg5NjBlZDZlYzdhNDhjY2EyZjZiYjAwMmUyMDFjMjliOQ==
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)}
Troubleshooting
Need some help? Take a look at our Accounts API Knowledge Base articles or Community discussions. For further assistance, contact Support.
If you receive an error message indicating "Cross-deployment Admin API usage through Accounts API is currently not available" when using Accounts API with Admin API, make sure you specified the subaccount's API hostname correctly when constructing the HMAC signature for the request.
If you receive 401 error responses to your API requests, check the following:
- Is the
Authorization
header correctly formatted? If not, you may receive a 40101 error. - Does your framework override the
Date
header? The HTTPDate:
header must be exactly the same string as was signed. This could result in a 40103 error. - Are the
Date
and time zone used RFC 3339 compliant?? If not, you may get a 40104 or 40105 response. - 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 any hex digits lower-case?
- Are the
Content-Length
andContent-Type
parameters correct? If not, your parameters may be ignored or you may receive a 40103 response because your signature considered parameters that the service didn't receive.