Developer Guide
Comprehensive guide for integrating with Audit1 APIs
Developer API Documentation
Welcome to the Audit1 Developer API! This comprehensive guide will help you integrate with our platform using API keys and webhooks.
📖 Quick Reference
# Base URL
https://api.audit1.info/api/v1
# Authentication
Authorization: Bearer audit1_test_sk_YOUR_KEY_HERE
# Content Type
Content-Type: application/jsonAPI Endpoints at a Glance
| Endpoint | Method | Purpose | Auth Required |
|---|---|---|---|
/payroll/reports | POST | Submit payroll data | ✅ Yes |
/employees/sync | POST | Report employee changes | ✅ Yes |
/files/status/:id | GET | Check file processing | ✅ Yes |
Response Codes
| Code | Meaning | Action |
|---|---|---|
200 | Success | Request completed |
202 | Accepted | Processing async |
400 | Bad Request | Check request body |
401 | Unauthorized | Verify API key |
429 | Rate Limited | Retry with backoff |
500 | Server Error | Contact support |
Table of Contents
- Getting Started
- Authentication
- API Keys Management
- Webhooks
- Event Types
- Testing & Environments
- Error Handling
- Rate Limits
- Support
Getting Started
Overview
The Audit1 Developer API enables payroll providers, insurance carriers, and software integrators to:
- Submit Payroll Reports: Send employee wages, hours, and classification codes for workers' comp audits
- Sync Employee Data: Report hires, terminations, and wage changes in real-time
- Track File Processing: Monitor the status of uploaded payroll files
- Receive Webhook Events: Get notified when audits complete, policies update, etc.
Note: API keys and webhooks are managed through your Audit1 portal dashboard, not programmatically via the API.
Portal URLs by User Type:
- Employers: https://employer.audit1.info
- Payroll Companies: https://payrollcompany.audit1.info
- Carriers: https://carrier.audit1.info
Base URL
Production: https://api.audit1.info/api/v1
Quick Example
# Submit payroll report
curl -X POST https://api.audit1.info/api/v1/payroll/reports \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"employer_id": "EMP_12345",
"period_start": "2024-01-01",
"period_end": "2024-03-31",
"employees": [...],
"payroll_data": {...}
}'Prerequisites
Before you begin, ensure you have:
- An active Audit1 account
- API key from your Audit1 portal (Dashboard → API Keys section)
- Employer ID(s) for payroll reporting
- Basic understanding of REST APIs and HTTP protocols
Authentication
All API requests must be authenticated using API keys created in your Audit1 portal dashboard (navigate to API Keys section).
Bearer Token Authentication
Authorization: Bearer audit1_test_sk_1234567890abcdef1234567890abcdefEnvironment Auto-Detection
The API automatically detects your environment from the key prefix:
audit1_test_sk_* → Sandbox (test data, faster processing)
audit1_live_sk_* → Production (real data, standard processing)
Complete Request Example
curl -X POST https://api.audit1.info/api/v1/payroll/reports \
-H "Authorization: Bearer audit1_test_sk_abc123..." \
-H "Content-Type: application/json" \
-d '{"employer_id": "EMP_001", ...}'Code Examples
Node.js:
const axios = require('axios');
const client = axios.create({
baseURL: 'https://api.audit1.info/api/v1',
headers: {
'Authorization': `Bearer ${process.env.AUDIT1_API_KEY}`,
'Content-Type': 'application/json'
}
});
// Make request
const response = await client.post('/payroll/reports', payloadData);Python:
import requests
import os
headers = {
'Authorization': f'Bearer {os.getenv("AUDIT1_API_KEY")}',
'Content-Type': 'application/json'
}
response = requests.post(
'https://api.audit1.info/api/v1/payroll/reports',
headers=headers,
json=payload_data
)C# / .NET:
using System.Net.Http;
using System.Net.Http.Headers;
var client = new HttpClient();
client.BaseAddress = new Uri("https://api.audit1.info/api/v1");
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("AUDIT1_API_KEY"));
var response = await client.PostAsJsonAsync("/payroll/reports", payloadData);Authentication Errors
| Error | Cause | Solution |
|---|---|---|
Missing Authorization header | No auth header | Add Authorization: Bearer YOUR_KEY |
Invalid API key format | Wrong prefix | Use audit1_test_sk_* or audit1_live_sk_* |
API key not found | Key deleted/invalid | Create new key in Dashboard |
API key inactive | Key disabled | Re-enable in Dashboard |
Bearer Token Authentication
Authorization: Bearer YOUR_API_KEYExample Request
curl -X POST https://api.audit1.info/api/v1/payroll/reports \
-H "Authorization: Bearer audit1_live_sk_abc123..." \
-H "Content-Type: application/json" \
-d '{...}'API Key Format
API keys follow this format:
- Sandbox:
audit1_test_sk_[32-character-string] - Production:
audit1_live_sk_[32-character-string]
Get your API key: Log into your portal → Dashboard → API Keys
Available Endpoints
Payroll Reporting
Submit Payroll Report
POST /api/v1/payroll/reportsSubmit employee wages, hours, and classification codes for a reporting period.
Request Body:
{
"employer_id": "EMP_12345",
"period_start": "2024-01-01",
"period_end": "2024-03-31",
"employees": [
{
"employee_id": "EE_001",
"name": "John Smith",
"classification_code": "8810",
"hours_worked": 520,
"gross_wages": 26000
}
],
"payroll_data": {
"total_payroll": 26000,
"total_hours": 520,
"officer_payroll": 0,
"subcontractor_payments": 0
}
}Employee Sync
Sync Employee Changes
POST /api/v1/employees/syncReport employee hires, terminations, and wage updates.
Request Body:
{
"employer_id": "EMP_12345",
"employees": [
{
"employee_id": "EE_NEW_001",
"action": "hire",
"name": "Jane Doe",
"hire_date": "2024-02-01",
"classification_code": "8810",
"hourly_rate": 30.00
}
]
}File Status
Check File Processing Status
GET /api/v1/files/status/:idCheck the processing status of an uploaded payroll file.
Response:
{
"file_id": "FILE_123",
"status": "completed",
"records_total": 150,
"records_processed": 150,
"records_failed": 0
}Webhooks (Receiving Events)
Create API Key
Creates a new API key for your organization.
Endpoint: POST /api-keys
Request Body:
{
"owner_id": "your-entity-id",
"owner_type": "employer|payroll_company|software_company",
"environment": "sandbox|production"
}Response:
{
"apiKey": {
"_id": "507f1f77bcf86cd799439011",
"id": "ak_12345",
"key": "ak_prod_1234567890abcdef...",
"environment": "production",
"owner_id": "your-entity-id",
"owner_type": "employer",
"is_active": true,
"created_at": "2024-01-15T10:30:00.000Z",
"updated_at": "2024-01-15T10:30:00.000Z"
},
"secret": "sk_prod_abcdef1234567890..."
}
## Platform Architecture Snapshot
The Developer API mirrors how the Audit1 workers' compensation stack is assembled:
1. **Experience Layer** – employer and carrier portals powered by Audit1 UI components.
2. **Service Layer** – stateless Node services (Developer API, Notification Handler, Ledger) deployed to Cloud Run.
3. **Data Layer** – MongoDB Atlas clusters split by environment with regional failover.
4. **Integration Fabric** – webhook dispatcher, SFTP collectors, and third-party connectors (Paylocity, UKG, Hartford, etc.).
Client -> Developer API -> Orchestrator -> Ledger / Policy Store -> MongoDB Atlas ↘ Notification/Webhook Engine -> Partner Systems
### Domain Boundaries (Workers' Comp Lens)
- **Policy & Audit**: Policies, endorsements, physical/virtual audits; authoritative source lives in Audit1 and respects bureau rules.
- **Exposure**: Payroll, employee census, WC class-code metadata, overtime modifiers; mastered by payroll reporters, cached by Audit1.
- **Financial**: Premium deltas, deposit adjustments, PayGo remittance schedules; reconciled in Ledger Service and surfaced through this API.
- **Compliance**: Audit packets, affidavit status, document retention aligned with DOI / bureau checklists.
### Data Residency & Compliance Notes
- Data residency currently in `us-central1` with warm standby in `us-east4` (keeps latency low for national carriers).
- PII is encrypted at rest with customer-specific keys; transport requires TLS 1.2+.
- SOC 2 Type II controls govern key management, access logs, and webhook replay detection.
- Default retention: 7 years for audit artifacts (matches most WC state requirements), configurable via support ticket.
### Business Metrics to Track
- **Adoption**: number of active payroll reporters / carrier divisions calling the API vs. manual portals.
- **Automation**: % of audits auto-closed because wage statements or exposure files arrived via webhook.
- **Data Freshness**: median lag between payroll event and Audit1 ingestion (goal < 15 minutes for PayGo).
- **Accuracy**: premium leakage prevented due to near-real-time exposure sync.
- **Error Budget**: 99.9% monthly SLA; use `X-Audit1-Request-Id` for support escalations.
⚠️ Important: The secret is only returned once during creation. Store it securely!
List API Keys
Retrieves all active API keys for your organization.
Endpoint: GET /api-keys?owner_id={your-entity-id}&owner_type={type}
Query Parameters:
owner_id(required): Your entity IDowner_type(required):user,employer,payroll_company, orsoftware_company
Response:
{
"apiKeys": [
{
"_id": "507f1f77bcf86cd799439011",
"id": "ak_12345",
"key": "ak_prod_1234567890abcdef...",
"environment": "production",
"owner_id": "your-entity-id",
"owner_type": "employer",
"is_active": true,
"created_at": "2024-01-15T10:30:00.000Z",
"updated_at": "2024-01-15T10:30:00.000Z"
}
]
}Delete API Key
Soft-deletes an API key (sets is_active to false).
Endpoint: DELETE /api-keys/{api_key_id}?owner_id={your-entity-id}
Response:
{
"message": "api_key_deleted"
}Webhooks
Webhooks allow you to receive real-time notifications when events occur in the Audit1 system.
How Webhooks Work
- Event Occurs: Something happens in Audit1 (e.g., policy created, payment processed)
- Event Sent: We send an HTTP POST request to your webhook URL
- Response Required: Your endpoint must respond with a 2xx status code
- Retry Logic: Failed deliveries are retried with exponential backoff
Create Webhook
Endpoint: POST /webhooks
Request Body:
{
"owner_id": "your-entity-id",
"owner_type": "employer|payroll_company|software_company",
"url": "https://your-domain.com/webhooks/audit1",
"events": ["policy.created", "payment.processed", "employee.updated"]
}Response:
{
"webhook": {
"_id": "507f1f77bcf86cd799439012",
"id": "wh_12345",
"url": "https://your-domain.com/webhooks/audit1",
"events": ["policy.created", "payment.processed"],
"owner_id": "your-entity-id",
"owner_type": "employer",
"is_active": true,
"created_at": "2024-01-15T10:35:00.000Z",
"updated_at": "2024-01-15T10:35:00.000Z"
},
"secret": "whsec_1234567890abcdef..."
}Webhook Security
Signature Verification
Every webhook includes a signature in the Audit1-Signature header:
Audit1-Signature: t=1642291200,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bdVerification Steps:
-
Extract timestamp and signature:
const signature = req.headers['audit1-signature']; const elements = signature.split(','); const timestamp = elements[0].split('=')[1]; const v1 = elements[1].split('=')[1]; -
Create expected signature:
const crypto = require('crypto'); const payload = req.body; const expectedSignature = crypto .createHmac('sha256', webhookSecret) .update(timestamp + '.' + JSON.stringify(payload)) .digest('hex'); -
Compare signatures:
const isValid = crypto.timingSafeEqual( Buffer.from(v1, 'hex'), Buffer.from(expectedSignature, 'hex') );
Example Webhook Handler (Node.js/Express)
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.raw({ type: 'application/json' }));
app.post('/webhooks/audit1', (req, res) => {
const signature = req.headers['audit1-signature'];
const payload = req.body;
// Verify signature
if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(400).send('Invalid signature');
}
const event = JSON.parse(payload);
// Handle the event
switch (event.type) {
case 'policy.created':
handlePolicyCreated(event.data);
break;
case 'payment.processed':
handlePaymentProcessed(event.data);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
res.status(200).send('OK');
});
function verifyWebhookSignature(payload, signature, secret) {
const elements = signature.split(',');
const timestamp = elements[0].split('=')[1];
const v1 = elements[1].split('=')[1];
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(timestamp + '.' + payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(v1, 'hex'),
Buffer.from(expectedSignature, 'hex')
);
}List Webhooks
Endpoint: GET /webhooks?owner_id={your-entity-id}&owner_type={type}
Delete Webhook
Endpoint: DELETE /webhooks/{webhook_id}?owner_id={your-entity-id}
Event Types
Policy Events
policy.created- New insurance policy createdpolicy.updated- Policy details modifiedpolicy.cancelled- Policy cancelledpolicy.renewed- Policy renewed
Employee Events
employee.created- New employee addedemployee.updated- Employee information changedemployee.terminated- Employee terminated
Payment Events
payment.processed- Payment successfully processedpayment.failed- Payment processing failedpayment.refunded- Payment refunded
Audit Events
audit.started- New audit process initiatedaudit.completed- Audit process completedaudit.failed- Audit process failed
Example Event Payload
{
"id": "evt_1234567890",
"type": "policy.created",
"created_at": "2024-01-15T10:40:00.000Z",
"data": {
"policy": {
"id": "pol_12345",
"employer_id": "emp_67890",
"policy_number": "POL-2024-001",
"effective_date": "2024-02-01",
"premium": 1500.00,
"status": "active"
}
},
"metadata": {
"source": "audit1_system",
"environment": "production"
}
}Testing & Environments
Sandbox vs Production
Audit1 uses key-based environment switching - both sandbox and production use the same base URL:
Base URL: https://api.audit1.info/api/v1
The environment is determined by your API key prefix:
| Environment | Key Prefix | Data |
|---|---|---|
| Sandbox | audit1_test_sk_... | Test data, safe for development |
| Production | audit1_live_sk_... | Real data, live transactions |
Example:
# Sandbox request (test data)
curl -H "Authorization: Bearer audit1_test_sk_abc123..." \
https://api.audit1.info/api/v1/payroll/reports
# Production request (real data)
curl -H "Authorization: Bearer audit1_live_sk_xyz789..." \
https://api.audit1.info/api/v1/payroll/reportsWebhook Testing
For testing webhooks locally, use tools like:
- ngrok - Expose local server to internet
- webhook.site - Inspect webhook payloads
ngrok for Local Development:
# Install ngrok
npm install -g ngrok
# Expose local server
ngrok http 3000
# Use the generated URL for webhooks
# https://abc123.ngrok.io/webhooks/audit1Error Handling
Error Response Format
All errors follow a consistent format:
{
"error": "Error type",
"message": "Human-readable description",
"details": {
"field": "Additional context"
}
}Common Errors
400 Bad Request
Missing Required Fields:
{
"error": "Validation Error",
"message": "Missing required fields: employees",
"details": {
"required": ["employer_id", "period_start", "period_end", "employees", "payroll_data"]
}
}Solution: Ensure all required fields are included in your request.
401 Unauthorized
Invalid API Key:
{
"error": "Authentication Failed",
"message": "Invalid API key format. Expected audit1_test_sk_* or audit1_live_sk_*"
}Solution: Verify your API key starts with the correct prefix.
429 Too Many Requests
{
"error": "Rate Limit Exceeded",
"message": "Too many requests. Please retry after 60 seconds.",
"details": {
"retry_after": 60,
"limit": 100,
"window": "1 minute"
}
}Solution: Implement exponential backoff retry logic.
Retry Logic Best Practices
Exponential Backoff:
async function retryRequest(fn, maxRetries = 3) {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
lastError = error;
// Don't retry client errors (4xx except 429)
if (error.status >= 400 && error.status < 500 && error.status !== 429) {
throw error;
}
// Calculate backoff: 1s, 2s, 4s, 8s...
const backoffMs = Math.min(Math.pow(2, i) * 1000, 30000);
console.log(`Retry ${i + 1}/${maxRetries} after ${backoffMs}ms`);
await new Promise(resolve => setTimeout(resolve, backoffMs));
}
}
throw lastError;
}
// Usage
const result = await retryRequest(() =>
submitPayrollReport(data)
);Which Errors to Retry:
| Status Code | Retry? | Reason |
|---|---|---|
429 | ✅ Yes | Rate limit - temporary |
500 | ✅ Yes | Server error - may be temporary |
502/503 | ✅ Yes | Gateway/Service unavailable |
400 | ❌ No | Bad request - fix payload first |
401 | ❌ No | Auth error - fix credentials first |
404 | ❌ No | Not found - check endpoint |
Validation Errors
Employee Validation:
{
"error": "Validation Error",
"message": "Invalid employee data",
"details": {
"employees[0].classification_code": "Required field missing",
"employees[1].gross_wages": "Must be a positive number"
}
}Date Format Errors:
{
"error": "Validation Error",
"message": "Invalid date format for period_start",
"details": {
"expected": "YYYY-MM-DD",
"received": "01/15/2024"
}
}Logging Errors
Recommended Log Structure:
function logApiError(error, context) {
console.error({
timestamp: new Date().toISOString(),
error_type: error.name,
status_code: error.status,
message: error.message,
endpoint: context.endpoint,
method: context.method,
request_id: error.response?.headers?.['x-request-id'],
environment: process.env.NODE_ENV,
stack: error.stack
});
}Error Handling
HTTP Status Codes
200- Success201- Created successfully400- Bad request (validation error)401- Unauthorized (invalid API key)403- Forbidden (insufficient permissions)404- Resource not found409- Conflict (duplicate resource)429- Rate limit exceeded500- Internal server error
Error Response Format
{
"error": "owner_id_required",
"message": "The owner_id field is required",
"code": "VALIDATION_ERROR",
"timestamp": "2024-01-15T10:45:00.000Z"
}Common Error Codes
owner_id_required- Missing owner IDowner_type_required- Missing owner typeenvironment_required- Missing environmentapi_key_not_found- API key doesn't existwebhook_creation_failed- Webhook creation errorinvalid_signature- Webhook signature verification failed
Rate Limits
API requests are rate-limited to ensure fair usage:
Limits by Plan
| Plan Type | Requests/Minute | Requests/Hour |
|---|---|---|
| Sandbox | 100 | 1,000 |
| Production | 500 | 10,000 |
| Enterprise | 2,000 | 50,000 |
Rate Limit Headers
X-RateLimit-Limit: 500
X-RateLimit-Remaining: 499
X-RateLimit-Reset: 1642291260Handling Rate Limits
const response = await fetch('/api/v1/api-keys', {
headers: { 'Authorization': `Bearer ${apiKey}` }
});
if (response.status === 429) {
const resetTime = response.headers.get('X-RateLimit-Reset');
const waitTime = (resetTime * 1000) - Date.now();
console.log(`Rate limited. Waiting ${waitTime}ms`);
await new Promise(resolve => setTimeout(resolve, waitTime));
// Retry request
}Support
Getting Help
- Documentation: https://docs.audit1.com/developer-api
- Status Page: https://status.audit1.com
- Support Email: [email protected]
- Response Time: 24 hours (business days)
Community
- GitHub: https://github.com/audit1/developer-examples
- Stack Overflow: Tag your questions with
audit1-api - Discord: Join our developer community
Contact Information
Technical Support:
- Email: [email protected]
- Phone: 1-800-AUDIT-1 (1-800-283-481)
- Hours: Monday-Friday, 9 AM - 6 PM EST
Partnership Inquiries:
- Email: [email protected]
- Dedicated account managers for enterprise clients
Appendix
SDK & Libraries
Official SDKs:
- Node.js:
npm install @audit1/node-sdk - Python:
pip install audit1-python - PHP:
composer require audit1/php-sdk - .NET: Available on NuGet
Community Libraries:
- Ruby gem:
audit1-ruby - Go module:
go get github.com/audit1/go-sdk
Postman Collection
Import our Postman collection for easy API testing: https://www.getpostman.com/collections/audit1-developer-api
Changelog
v1.2.0 - January 2024
- Added webhook signature verification
- Improved error messages
- Added rate limiting headers
v1.1.0 - December 2023
- Initial public release
- API Keys management
- Webhooks support
Last updated: January 3, 2026
Updated 1 day ago
