Documentation

Verify API

The Verify API provides the ability to call or text (SMS) a phone number with a one-time PIN number.

Overview

Verification Methods

The general flow of the verification methods (/call and /sms) looks like this:

  1. The application requests that Duo make a call or send an SMS with a one-time PIN number.
  2. The PIN number is returned to the application and Duo places the call or sends the SMS.
  3. The application prompts the user for the PIN.
  4. The user enters the PIN.
  5. The application checks for correctness.

If the PIN is delivered via phone call, the application can optionally call the /status method to check on the status of the call (i.e. INITIALIZED, DIALING, ANSWERED, etc.).

Examples

See duo_verify on GitHub for examples of this API in use.

First Steps

Before starting:

  1. Sign up for a Duo account
  2. Create a new Verify API integration. This will generate an integration key and secret key for you to use. (See Getting Started for help).

Duo Security has demonstration clients available on Github to call the Duo API methods. Examples are available in: Python, Java, C#, Ruby, and Perl.

Connectivity Requirements

This integration communicates with Duo’s service on TCP port 443. Also, we do not recommend locking down your firewall to individual IP addresses, since these may change over time to maintain our service’s high availability.

API Methods

/call

The /call method places a voice call with a PIN number and returns that PIN number to the method caller.

POST /verify/v1/call

Parameters

Param Required? Description
phone Required The phone number of the call recipient, formatted in the E.164 format. Spaces and dashes are optional. For example, a US number could look like “+1 555 555-5555” or “+15555555555”
message Required The message to speak to the call recipient. The term “<pin>” will be replaced with the actual one-time PIN provided (or generated) for the call. For example: “Your PIN is <pin>”.
caller Optional The number to appear in the recipient’s caller ID
pin Optional A numeric PIN to be sent on the call. We will generate one if this is not provided.
digits Optional If we generate a PIN, this parameter specifies its length. If not provided, the PIN will be 4 digits long. If the pin parameter is specified, then this parameter has no effect.
extension Optional The extension dialed after the call has connected. If this parameter is not specified then no extension will be dialed.
predelay Optional The number of seconds to wait after the call has connected and before dialing an extension.
postdelay Optional The number of seconds to wait after dialing the extension and before speaking the voice message.

Response Codes

Response Meaning
200 Success.
400 Invalid or missing parameters or message template does not contain “<pin>”.
404 Invalid phone number.

Response Format

Key Value
pin The sent PIN
txid A unique ID for this call

Example Successful Response

{
  "stat": "OK",
  "response": {
    "pin": "1234",
    "txid": "9c1d4bbe-3efb-4e29-9976-daed185d561a"
  }
}

/status

Poll for a call’s status.

GET /verify/v1/status

Parameters

Param Required? Description
txid Required The ID of the call (returned as txid from /call)

Response Codes

Response Meaning
200 Success.
400 Invalid or missing parameters or no call in progress with this ID.
503 Call time limit reached with no response.

Response Format

Key Value
state The state of the call: “started”, “progress”, or “ended”
event The most recent event of the call: “INITIALIZED”, “DIALING”, “ANSWERED”, “DIGITS”, “ERROR”, or “COMPLETED”
info A human-readable string describing the most recent event

This API call will return the earliest available status about the phone call that has not been returned by an earlier call. To monitor a phone call’s status, continue polling as the call moves from started to ended.

Call status records will expire 120 seconds after the initial call request. If you request a call status after it has expired, you will receive a 400 error. Checking the status after an “ended” status will not return a response before it expires, since the call will be waiting to return the next status message, which will never come. This method should only be called once at a time; if a second call is made while a first caller is waiting for a result, the first caller will be ignored.

Example Successful Response

{
  "stat": "OK",
  "response": {
    "info": "Call has been answered",
    "state": "progress",
    "event": "ANSWERED"
  }
}

/sms

The /sms method sends a text message with a PIN number to the specified number, and returns that PIN number to the method caller.

POST /verify/v1/sms

Parameters

Param Required? Description
phone Required The phone number of the SMS recipient, formatted in the E.164 format. Spaces and dashes are optional. For example, a US number could look like “+1 555 555-5555” or “+15555555555”
message Required The message to send to the SMS recipient. The term “<pin>” will be replaced with the actual one-time PIN provided (or generated) for the message. For example: “Hello. Your one-time PIN is <pin>”.
pin Optional A numeric PIN to be sent in the SMS message. We will generate one if this is not provided.
digits Optional If we generate a PIN, this parameter specifies its length. If not provided, the PIN will be 4 digits long. If the pin parameter is specified, then this parameter has no effect.

Response Codes

Response Meaning
200 Success.
202 The SMS message could not be sent.
400 Invalid or missing parameters or message template does not contain “<pin>”.
404 Invalid phone number.

Response Format

Key Value
pin The value of the PIN sent

Example Successful Response

{
  "stat": "OK",
  "response": {
    "pin": "1234"
  }
}

API Details

Base URL

All API methods use your API hostname, https://api-XXXXXXXX.duosecurity.com.

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: /accounts/v1/account/list?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.

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).

Authentication

The API uses HTTP Basic Authentication to authenticate requests. Use your integration’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

/accounts/v1/account/list
params The URL-encoded list of key=value pairs, lexicographically sorted by key. These come from the request parameters (the URL query string for GET and DELETE requests or the request body for POST requests). 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. 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/list
realname=First%20Last&username=root

GET requests also use this five-line format:

Tue, 21 Aug 2012 17:29:18 -0000
GET
api-xxxxxxxx.duosecurity.com
/accounts/v1/account/list
username=root

Lastly, compute the HMAC-SHA1 of this canonical representation, using your integration’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.

For example, here are the headers for the above POST request to api-XXXXXXXX.duosecurity.com/accounts/v1/account/list, using DIWJ8X6AEYOR5OMC6TQ1 as the integration key and Zh5eGmUq9zpfQnyUIu5OL9iWoMMv5ZNmk3zLJ4Ep as the secret key:

Date: Tue, 21 Aug 2012 17:29:18 -0000
Authorization: Basic RElXSjhYNkFFWU9SNU9NQzZUUTE6MmQ5N2Q2MTY2MzE5NzgxYjVhM2EwN2FmMzlkMzY2ZjQ5MTIzNGVkYw==
Host: api-XXXXXXXX.duosecurity.com
Content-Length: 35
Content-Type: application/x-www-form-urlencoded

Separate HTTP request header lines CRLF newlines.

The following Python function can be used to construct the “Authorization” and “Date” headers:

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)}