Duo Admin API Integration
11 minute read
Overview
Duo Security’s Admin API provides programmatic access to authentication logs, administrator actions, telephony records, and security events from your Duo-protected infrastructure. The Edge Delta HTTP Pull source integrates seamlessly with all five Duo Admin API log endpoints, enabling real-time security monitoring and compliance reporting through your observability platform.
Key Benefits
- Security Monitoring - Track multi-factor authentication events across your organization
- Compliance Auditing - Maintain complete audit trails of administrative actions
- Cost Analysis - Monitor telephony usage and credits for voice/SMS authentications
- Threat Detection - Identify anomalous authentication patterns and potential breaches
- Activity Tracking - Comprehensive visibility into user and admin activities
Quick Reference - Duo Log Types
Log Type | API Version | Timestamp Format | Pull Interval | Primary Use Case |
---|---|---|---|---|
Authentication | v2 | Milliseconds | 5m | MFA events, login tracking |
Administrator | v1 | Seconds | 6m | Admin actions, config changes |
Telephony | v2 | Milliseconds | 7m | Voice/SMS usage, costs |
Activity | v2 | Milliseconds | 8m | User activity patterns |
Offline Enrollment | v1 | Seconds | 10m | Hardware token enrollment |
API Version Timestamp Differences
v1 API endpoints use Unix timestamps in seconds v2 API endpoints use Unix timestamps in milliseconds (multiply seconds by 1000)Authentication
Duo Admin API requires HMAC-SHA1 signature authentication for all requests. Unlike simple token-based authentication, Duo’s approach requires constructing a canonical request string and generating a cryptographic signature.
Prerequisites
- Create a Duo Admin API application in your Duo Admin Panel
- Note your Integration Key (starts with
DI
) - Securely store your Secret Key
- Record your API hostname (e.g.,
api-xxxxxxxx.duosecurity.com
)
Environment Variables
Configure these environment variables before deploying your Edge Delta pipeline:
# Duo API Credentials
export DUO_IKEY="DIXXXXXXXXXXXXXXXXXX" # Your Integration Key
export DUO_SKEY="your_secret_key_here" # Your Secret Key (keep secure!)
export DUO_API_HOST="api-xxxxxxxx.duosecurity.com" # Your API hostname
# Optional: Customize pull intervals
export DUO_AUTH_PULL_INTERVAL="5m" # Authentication logs interval
export DUO_ADMIN_PULL_INTERVAL="6m" # Administrator logs interval
export DUO_TELEPHONY_PULL_INTERVAL="7m" # Telephony logs interval
export DUO_ACTIVITY_PULL_INTERVAL="8m" # Activity logs interval
export DUO_OFFLINE_PULL_INTERVAL="10m" # Offline enrollment interval
HMAC-SHA1 Signature Construction
The Duo API authentication process requires building a canonical request string and signing it with your secret key.
Important: Single-Line OTTL Requirement
Edge Delta configuration requires OTTL expressions to be on a single line. Throughout this documentation, authorization headers are shown on single lines ready for copy-paste into your configuration.Understanding the HMAC Components:
-
Date Header: RFC 2822 formatted timestamp
Date: FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700")
-
Canonical String: Concatenate with newlines
date\nmethod\nhost\npath\nparams
-
HMAC Signature: Sign with secret key using SHA1
EDXHmac(canonical_string, secret_key, "sha1", "hex")
-
Authorization Header: Basic auth with ikey:signature
Authorization: Concat(["Basic ", EDXEncode(Concat([ikey, ":", signature], ""), "base64", false)], "")
Authentication Logs
Monitor multi-factor authentication events including success/failure rates, authentication methods, and device information.
Duo API Reference: Authentication Logs Endpoint
nodes:
- name: duo_authentication_logs
type: http_pull_input
endpoint: https://api-xxxxxxxx.duosecurity.com/admin/v2/logs/authentication
method: GET
pull_interval: 5m
header_expressions:
Date: FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700")
Accept: '"application/json"'
Authorization: Concat(["Basic ", EDXEncode(Concat([EDXEnv("DUO_IKEY", ""), ":", EDXHmac(Format("%s\n%s\n%s\n%s\n%s", [FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700"), "GET", EDXEnv("DUO_API_HOST", ""), "/admin/v2/logs/authentication", Concat(["limit=1000&mintime=", String(Int(UnixSeconds(Now()) - 360) * 1000), "&sort=ts%3Adesc"], "")]), EDXEnv("DUO_SKEY", ""), "sha1", "hex")], ""), "base64", false)], "")
parameter_expressions:
limit: '"1000"'
sort: '"ts:desc"'
mintime: String(Int(UnixSeconds(Now()) - 360) * 1000)
Key Fields in Response:
user
: Username and user keyfactor
: Authentication method (push, sms, phone, hardware_token)result
: Success, failure, or deniedapplication
: Protected application nameaccess_device
: Browser and IP informationauth_device
: Authentication device details
Administrator Logs
Track administrative actions and configuration changes within your Duo environment.
Duo API Reference: Administrator Logs Endpoint
nodes:
- name: duo_administrator_logs
type: http_pull_input
endpoint: https://api-xxxxxxxx.duosecurity.com/admin/v1/logs/administrator
method: GET
pull_interval: 6m
header_expressions:
Date: FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700")
Accept: '"application/json"'
Authorization: Concat(["Basic ", EDXEncode(Concat([EDXEnv("DUO_IKEY", ""), ":", EDXHmac(Format("%s\n%s\n%s\n%s\n%s", [FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700"), "GET", EDXEnv("DUO_API_HOST", ""), "/admin/v1/logs/administrator", Concat(["mintime=", String(Int(UnixSeconds(Now()) - 420))], "")]), EDXEnv("DUO_SKEY", ""), "sha1", "hex")], ""), "base64", false)], "")
parameter_expressions:
mintime: String(Int(UnixSeconds(Now()) - 420))
Note: v1 API uses seconds, not milliseconds for timestamps.
Key Fields in Response:
username
: Administrator who performed the actionaction
: Type of administrative actionobject
: Target of the actiondescription
: Detailed description of the change
Telephony Logs
Monitor voice call and SMS authentication events, including credit usage.
Duo API Reference: Telephony Logs Endpoint
nodes:
- name: duo_telephony_logs
type: http_pull_input
endpoint: https://api-xxxxxxxx.duosecurity.com/admin/v2/logs/telephony
method: GET
pull_interval: 7m
header_expressions:
Date: FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700")
Accept: '"application/json"'
Authorization: Concat(["Basic ", EDXEncode(Concat([EDXEnv("DUO_IKEY", ""), ":", EDXHmac(Format("%s\n%s\n%s\n%s\n%s", [FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700"), "GET", EDXEnv("DUO_API_HOST", ""), "/admin/v2/logs/telephony", Concat(["limit=1000&mintime=", String(Int(UnixSeconds(Now()) - 480) * 1000), "&maxtime=", String(Int(UnixSeconds(Now())) * 1000), "&sort=ts%3Adesc"], "")]), EDXEnv("DUO_SKEY", ""), "sha1", "hex")], ""), "base64", false)], "")
parameter_expressions:
limit: '"1000"'
sort: '"ts:desc"'
mintime: String(Int(UnixSeconds(Now()) - 480) * 1000)
maxtime: String(Int(UnixSeconds(Now())) * 1000)
Key Fields in Response:
type
: Call or SMSphone
: Phone number usedcredits
: Credits consumed for the call/SMScontext
: Authentication context
Activity Logs
Track user and administrator activity within Duo-protected applications.
Duo API Reference: Activity Logs Endpoint
nodes:
- name: duo_activity_logs
type: http_pull_input
endpoint: https://api-xxxxxxxx.duosecurity.com/admin/v2/logs/activity
method: GET
pull_interval: 8m
header_expressions:
Date: FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700")
Accept: '"application/json"'
Authorization: Concat(["Basic ", EDXEncode(Concat([EDXEnv("DUO_IKEY", ""), ":", EDXHmac(Format("%s\n%s\n%s\n%s\n%s", [FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700"), "GET", EDXEnv("DUO_API_HOST", ""), "/admin/v2/logs/activity", Concat(["limit=1000&mintime=", String(Int(UnixSeconds(Now()) - 540) * 1000), "&maxtime=", String(Int(UnixSeconds(Now())) * 1000), "&sort=ts%3Adesc"], "")]), EDXEnv("DUO_SKEY", ""), "sha1", "hex")], ""), "base64", false)], "")
parameter_expressions:
limit: '"1000"'
sort: '"ts:desc"'
mintime: String(Int(UnixSeconds(Now()) - 540) * 1000)
maxtime: String(Int(UnixSeconds(Now())) * 1000)
Key Fields in Response:
action
: Activity typeactor
: Entity performing the actiontarget
: Entity being acted uponcontext
: Additional context information
Offline Enrollment Logs
Monitor offline enrollment activities for hardware tokens and mobile authenticators.
Duo API Reference: Offline Enrollment Logs Endpoint
nodes:
- name: duo_offline_enrollment_logs
type: http_pull_input
endpoint: https://api-xxxxxxxx.duosecurity.com/admin/v1/logs/offline_enrollment
method: GET
pull_interval: 10m
header_expressions:
Date: FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700")
Accept: '"application/json"'
Authorization: Concat(["Basic ", EDXEncode(Concat([EDXEnv("DUO_IKEY", ""), ":", EDXHmac(Format("%s\n%s\n%s\n%s\n%s", [FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700"), "GET", EDXEnv("DUO_API_HOST", ""), "/admin/v1/logs/offline_enrollment", Concat(["mintime=", String(Int(UnixSeconds(Now()) - 720))], "")]), EDXEnv("DUO_SKEY", ""), "sha1", "hex")], ""), "base64", false)], "")
parameter_expressions:
mintime: String(Int(UnixSeconds(Now()) - 720))
Note: v1 API uses seconds for timestamps.
Key Fields in Response:
user
: User being enrolledaction
: Enrollment action typedescription
: Enrollment details
Pagination
Duo Admin API uses offset-based pagination for large result sets. The API returns metadata with pagination information:
{
"metadata": {
"next_offset": 1000,
"prev_offset": 0,
"total_objects": 5432
},
"response": [...]
}
To handle pagination in Edge Delta:
pagination:
url_json_path: "$['metadata']['next_offset']"
max_parallel_requests: 3
# Include offset in parameters if continuing from previous page
parameter_expressions:
limit: '"1000"'
offset: "0" # Start from 0, use next_offset for subsequent pages
Best Practices:
- Use
limit=1000
for optimal performance - Process pages sequentially to maintain order
- Monitor
total_objects
to track data volume - Implement retry logic for partial page failures
Rate Limiting
Duo enforces rate limits to ensure API stability. Follow these guidelines to avoid throttling:
Recommended Configuration
Best Practice
Use staggered intervals across different log types to avoid concurrent API calls and distribute load evenly.Log Type | Pull Interval | Lookback Window | Why This Interval? |
---|---|---|---|
Authentication | 5 minutes | 6 minutes | High-priority security events need timely detection |
Administrator | 6 minutes | 7 minutes | Less frequent but critical configuration changes |
Telephony | 7 minutes | 8 minutes | Balance between cost tracking and API load |
Activity | 8 minutes | 9 minutes | Lower priority general activity monitoring |
Offline Enrollment | 10 minutes | 12 minutes | Infrequent enrollment events |
Critical Rate Limit Guidelines
Limit Type | Value | Impact | Recommendation |
---|---|---|---|
Hard Limit | 50 calls/min | 429 errors | Never exceed for bulk operations |
Soft Limit | 1 call/min/endpoint | Throttling risk | Stay well below this |
Log Delay | 2 minutes | Missing recent data | Account for in lookback window |
Retry Backoff | 30s → 5m | Recovery time | Implement exponential backoff |
Rate Limit Configuration Example
# Implement exponential backoff on rate limit errors
retry_http_code: [429, 503]
retry_max_attempts: 3
retry_initial_interval: 30s
retry_max_interval: 5m
Troubleshooting
401 Unauthorized: Invalid Signature
Error: {"code": 40103, "message": "Invalid signature in request credentials"}
Common Causes & Solutions:
-
Incorrect Date Format
- ✅ Correct:
Mon, 02 Jan 2006 15:04:05 -0700
- ❌ Wrong:
2006-01-02T15:04:05Z
- Ensure RFC 2822 format exactly
- ✅ Correct:
-
Secret Key Issues
- Verify
DUO_SKEY
environment variable is set correctly - Check for extra spaces or newlines in the secret key
- Regenerate keys if compromised
- Verify
-
Canonical String Mismatch
- Ensure query parameters are URL-encoded (
:
→%3A
) - Verify host matches exactly (include subdomain)
- Check path starts with
/
- Ensure query parameters are URL-encoded (
-
Clock Skew
- Synchronize system time with NTP
- Maximum allowed drift is 5 minutes
429 Too Many Requests
Error: {"code": 42901, "message": "Too Many Requests"}
Solutions:
-
Increase Pull Intervals
pull_interval: 10m # Increase from 5m
-
Implement Staggered Starts
- Don’t start all pulls simultaneously
- Use different intervals for each log type
-
Add Retry Logic
retry_http_code: [429] retry_max_attempts: 3 retry_initial_interval: 60s
-
Monitor API Usage
- Check Duo Admin Panel for API usage metrics
- Set up alerts for rate limit approaches
Timestamp Format Mismatches
Error: {"code": 40002, "message": "Invalid request parameters", "message_detail": "mintime"}
Common Mistakes:
-
v1 vs v2 API Confusion
- v1 endpoints (Administrator, Offline): Use seconds
mintime: String(Int(UnixSeconds(Now()) - 420))
- v2 endpoints (Authentication, Telephony, Activity): Use milliseconds
mintime: String(Int(UnixSeconds(Now()) - 360) * 1000)
- v1 endpoints (Administrator, Offline): Use seconds
-
Missing Multiplication
- ❌ Wrong:
String(Int(UnixSeconds(Now()) - 360))
for v2 - ✅ Right:
String(Int(UnixSeconds(Now()) - 360) * 1000)
for v2
- ❌ Wrong:
-
Parameter Availability
- v1 endpoints: Only support
mintime
- v2 endpoints: Support both
mintime
andmaxtime
- v1 endpoints: Only support
Complete Pipeline Example
Here’s a production-ready configuration combining all five Duo log types with proper error handling.
Remember: Single-Line Authorization Headers
The Authorization headers in this example must be on a single line in your actual configuration.version: v3
settings:
tag: duo-security-logs
log:
level: info
nodes:
# Edge Delta system monitoring
- name: ed_system_stats
type: ed_system_stats_input
# Authentication Logs - Primary security events
- name: duo_auth_logs
type: http_pull_input
endpoint: https://api-xxxxxxxx.duosecurity.com/admin/v2/logs/authentication
method: GET
pull_interval: 5m
retry_http_code: [429, 503]
retry_max_attempts: 3
header_expressions:
Date: FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700")
Accept: '"application/json"'
Authorization: Concat(["Basic ", EDXEncode(Concat([EDXEnv("DUO_IKEY", ""), ":", EDXHmac(Format("%s\n%s\n%s\n%s\n%s", [FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700"), "GET", EDXEnv("DUO_API_HOST", ""), "/admin/v2/logs/authentication", Concat(["limit=1000&mintime=", String(Int(UnixSeconds(Now()) - 360) * 1000), "&sort=ts%3Adesc"], "")]), EDXEnv("DUO_SKEY", ""), "sha1", "hex")], ""), "base64", false)], "")
parameter_expressions:
limit: '"1000"'
sort: '"ts:desc"'
mintime: String(Int(UnixSeconds(Now()) - 360) * 1000)
# Administrator Logs - Admin actions
- name: duo_admin_logs
type: http_pull_input
endpoint: https://api-xxxxxxxx.duosecurity.com/admin/v1/logs/administrator
method: GET
pull_interval: 6m
retry_http_code: [429, 503]
header_expressions:
Date: FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700")
Accept: '"application/json"'
Authorization: Concat(["Basic ", EDXEncode(Concat([EDXEnv("DUO_IKEY", ""), ":", EDXHmac(Format("%s\n%s\n%s\n%s\n%s", [FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700"), "GET", EDXEnv("DUO_API_HOST", ""), "/admin/v1/logs/administrator", Concat(["mintime=", String(Int(UnixSeconds(Now()) - 420))], "")]), EDXEnv("DUO_SKEY", ""), "sha1", "hex")], ""), "base64", false)], "")
parameter_expressions:
mintime: String(Int(UnixSeconds(Now()) - 420))
# Add Telephony, Activity, and Offline Enrollment with similar patterns...
# Processing pipeline
- name: duo_processor
type: sequence
- name: duo_enrichment
type: ottl
statements: |
// Add common fields
set(attributes["source"], "duo")
set(attributes["log_type"], attributes["_http_pull_source"])
// Parse timestamps correctly based on API version
set(attributes["timestamp"], Int(cache["ts"]) * 1000000) where cache["ts"] != nil
set(attributes["timestamp"], Int(cache["timestamp"]) * 1000000000) where cache["timestamp"] != nil
# Output destination
- name: edge_delta_output
type: ed_output
# Links connecting the pipeline
links:
- from: ed_system_stats
to: edge_delta_output
- from: duo_auth_logs
to: duo_processor
- from: duo_admin_logs
to: duo_processor
- from: duo_processor
to: duo_enrichment
- from: duo_enrichment
to: edge_delta_output
Security Best Practices
Security Notice
Never commit Duo API credentials to version control. Always use environment variables or secure secret management systems.Credential Management
-
Use Environment Variables
- Store
DUO_IKEY
andDUO_SKEY
in environment variables - Never hardcode credentials in configuration files
- Use
EDXEnv()
function to reference credentials
- Store
-
Rotate Keys Regularly
- Follow your organization’s key rotation policy
- Update both Edge Delta and Duo when rotating
- Test new keys in a non-production environment first
-
Limit API Permissions
- Create dedicated API applications for Edge Delta
- Grant minimum required permissions
- Use read-only access for log retrieval
-
Monitor API Usage
- Set up alerts for unusual API activity
- Review Duo Admin Panel for access patterns
- Monitor for authentication failures in Edge Delta logs
Network Security
-
Use HTTPS Exclusively
- All Duo API calls must use HTTPS
- Verify SSL certificates
- Don’t disable certificate validation
-
Implement IP Restrictions
- Configure API access from specific IP ranges in Duo
- Use Edge Delta’s static IP addresses if available
- Monitor for access from unexpected locations
Copy-Paste Ready Authorization Headers
Quick Copy Section
These are the exact single-line Authorization headers for each endpoint. Copy and paste directly into your Edge Delta configuration.Authentication Logs (v2 - milliseconds)
Authorization: Concat(["Basic ", EDXEncode(Concat([EDXEnv("DUO_IKEY", ""), ":", EDXHmac(Format("%s\n%s\n%s\n%s\n%s", [FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700"), "GET", EDXEnv("DUO_API_HOST", ""), "/admin/v2/logs/authentication", Concat(["limit=1000&mintime=", String(Int(UnixSeconds(Now()) - 360) * 1000), "&sort=ts%3Adesc"], "")]), EDXEnv("DUO_SKEY", ""), "sha1", "hex")], ""), "base64", false)], "")
Administrator Logs (v1 - seconds)
Authorization: Concat(["Basic ", EDXEncode(Concat([EDXEnv("DUO_IKEY", ""), ":", EDXHmac(Format("%s\n%s\n%s\n%s\n%s", [FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700"), "GET", EDXEnv("DUO_API_HOST", ""), "/admin/v1/logs/administrator", Concat(["mintime=", String(Int(UnixSeconds(Now()) - 420))], "")]), EDXEnv("DUO_SKEY", ""), "sha1", "hex")], ""), "base64", false)], "")
Telephony Logs (v2 - milliseconds)
Authorization: Concat(["Basic ", EDXEncode(Concat([EDXEnv("DUO_IKEY", ""), ":", EDXHmac(Format("%s\n%s\n%s\n%s\n%s", [FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700"), "GET", EDXEnv("DUO_API_HOST", ""), "/admin/v2/logs/telephony", Concat(["limit=1000&mintime=", String(Int(UnixSeconds(Now()) - 480) * 1000), "&maxtime=", String(Int(UnixSeconds(Now())) * 1000), "&sort=ts%3Adesc"], "")]), EDXEnv("DUO_SKEY", ""), "sha1", "hex")], ""), "base64", false)], "")
Activity Logs (v2 - milliseconds)
Authorization: Concat(["Basic ", EDXEncode(Concat([EDXEnv("DUO_IKEY", ""), ":", EDXHmac(Format("%s\n%s\n%s\n%s\n%s", [FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700"), "GET", EDXEnv("DUO_API_HOST", ""), "/admin/v2/logs/activity", Concat(["limit=1000&mintime=", String(Int(UnixSeconds(Now()) - 540) * 1000), "&maxtime=", String(Int(UnixSeconds(Now())) * 1000), "&sort=ts%3Adesc"], "")]), EDXEnv("DUO_SKEY", ""), "sha1", "hex")], ""), "base64", false)], "")
Offline Enrollment Logs (v1 - seconds)
Authorization: Concat(["Basic ", EDXEncode(Concat([EDXEnv("DUO_IKEY", ""), ":", EDXHmac(Format("%s\n%s\n%s\n%s\n%s", [FormatTime(Now(), "Mon, 02 Jan 2006 15:04:05 -0700"), "GET", EDXEnv("DUO_API_HOST", ""), "/admin/v1/logs/offline_enrollment", Concat(["mintime=", String(Int(UnixSeconds(Now()) - 720))], "")]), EDXEnv("DUO_SKEY", ""), "sha1", "hex")], ""), "base64", false)], "")
Additional Resources
- Duo Admin API Official Documentation
- Duo API Rate Limits
- Edge Delta HTTP Pull Documentation
- OTTL Expression Reference
- Duo Security Best Practices
Support
For assistance with Duo API integration:
- Duo Support: Contact through Duo Admin Panel
- Edge Delta Support: support@edgedelta.com
- Community Forum: Edge Delta Community