# API Keys Guide Learn how to create and manage API keys for dual-factor authentication # API Keys Management Guide ## 📝 Quick Reference ### Dual Authentication Format ``` 🔑 Client ID: audit1_test_cli_1234567890abcdef1234567890abcdef 🔐 Client Secret: audit1_test_sec_abcdefabcdef1234567890123456789 🔑 Client ID: audit1_live_cli_9876543210fedcba9876543210fedcba 🔐 Client Secret: audit1_live_sec_fedcbafedcba9876543210987654321 │ │ │ └─── 32-character unique ID └────────────── Environment (test/live) ``` ### Key Lifecycle ```mermaid Create → Store Securely → Use in Requests → Rotate Regularly → Revoke if Compromised ``` ### Security Checklist * \[ ] Store client secrets in environment variables or secrets vault * \[ ] Never commit secrets to version control * \[ ] Use different keys for dev/staging/production * \[ ] Rotate keys every 90 days * \[ ] Use HMAC-SHA256 signatures for critical operations * \[ ] Monitor key usage via audit logs * \[ ] Revoke unused keys immediately * \[ ] Label keys for easy identification *** ## Overview Audit1's Developer API uses **dual-factor authentication** with Client ID + Client Secret pairs for enhanced security. This guide covers everything you need to know about creating, managing, and using API keys securely. > **Important**: API keys are created and managed through your Audit1 portal dashboard (API Keys section). > > **Portal URLs by User Type:** > > * **Employers**: [https://employer.audit1.info](https://employer.audit1.info) > * **Payroll Companies**: [https://payrollcompany.audit1.info](https://payrollcompany.audit1.info) > * **Carriers**: [https://carrier.audit1.info](https://carrier.audit1.info) > * **Software Companies**: [https://software.audit1.info](https://software.audit1.info) ## Table of Contents 1. [Understanding Dual Authentication](#understanding-dual-authentication) 2. [Supported Entity Types](#supported-entity-types) 3. [Creating API Keys](#creating-api-keys) 4. [Using API Keys in Requests](#using-api-keys-in-requests) 5. [Request Signature (HMAC-SHA256)](#request-signature-hmac-sha256) 6. [Security Best Practices](#security-best-practices) 7. [Environment Management](#environment-management) 8. [Audit Logging](#audit-logging) 9. [Troubleshooting](#troubleshooting) *** ## Understanding Dual Authentication ### What is Dual Authentication? Audit1 uses a **two-factor API key system** for enhanced security: 1. **Client ID**: Public identifier (can be logged, shown in UI) 2. **Client Secret**: Private secret (stored hashed, shown only once) Both are required for authentication. This provides stronger security than single-key systems. ### Key Components | Component | Format | Visibility | Purpose | |-----------|--------|------------|---------| | Client ID | `audit1_{env}_cli_{32chars}` | Public | Identifies the API key | | Client Secret | `audit1_{env}_sec_{32chars}` | Private | Proves ownership | | Label | User-defined string | Public | Organizes keys | | Environment | `sandbox` or `production` | Public | Data isolation | ### Security Benefits ✅ **Two-factor verification** - Both credentials required\ ✅ **Secure storage** - Secrets hashed with bcrypt\ ✅ **One-time display** - Secrets never shown again after creation\ ✅ **Granular tracking** - Each key independently monitored\ ✅ **Easy rotation** - Replace keys without service disruption *** ## Supported Entity Types API keys can be created for the following entity types: | Entity Type | Use Case | Example | |-------------|----------|---------| | **Employer** | Self-service payroll reporting, audit data access | Manufacturing company reporting own payroll | | **Payroll Company** | Automated payroll streaming for multiple employers | ADP, Paychex reporting payroll data | | **Carrier** | Audit operations, policy endorsement ingestion | Insurance carrier accessing audit reports | | **Software Company** | Platform integrations, broker portals | SaaS provider building WC audit tools | | **User** | Individual developer access, testing | Developer testing API integration | *** ## Creating API Keys ### Via Portal Dashboard 1. **Log in to Your Portal** * Navigate to your entity-specific portal (see URLs above) 2. **Go to API Keys Section** * Dashboard → **Developer** → **API Keys** 3. **Create New Key** * Click **"Create API Key"** button * Fill in the form: * **Label**: Descriptive name (e.g., "Production - Payroll Integration") * **Environment**: Sandbox or Production * Click **"Generate"** 4. **Save Your Credentials** * **Client ID** and **Client Secret** are displayed * **⚠️ Client Secret is shown only once!** * Store securely in password manager or secrets vault * Copy both values before closing ### Example Creation Response ```json { "client_id": "audit1_test_cli_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4", "client_secret": "audit1_test_sec_f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3", "label": "Production - Payroll Integration", "environment": "sandbox", "created_at": "2026-01-06T10:30:00Z", "message": "Store client_secret securely - it will not be shown again" } ``` *** ## Using API Keys in Requests ### Required Headers All authenticated requests must include: ```http X-Client-ID: audit1_test_cli_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4 X-Client-Secret: audit1_test_sec_f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3 ``` ### Basic Request Example ```bash curl -X POST https://api.audit1.info/api/v1/payroll/reports \ -H "X-Client-ID: audit1_test_cli_a1b2c3d4..." \ -H "X-Client-Secret: audit1_test_sec_f6e5d4c3..." \ -H "Content-Type: application/json" \ -d '{ "employer_id": "emp_12345", "period_start": "2026-01-01", "period_end": "2026-01-31", "employees": [...] }' ``` ### Code Examples **JavaScript/Node.js**: ```javascript const clientId = process.env.AUDIT1_CLIENT_ID; const clientSecret = process.env.AUDIT1_CLIENT_SECRET; const response = await fetch('https://api.audit1.info/api/v1/payroll/reports', { method: 'POST', headers: { 'X-Client-ID': clientId, 'X-Client-Secret': clientSecret, 'Content-Type': 'application/json' }, body: JSON.stringify({ employer_id: 'emp_12345', period_start: '2026-01-01', period_end: '2026-01-31', employees: [...] }) }); const data = await response.json(); console.log(data); ``` **Python**: ```python import os import requests client_id = os.environ['AUDIT1_CLIENT_ID'] client_secret = os.environ['AUDIT1_CLIENT_SECRET'] headers = { 'X-Client-ID': client_id, 'X-Client-Secret': client_secret, 'Content-Type': 'application/json' } payload = { 'employer_id': 'emp_12345', 'period_start': '2026-01-01', 'period_end': '2026-01-31', 'employees': [...] } response = requests.post( 'https://api.audit1.info/api/v1/payroll/reports', headers=headers, json=payload ) print(response.json()) ``` **PHP**: ```php 'emp_12345', 'period_start' => '2026-01-01', 'period_end' => '2026-01-31', 'employees' => [...] ])); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); curl_close($ch); echo $response; ?> ``` *** ## Request Signature (HMAC-SHA256) ### Optional but Recommended For enhanced security, sign your requests with HMAC-SHA256. This prevents: * **Replay attacks**: Timestamp validation (5-minute window) * **Request tampering**: Signature includes full request payload * **Man-in-the-middle attacks**: Cryptographic verification ### How It Works 1. Generate timestamp (Unix milliseconds) 2. Build signature payload: `timestamp + method + path + body` 3. Calculate HMAC-SHA256 using your Client Secret 4. Include signature in request headers ### Signature Headers ```http X-Signature: a1b2c3d4e5f6... (HMAC-SHA256 hex digest) X-Timestamp: 1704538800000 (Unix timestamp in milliseconds) ``` ### Implementation Examples **JavaScript**: ```javascript const crypto = require('crypto'); function generateSignature(clientSecret, timestamp, method, path, body) { const payload = `${timestamp}.${method}.${path}.${body}`; const hmac = crypto.createHmac('sha256', clientSecret); hmac.update(payload); return hmac.digest('hex'); } const timestamp = Date.now(); const method = 'POST'; const path = '/api/v1/payroll/reports'; const body = JSON.stringify({ employer_id: 'emp_12345', ... }); const signature = generateSignature(clientSecret, timestamp, method, path, body); // Include in request const response = await fetch('https://api.audit1.info/api/v1/payroll/reports', { method: 'POST', headers: { 'X-Client-ID': clientId, 'X-Client-Secret': clientSecret, 'X-Signature': signature, 'X-Timestamp': timestamp.toString(), 'Content-Type': 'application/json' }, body: body }); ``` **Python**: ```python import hmac import hashlib import time import json def generate_signature(client_secret, timestamp, method, path, body): payload = f"{timestamp}.{method}.{path}.{body}" signature = hmac.new( client_secret.encode('utf-8'), payload.encode('utf-8'), hashlib.sha256 ).hexdigest() return signature timestamp = int(time.time() * 1000) method = 'POST' path = '/api/v1/payroll/reports' body = json.dumps({'employer_id': 'emp_12345', ...}) signature = generate_signature(client_secret, timestamp, method, path, body) headers = { 'X-Client-ID': client_id, 'X-Client-Secret': client_secret, 'X-Signature': signature, 'X-Timestamp': str(timestamp), 'Content-Type': 'application/json' } response = requests.post( 'https://api.audit1.info/api/v1/payroll/reports', headers=headers, data=body ) ``` **PHP**: ```php 'emp_12345', ...]); $signature = generateSignature($clientSecret, $timestamp, $method, $path, $body); $ch = curl_init('https://api.audit1.info/api/v1/payroll/reports'); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'X-Client-ID: ' . $clientId, 'X-Client-Secret: ' . $clientSecret, 'X-Signature: ' . $signature, 'X-Timestamp: ' . $timestamp, 'Content-Type: application/json' ]); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $body); ?> ``` *** ## Security Best Practices ### Storage ✅ **DO**: * Store secrets in environment variables * Use secrets management (AWS Secrets Manager, HashiCorp Vault, Azure Key Vault) * Encrypt secrets at rest in databases * Restrict access to production keys ❌ **DON'T**: * Hardcode secrets in source code * Commit secrets to version control (use `.gitignore`) * Share secrets via email or Slack * Store secrets in plain text files ### Key Management ✅ **DO**: * Use descriptive labels ("Production - Gusto Integration") * Create separate keys per environment * Create separate keys per integration/service * Rotate keys every 90 days * Revoke keys immediately when compromised * Monitor key usage for anomalies ❌ **DON'T**: * Reuse keys across environments * Share keys between services * Leave unused keys active * Use overly broad permissions ### Development Workflow ```bash # .env file (never commit!) AUDIT1_CLIENT_ID_SANDBOX=audit1_test_cli_... AUDIT1_CLIENT_SECRET_SANDBOX=audit1_test_sec_... AUDIT1_CLIENT_ID_PRODUCTION=audit1_live_cli_... AUDIT1_CLIENT_SECRET_PRODUCTION=audit1_live_sec_... # .env.example (commit this) AUDIT1_CLIENT_ID_SANDBOX=your_sandbox_client_id_here AUDIT1_CLIENT_SECRET_SANDBOX=your_sandbox_client_secret_here AUDIT1_CLIENT_ID_PRODUCTION=your_production_client_id_here AUDIT1_CLIENT_SECRET_PRODUCTION=your_production_client_secret_here ``` ### .gitignore Example ``` # API Keys and Secrets .env .env.local .env.*.local *.pem *.key secrets/ config/secrets.yml ``` *** ## Environment Management ### Sandbox vs Production | Feature | Sandbox | Production | |---------|---------|------------| | **Prefix** | `audit1_test_cli_*` / `audit1_test_sec_*` | `audit1_live_cli_*` / `audit1_live_sec_*` | | **Data** | Test data only | Real business data | | **Base URL** | Same: `https://api.audit1.info/api/v1` | Same: `https://api.audit1.info/api/v1` | | **Isolation** | Completely separate database | Production database | | **Purpose** | Development, testing, integration | Live business operations | ### Testing Workflow 1. **Develop** with sandbox keys 2. **Test thoroughly** in sandbox environment 3. **Request production access** when ready 4. **Create production keys** in portal 5. **Deploy** with production keys ### Environment Variable Setup ```bash # Development export AUDIT1_CLIENT_ID=$AUDIT1_CLIENT_ID_SANDBOX export AUDIT1_CLIENT_SECRET=$AUDIT1_CLIENT_SECRET_SANDBOX # Production export AUDIT1_CLIENT_ID=$AUDIT1_CLIENT_ID_PRODUCTION export AUDIT1_CLIENT_SECRET=$AUDIT1_CLIENT_SECRET_PRODUCTION ``` *** ## Audit Logging ### What's Logged Every API request is logged with: * Request ID (unique identifier) * Client ID used * Owner ID and type * Timestamp * HTTP method and path * IP address and user agent * Response status code * Response time * Signature validation result (if applicable) * Any errors ### Viewing Audit Logs Access your audit logs via: 1. **Portal Dashboard** → **Developer** → **API Usage** 2. View by date range, status code, or endpoint 3. Filter by Client ID to see specific key usage ### Log Retention * Sandbox: 30 days * Production: 1 year * Available for export (CSV, JSON) *** ## Troubleshooting ### Authentication Errors #### Error: Missing authentication headers ```json { "error": "Unauthorized", "message": "Missing authentication headers. Required: X-Client-ID, X-Client-Secret" } ``` **Solution**: Include both `X-Client-ID` and `X-Client-Secret` headers in your request. #### Error: Invalid client\_id ```json { "error": "Unauthorized", "message": "Invalid client_id. Generate API keys at your portal dashboard." } ``` **Solution**: * Verify Client ID is correct (copy from portal) * Ensure key hasn't been revoked * Check you're using the right environment key #### Error: Invalid client\_secret ```json { "error": "Unauthorized", "message": "Invalid client_secret" } ``` **Solution**: * Client Secret is incorrect (copy carefully) * If lost, you cannot retrieve it - generate new key * Check for extra spaces or newlines #### Error: Environment mismatch ```json { "error": "Unauthorized", "message": "Environment mismatch. This client_id is for sandbox" } ``` **Solution**: Client ID prefix doesn't match its environment. Use matching key pair. ### Signature Errors #### Error: Invalid request signature ```json { "error": "Unauthorized", "message": "Invalid request signature", "details": "Signature mismatch" } ``` **Solution**: * Verify signature generation logic * Ensure timestamp is current (within 5 minutes) * Check payload format: `timestamp.method.path.body` * Verify HMAC-SHA256 implementation #### Error: Request timestamp expired ```json { "error": "Unauthorized", "message": "Invalid request signature", "details": "Request timestamp expired (>5 minutes old)" } ``` **Solution**: * Generate new timestamp for each request * Check system clock is synchronized (NTP) * Don't reuse signatures *** ## Key Rotation ### When to Rotate * **Scheduled**: Every 90 days (recommended) * **Compromise**: Immediately if suspected breach * **Personnel changes**: When team members leave * **Service changes**: Major infrastructure updates ### Rotation Process 1. **Create new key** in portal (same label + " - v2") 2. **Deploy new key** to staging environment 3. **Test thoroughly** in staging 4. **Deploy to production** (zero-downtime) 5. **Monitor for errors** (24-48 hours) 6. **Revoke old key** after grace period ### Zero-Downtime Rotation ```javascript // Support both old and new keys temporarily const clientId = process.env.AUDIT1_CLIENT_ID_NEW || process.env.AUDIT1_CLIENT_ID; const clientSecret = process.env.AUDIT1_CLIENT_SECRET_NEW || process.env.AUDIT1_CLIENT_SECRET; ``` *** ## Support ### Getting Help * **Documentation**: [https://docs.audit1.com](https://docs.audit1.com) * **API Status**: [https://status.audit1.com](https://status.audit1.com) * **Support Email**: api-support@audit1.com * **Developer Slack**: [Join Community](https://slack.audit1.com) ### Reporting Security Issues If you discover a security vulnerability: 1. **DO NOT** open a public issue 2. Email: security@audit1.com 3. Include: affected endpoint, reproduction steps 4. We'll respond within 24 hours *** ## Additional Resources * [Getting Started Guide](./getting-started.md) * [Webhooks Guide](./webhooks-guide.md) * [Integration Examples](./integration-examples.md) * [API Reference](./api-reference.md) * [Troubleshooting](./troubleshooting.md)