Search docs...

Search docs...

Search docs...

Introduction

Automated certificate review

Automated certificate review

Automated certificate review

Automated certificate review

Automatically evaluate contractor-provided Certificates of Insurance (COI) against your organization's insurance requirements using AI-powered document parsing and rule-based validation.

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:

  1. Create an Insurance Requirement - Define the set of coverage rules (e.g., minimum General Liability limits, required coverage types)
  2. Set as Default - Mark one insurance requirement as the default for your organization
  3. 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_id when 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.

bash
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:

bash
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:

json
{
  "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 started
  • review_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:

bash
curl https://api.1099policy.com/api/v1/files/certificates/ci_YnsHeB9PTo \
  -u t9k_test_your_secret_key:

Status Lifecycle:

  1. pending - Certificate uploaded, waiting to be processed
  2. processing - Document is being parsed and evaluated
  3. approved - Certificate passed all requirements
  4. flagged - Certificate failed some requirements (may still be acceptable)
  5. denied - Certificate was denied
  6. error - An error occurred during processing

Step 3: Retrieve Review Results

Once processing completes, retrieve detailed results using the expand parameter:

Abbreviated Results (Summary):

bash
curl "https://api.1099policy.com/api/v1/files/certificates/ci_YnsHeB9PTo?expand[]=review_results" \
  -u t9k_test_your_secret_key:

Response:

json
{
  "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):

bash
curl "https://api.1099policy.com/api/v1/files/certificates/ci_YnsHeB9PTo?expand[]=review_results.full" \
  -u t9k_test_your_secret_key:

Response:

json
{
  "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

  1. Upload - You POST a certificate PDF to the API
  2. Queue - Certificate is queued for asynchronous processing
  3. Parse - AI service extracts structured data from the PDF
  4. Validate - Certificate is evaluated against your insurance requirements
  5. 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:

  1. Extracts Data - Parses coverage limits, expiration dates, certificate holder, insured name, etc.
  2. Applies Rules - Evaluates each coverage rule defined in your insurance requirement
  3. Generates Results - Creates audit results for each rule (pass/fail with messages)
  4. Determines Status - Sets final status based on rule outcomes:
    • All rules pass → approved
    • Some rules fail → flagged
    • Critical rules fail → denied
    • Processing error → error

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., 1000000 for $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:

Automated certificate review sequence diagram
Figure 1. Automated certificate review sequence diagram

Implementation Example:

python
# 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)

For production, use webhooks to avoid polling:

python
# 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:

bash
# 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:

python
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:

python
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:

python
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:

python
# 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 requirements
  • certificate.flagged - Certificate failed some requirements
  • certificate.denied - Certificate was denied

Event Payload

json
{
  "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:

python
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

StatusDescriptionNext Steps
pendingCertificate uploaded, queued for processingWait for processing to start
processingCertificate is being parsed and evaluatedWait for completion
approvedAll requirements passedProceed with contractor approval
flaggedSome requirements failedReview failed rules, decide manually
deniedCritical requirements failedReject or request new certificate
errorProcessing error occurredRe-upload certificate or contact support

API Reference

Upload Certificate

http
POST /api/v1/files/certificates

Parameters:

  • contractor (required) - Contractor public ID
  • certificate (required) - PDF file

Response: Certificate object with status: "pending"

Get Certificate

http
GET /api/v1/files/certificates/{certificate_id}

Query Parameters:

  • expand[]=review_results - Include abbreviated results
  • expand[]=review_results.full - Include full results with parsed data

Response: Certificate object with current status

List Certificates

http
GET /api/v1/files/certificates

Query Parameters:

  • limit - Number of results (1-100, default: 10)
  • starting_after - Cursor for pagination
  • ending_before - Cursor for pagination
  • expand[]=review_results - Include results in list

Delete Certificate

http
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:

  1. Check system status
  2. Verify the PDF is valid and not corrupted
  3. Re-upload the certificate
  4. 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_results parameter when fetching

Incorrect Evaluation Results

If evaluation results seem incorrect:

  1. Verify your insurance requirements are configured correctly
  2. Check the parsed_certificate_json to see what data was extracted
  3. Review individual audit_results to understand rule evaluations
  4. Contact support with certificate ID for investigation

Support

For additional help:

Was this page helpful?

Yes

No

Was this page helpful?

Yes

No

Was this page helpful?

Yes

No

Was this page helpful?

Yes

No

Was this page helpful?

Yes

No

Was this page helpful?

Yes

No

Manually uploading contractors

General Opt-In Application

© Copyright 2024. All rights reserved.

© Copyright 2024. All rights reserved.

© Copyright 2024. All rights reserved.

© Copyright 2024. All rights reserved.

© Copyright 2024. All rights reserved.

© Copyright 2024. All rights reserved.