Error status
When a certificate ends up in error status, processing failed before evaluation could complete. There are three common causes:
| Cause | Description |
|---|---|
| AI parsing failure | The AI couldn't extract any meaningful data from the PDF (blank, corrupted, or unreadable) |
| Processing exception | An unexpected error occurred during processing |
| Retries exhausted | Transient errors (rate limits, timeouts) persisted after multiple retry attempts |
What to do
def handle_certificate_status(certificate):
if certificate['status'] == 'error':
# Log for investigation
log_error(f"Certificate {certificate['id']} failed processing")
# Notify contractor to re-upload
request_new_certificate(certificate['contractor'])
There is no automatic retry mechanism you can trigger. The recommended approach is to request a new certificate from the contractor.
No webhook is sent for error status. If you rely solely on webhooks, implement a fallback check for certificates that remain in pending or processing status for an extended period.
Processing timeouts
Certificate processing typically completes within 1-2 minutes. However, under certain conditions it can take longer:
| Timeout | Duration |
|---|---|
| AI processing | 5 minutes per attempt |
| Overall job | 10 minutes per attempt |
| Maximum with retries | ~2 hours (worst case) |
Transient failures (rate limits, server errors, timeouts) are retried up to 3 times with exponential backoff (30s, 60s, 120s delays).
Recommended handling
def check_stuck_certificates():
"""Run periodically to catch certificates that may be stuck"""
two_hours_ago = time.time() - (2 * 60 * 60)
# Find certificates still processing after 2 hours
stuck = Certificate.query.filter(
Certificate.status.in_(['pending', 'processing']),
Certificate.created < two_hours_ago
).all()
for cert in stuck:
# Alert your team or request re-upload
handle_stuck_certificate(cert)
File issues
Rejected uploads
| Issue | Error | Solution |
|---|---|---|
| Wrong format | File must be PDF | Only PDF files are accepted. Convert images, Word docs, or scanned TIFFs to PDF first. |
| Too large | File exceeds 15 MB limit | Compress the PDF or request a smaller file from the contractor. |
| Invalid PDF | File is not a valid PDF | The file may be corrupted or have an incorrect extension. Request a new copy. |
Processing failures due to file quality
| Issue | Result | Solution |
|---|---|---|
| Blank or corrupted PDF | error status | Request a valid PDF from the contractor |
| Poor scan quality | Rules fail due to unparseable data | Request a clearer scan or a digitally-generated certificate |
| Important data on page 3+ | Rules fail (data not extracted) | Request a certificate with all coverage details on pages 1-2 |
Only the first 2 pages of each PDF are analyzed. Most standard ACORD forms include all relevant information on page 1, but custom certificates may vary.
Date parsing issues
The AI recognizes only three date formats:
| Format | Example |
|---|---|
MM/DD/YYYY | 01/15/2025 |
MM/DD/YY | 01/15/25 |
YYYY-MM-DD | 2025-01-15 |
Certificates using other formats (e.g., 15 Jan 2025, January 15, 2025, DD/MM/YYYY) will have dates parsed as null, causing expiration-related rules to fail.
Symptoms
- Policy expiration rules fail even though the certificate isn't expired
parsed_certificate_jsonshowsnullfor date fields
Solution
Request a certificate with dates in a supported format, or have your compliance team manually review and approve the expiration rules.
Missing or unparseable fields
When the AI cannot extract a field that a rule depends on, the rule fails. This is by design—certificates are flagged for human review rather than auto-approved when data is uncertain.
| Scenario | Behavior |
|---|---|
| Amount field missing or unparseable | Treated as $0, rule fails |
| Required field is null or empty | is_not_none check fails |
| Date field missing or unparseable | Treated as null, rule fails |
| Boolean field missing | Treated as false, rule fails |
Debugging
Fetch the full review results to see what was extracted:
curl "https://api.1099policy.com/api/v1/files/certificates/{id}?expand[]=review_results.full" \
-u YOUR_SECRET_KEY:
Compare parsed_certificate_json with the original PDF to identify parsing issues.
Multiple certificates per contractor
Each certificate upload creates an independent record. The system does not deduplicate or lock based on contractor.
| Scenario | Behavior |
|---|---|
| New certificate uploaded while one is processing | Both process independently, both produce results |
| Multiple approved certificates exist | Both remain valid; your application decides which to use |
| Contractor uploads corrected certificate | Original certificate remains (with its status); new one evaluated separately |
Determining the current certificate
def get_current_certificate(contractor_id):
response = requests.get(
'https://api.1099policy.com/api/v1/files/certificates',
auth=('YOUR_SECRET_KEY', ''),
params={'contractor': contractor_id}
)
certificates = response.json()['data']
# Option 1: Most recent approved certificate
approved = [c for c in certificates if c['status'] == 'approved']
if approved:
return max(approved, key=lambda x: x['created'])
# Option 2: Most recent non-error certificate
valid = [c for c in certificates if c['status'] != 'error']
if valid:
return max(valid, key=lambda x: x['created'])
return None
Expired certificates
Certificates are evaluated at upload time. A certificate that was approved may later have an expired policy.
Proactive expiration tracking
from datetime import datetime, timedelta
def check_expiring_certificates():
"""Run daily to identify certificates expiring soon"""
response = requests.get(
'https://api.1099policy.com/api/v1/files/certificates',
auth=('YOUR_SECRET_KEY', ''),
params={'status': 'approved', 'limit': 100}
)
for cert in response.json()['data']:
# Fetch full details to get expiration date
details = fetch_full_results(cert['id'])
if details['review_results']:
parsed = details['review_results'].get('parsed_certificate_json', {})
# Check GL expiration (adjust path for other coverage types)
gl = parsed.get('coverages', {}).get('commercial_general_liability', {})
expiration = gl.get('policy_expiration_date')
if expiration and is_expiring_soon(expiration, days=30):
notify_contractor_to_renew(cert['contractor'])
def is_expiring_soon(date_string, days=30):
"""Check if date is within the specified number of days"""
for fmt in ['%m/%d/%Y', '%m/%d/%y', '%Y-%m-%d']:
try:
expiration = datetime.strptime(date_string, fmt)
return expiration <= datetime.now() + timedelta(days=days)
except ValueError:
continue
return False # Couldn't parse date
Split limits vs. Combined Single Limit (Auto Liability)
For automobile liability, if you require split limits (e.g., Bodily Injury per Person ≥ $100K) but the certificate only shows a Combined Single Limit (CSL), the system automatically checks whether the CSL meets the required amount.
| Your rule | Certificate shows | Result |
|---|---|---|
| BI per Person ≥ $100,000 | CSL of $500,000 | Pass (CSL meets requirement) |
| BI per Person ≥ $100,000 | CSL of $50,000 | Fail (CSL below requirement) |
| BI per Person ≥ $100,000 | BI per Person of $250,000 | Pass (exact field matches) |
This prevents false failures for certificates that use CSL formatting.
Workers' Compensation only requirements
When your insurance requirement only includes Workers' Compensation coverage (no GL, Auto, etc.), the system automatically skips validation of additional insured names in the description of operations. This is because additional insured endorsements are not applicable to WC-only policies.
No action needed—this is handled automatically.
Voided audit results
In rare cases, audit results may be voided by your compliance team (e.g., if a certificate was evaluated against an incorrect requirement). Voided results:
- Are excluded from the API response entirely
- Don't appear in
audit_results - Aren't counted in
summarytotals
You don't need to handle voided results in your integration—they're filtered out before the response reaches you.
Troubleshooting checklist
When investigating certificate issues:
| Check | How |
|---|---|
| Certificate status | GET /api/v1/files/certificates/{id} — check status field |
| What was extracted | Add ?expand[]=review_results.full — examine parsed_certificate_json |
| Which rules failed | Look at audit_results where result: "fail" |
| Original file | Download from pdf_url and compare with parsed data |
| File requirements | Confirm PDF format, under 15 MB, coverage info on pages 1-2 |
| Date formats | Check certificate uses MM/DD/YYYY, MM/DD/YY, or YYYY-MM-DD |
Getting help
If you've investigated an issue and need assistance:
- Dashboard: Review certificates and audit results visually
- API Reference: docs.1099policy.com
- Support: support@1099policy.com — include the certificate ID (
cert_...) in your request
