Overview
The Certificate Review API allows you to upload COI PDF documents from contractors who bring their own insurance. Our platform automatically:
- Parses certificate documents using AI to extract coverage details, limits, expiration dates, and more
- Validates certificates against your organization's insurance requirements
- Evaluates each requirement rule and provides detailed pass/fail results
- Notifies you via webhooks when review completes
This enables you to automate compliance checking for contractors who provide their own insurance coverage, ensuring they meet your organization's standards before they begin work.
Prerequisites
Before using the Certificate Review API, you must:
1. Define Insurance Requirements
Insurance requirements define the coverage rules that certificates must satisfy. You can create and manage these in the Dashboard or via the GraphQL API.
Required Setup:
- Create an Insurance Requirement - Define the set of coverage rules (e.g., minimum General Liability limits, required coverage types)
- Set as Default - Mark one insurance requirement as the default for your organization
- Configure Coverage Rules - Define specific rules such as:
- Minimum coverage limits (e.g., $1M General Aggregate, $1M Per Occurrence)
- Required coverage types (General Liability, Workers' Compensation, etc.)
- Policy expiration requirements
- Additional insured requirements
- Certificate holder name validation
Default vs. Custom Requirements:
- Default Requirement: Used automatically when no specific requirement is specified
- Custom Requirements: Can be associated with specific entities, job categories, or contractors
- Future Enhancement: The API will support specifying a custom
insurance_requirement_idwhen uploading certificates
Note: Currently, certificates are evaluated against your organization's default insurance requirement. Support for specifying custom requirements per certificate upload is planned for a future API release.
2. Create Contractors
Each certificate must be associated with a contractor. Create contractors using the Contractors API before uploading certificates.
curl https://api.1099policy.com/api/v1/contractors \
-u t9k_test_your_secret_key: \
-d email="contractor@example.com" \
-d first_name="John" \
-d last_name="Doe"
Getting Started
Step 1: Upload a Certificate
Upload a COI PDF document for a contractor:
curl https://api.1099policy.com/api/v1/files/certificates \
-u t9k_test_your_secret_key: \
-F "contractor=cn_abc123" \
-F "certificate=@/path/to/certificate.pdf"
Response:
{
"id": "ci_YnsHeB9PTo",
"contractor": "cn_abc123",
"status": "pending",
"created": 1760509809,
"updated": null,
"filename": "certificate.pdf",
"pdf_url": null,
"review_results": null
}
The certificate is immediately accepted and queued for asynchronous processing. The initial response shows:
status: "pending"- Processing has not yet startedreview_results: null- Results will be available once processing completes
Step 2: Monitor Processing Status
Certificate processing happens asynchronously. Monitor the status by polling the certificate endpoint:
curl https://api.1099policy.com/api/v1/files/certificates/ci_YnsHeB9PTo \
-u t9k_test_your_secret_key:
Status Lifecycle:
pending- Certificate uploaded, waiting to be processedprocessing- Document is being parsed and evaluatedapproved- Certificate passed all requirementsflagged- Certificate failed some requirements (may still be acceptable)denied- Certificate was deniederror- An error occurred during processing
Step 3: Retrieve Review Results
Once processing completes, retrieve detailed results using the expand parameter:
Abbreviated Results (Summary):
curl "https://api.1099policy.com/api/v1/files/certificates/ci_YnsHeB9PTo?expand[]=review_results" \
-u t9k_test_your_secret_key:
Response:
{
"id": "ci_YnsHeB9PTo",
"contractor": "cn_abc123",
"status": "flagged",
"review_results": {
"id": "ca_xyz789",
"status": "flagged",
"summary": {
"total_rules": 8,
"passed": 6,
"failed": 2
},
"created": 1760509813
}
}
Full Results (Complete Details):
curl "https://api.1099policy.com/api/v1/files/certificates/ci_YnsHeB9PTo?expand[]=review_results.full" \
-u t9k_test_your_secret_key:
Response:
{
"id": "ci_YnsHeB9PTo",
"contractor": "cn_abc123",
"status": "flagged",
"review_results": {
"id": "ca_xyz789",
"status": "flagged",
"parsed_certificate_json": {
"date": "01/15/2024",
"certificate_holder": {
"name": "Your Organization Name"
},
"coverages": {
"commercial_general_liability": {
"limits": {
"general_aggregate_dollars": 2000000,
"each_occurrence_dollars": 1000000
},
"policy_expiration_date": "01/15/2025"
}
}
},
"audit_results": [
{
"id": "car_abc123",
"rule_path": "coverages.commercial_general_liability.limits.general_aggregate_dollars",
"rule_name": "Minimum General Aggregate",
"result": "pass",
"message": "Limit meets requirement",
"manually_approved": false,
"created": 1760509813
},
{
"id": "car_def456",
"rule_path": "coverages.commercial_general_liability.limits.each_occurrence_dollars",
"rule_name": "Minimum Per Occurrence",
"result": "fail",
"message": "Limit $500,000 is below required $1,000,000",
"manually_approved": false,
"created": 1760509813
}
],
"created": 1760509813,
"updated": 1760509813
}
}
How It Works
Processing Flow
- Upload - You POST a certificate PDF to the API
- Queue - Certificate is queued for asynchronous processing
- Parse - AI service extracts structured data from the PDF
- Validate - Certificate is evaluated against your insurance requirements
- Notify - Webhook event is sent when processing completes
Evaluation Process
Certificates are evaluated against your organization's default insurance requirement (or a custom requirement when that feature is available). The evaluation:
- Extracts Data - Parses coverage limits, expiration dates, certificate holder, insured name, etc.
- Applies Rules - Evaluates each coverage rule defined in your insurance requirement
- Generates Results - Creates audit results for each rule (pass/fail with messages)
- Determines Status - Sets final status based on rule outcomes:
- All rules pass →
approved - Some rules fail →
flagged - Critical rules fail →
denied - Processing error →
error
- All rules pass →
Insurance Requirements
Insurance requirements are collections of coverage rules that define what certificates must satisfy. Each rule specifies:
- Path - The data path to check (e.g.,
coverages.commercial_general_liability.limits.general_aggregate_dollars) - Condition - The comparison operator (e.g.,
greater_than_or_equal_to) - Value - The required value (e.g.,
1000000for $1M)
Example Rules:
- General Liability General Aggregate ≥ $2M
- General Liability Per Occurrence ≥ $1M
- Workers' Compensation required
- Policy expiration date ≥ 30 days from today
- Certificate holder name matches organization name
Workflows
Contractor Onboarding Flow
Integrate certificate review into your contractor onboarding process:
Implementation Example:
# 1. Upload certificate during onboarding
response = requests.post(
'https://api.1099policy.com/api/v1/files/certificates',
auth=('t9k_test_your_secret_key', ''),
files={'certificate': open('coi.pdf', 'rb')},
data={'contractor': contractor_id}
)
certificate = response.json()
# 2. Poll for completion (or use webhooks)
while True:
response = requests.get(
f'https://api.1099policy.com/api/v1/files/certificates/{certificate["id"]}',
auth=('t9k_test_your_secret_key', '')
)
cert = response.json()
if cert['status'] not in ['pending', 'processing']:
break
time.sleep(5) # Poll every 5 seconds
# 3. Check results
if cert['status'] == 'approved':
# Proceed with onboarding
approve_contractor(contractor_id)
elif cert['status'] == 'flagged':
# Get detailed results
response = requests.get(
f'https://api.1099policy.com/api/v1/files/certificates/{certificate["id"]}?expand[]=review_results.full',
auth=('t9k_test_your_secret_key', '')
)
results = response.json()['review_results']
# Review failed rules and decide
handle_flagged_certificate(results)
Webhook-Based Flow (Recommended)
For production, use webhooks to avoid polling:
# Webhook endpoint handler
@app.route('/webhooks/certificates', methods=['POST'])
def handle_certificate_webhook():
event = request.json
if event['type'] == 'certificate.approved':
certificate_id = event['data']['object']['id']
# Automatically approve contractor
approve_contractor_for_certificate(certificate_id)
elif event['type'] == 'certificate.flagged':
certificate_id = event['data']['object']['id']
# Fetch detailed results
fetch_and_review_certificate(certificate_id)
return jsonify({'received': True}), 200
Best Practices
1. Use Webhooks Instead of Polling
Webhooks provide real-time notifications and reduce API calls:
# Register webhook endpoint
curl https://api.1099policy.com/api/v1/webhook_endpoints \
-u t9k_test_your_secret_key: \
-d url="https://your-app.com/webhooks/certificates" \
-d events[]="certificate.approved" \
-d events[]="certificate.flagged" \
-d events[]="certificate.denied"
2. Handle All Status Types
Don't assume certificates will always be approved:
def handle_certificate_status(certificate):
status = certificate['status']
if status == 'approved':
# All requirements met
approve_contractor()
elif status == 'flagged':
# Some requirements failed, review manually
queue_for_manual_review(certificate)
elif status == 'denied':
# Critical requirements failed
reject_contractor(certificate)
elif status == 'error':
# Processing failed, may need to re-upload
notify_admin_for_review(certificate)
elif status in ['pending', 'processing']:
# Still processing
return # Wait for webhook
3. Store Certificate IDs
Store the certificate id in your database to enable:
- Quick status checks
- Linking certificates to contractors
- Audit trails
- Re-fetching results without re-uploading
4. Request Results Only When Needed
Use the expand parameter judiciously:
- Default response - Lightweight, includes status only
expand[]=review_results- Summary counts (good for dashboards)expand[]=review_results.full- Complete details (for detailed review)
5. Handle Expired Certificates
Certificates expire. Implement renewal workflows:
def check_certificate_expiration(certificate_id):
response = requests.get(
f'https://api.1099policy.com/api/v1/files/certificates/{certificate_id}?expand[]=review_results.full',
auth=('t9k_test_your_secret_key', '')
)
cert = response.json()
if cert.get('review_results'):
parsed = cert['review_results'].get('parsed_certificate_json', {})
expiration = parsed.get('coverages', {}).get('commercial_general_liability', {}).get('policy_expiration_date')
if expiration and is_expiring_soon(expiration):
notify_contractor_to_renew(certificate_id)
Edge Cases
1. Processing Delays
Processing typically completes within 30-60 seconds, but can take longer during high load. Implement:
- Timeout handling - Don't wait indefinitely
- Retry logic - Re-check status if webhook doesn't arrive
- Fallback - Manual review option for delayed certificates
2. Parsing Failures
If AI parsing fails, the certificate status will be error:
if certificate['status'] == 'error':
# Option 1: Re-upload the certificate
retry_upload(certificate_id)
# Option 2: Request manual review
request_manual_review(certificate_id)
3. Missing Review Results
If review_results is null:
- Certificate is still processing (
status: "pending"or"processing") - Processing failed (
status: "error") - Certificate was just uploaded
Always check status before accessing review_results.
4. Multiple Certificates Per Contractor
Contractors can upload multiple certificates. Each certificate is evaluated independently:
# List all certificates for a contractor
response = requests.get(
'https://api.1099policy.com/api/v1/files/certificates',
auth=('t9k_test_your_secret_key', ''),
params={'contractor': contractor_id}
)
certificates = response.json()
# Find the most recent approved certificate
approved = [c for c in certificates if c['status'] == 'approved']
latest = max(approved, key=lambda x: x['created']) if approved else None
5. Voided Audit Results
Some audit results may be voided (e.g., if rules are updated). Voided results are automatically excluded from review_results, but you can check the voided field in full results if needed.
Dashboard vs. API
Dashboard Functionality
The Dashboard provides:
- Visual certificate review - View certificates and audit results in a UI
- Manual approval/denial - Override automated decisions
- Bulk operations - Upload multiple certificates at once
- Certificate management - View, search, and manage all certificates
- Insurance requirement configuration - Create and edit requirements visually
API Functionality
The API provides:
- Programmatic upload - Integrate into your workflows
- Automated processing - No manual intervention required
- Webhook notifications - Real-time status updates
- Bulk operations - Process certificates at scale
- Custom integrations - Build your own review workflows
When to Use Each
Use the Dashboard when:
- Setting up insurance requirements for the first time
- Manually reviewing flagged certificates
- One-off certificate uploads
- Exploring certificate data
Use the API when:
- Integrating into contractor onboarding flows
- Processing certificates at scale
- Building automated compliance checks
- Creating custom review workflows
Webhooks
Available Events
Register webhooks to receive notifications when certificate processing completes:
certificate.approved- Certificate passed all requirementscertificate.flagged- Certificate failed some requirementscertificate.denied- Certificate was denied
Event Payload
{
"id": "evt_abc123",
"type": "certificate.approved",
"created": 1760509813,
"data": {
"object": {
"id": "ci_YnsHeB9PTo",
"contractor": "cn_abc123",
"status": "approved",
"created": 1760509809
}
}
}
Webhook Security
Always verify webhook signatures to ensure requests are from 1099Policy:
import hmac
import hashlib
def verify_webhook_signature(payload, signature, secret):
expected_signature = hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected_signature, signature)
Status Reference
| Status | Description | Next Steps |
|---|---|---|
pending | Certificate uploaded, queued for processing | Wait for processing to start |
processing | Certificate is being parsed and evaluated | Wait for completion |
approved | All requirements passed | Proceed with contractor approval |
flagged | Some requirements failed | Review failed rules, decide manually |
denied | Critical requirements failed | Reject or request new certificate |
error | Processing error occurred | Re-upload certificate or contact support |
API Reference
Upload Certificate
POST /api/v1/files/certificates
Parameters:
contractor(required) - Contractor public IDcertificate(required) - PDF file
Response: Certificate object with status: "pending"
Get Certificate
GET /api/v1/files/certificates/{certificate_id}
Query Parameters:
expand[]=review_results- Include abbreviated resultsexpand[]=review_results.full- Include full results with parsed data
Response: Certificate object with current status
List Certificates
GET /api/v1/files/certificates
Query Parameters:
limit- Number of results (1-100, default: 10)starting_after- Cursor for paginationending_before- Cursor for paginationexpand[]=review_results- Include results in list
Delete Certificate
DELETE /api/v1/files/certificates/{certificate_id}
Note: Deletes are soft deletes. Certificates are marked as deleted but retained for audit purposes.
Troubleshooting
Certificate Stuck in "pending" Status
If a certificate remains in pending status for more than 5 minutes:
- Check system status
- Verify the PDF is valid and not corrupted
- Re-upload the certificate
- Contact support if issue persists
Review Results Not Available
If review_results is null:
- Certificate may still be processing (check
status) - Processing may have failed (
status: "error") - Use
expand[]=review_resultsparameter when fetching
Incorrect Evaluation Results
If evaluation results seem incorrect:
- Verify your insurance requirements are configured correctly
- Check the
parsed_certificate_jsonto see what data was extracted - Review individual
audit_resultsto understand rule evaluations - Contact support with certificate ID for investigation
Support
For additional help:
- API Documentation: https://docs.1099policy.com
- Dashboard: https://dashboard.1099policy.com
- Support: support@1099policy.com
