Coverly Developer Docs
Everything you need to integrate Coverly into your agency, platform, or carrier workflow. Build white-label quote experiences, submit applications programmatically, and receive real-time events via webhooks.
What you can build
Base URLs
# Sandbox — safe to test, no real data https://sandbox.coverly-8.polsia.app # Production https://coverly-8.polsia.app
Rate limits
Default soft limit is 100 requests/minute per API key. If you need a higher limit for a high-volume integration, contact us at partners@coverly.app.
Quote Widget Embed
Add the Coverly quote widget to any page with a single script tag. Fully customizable via query params. No iframe, no API calls needed from your side.
Basic snippet
<!-- Coverly Quote Widget --> <script> window.coverlyConfig = { partner_id: 'your_partner_id', // Your numeric partner ID (e.g. 42) theme: 'dark' // 'dark' | 'light' }; </script> <script src="https://coverly-8.polsia.app/embed/coverly.js"></script>
Configuration options
| Param | Type | Description |
|---|---|---|
| partner_id | string | Your partner identifier. Routes leads to your CRM and sets notification preferences. |
| theme | string | dark (default) or light. Matches the widget's container background. |
| container_id | string | DOM element ID to mount the widget into. Defaults to creating a floating bubble. |
postMessage events
The widget fires postMessage events to the parent window so your page can react to quote lifecycle events.
window.addEventListener('message', (event) => { const data = JSON.parse(event.data); switch (data.type) { case 'quote.started': console.log('Lead started quote', data.payload); break; case 'quote.completed': console.log('Quote delivered', data.payload); break; case 'apply.submitted': console.log('Application submitted', data.payload); break; } });
Event payloads
// quote.started { "type": "quote.started", "payload": { "leadId": "cl_01J8X...", // CRM contact ID "partnerId": "acme_broker", "timestamp": "2026-07-04T10:32:00Z" } } // quote.completed { "type": "quote.completed", "payload": { "leadId": "cl_01J8X...", "quoteId": "qv_01J9A...", "carriers": ["AIG", "Lincoln", "Northwestern", "Pacific Life"], "tier": "hot", // 'hot' | 'warm' | 'cold' "aiScore": 92 } } // apply.submitted { "type": "apply.submitted", "payload": { "leadId": "cl_01J8X...", "applicationId": "app_01JB2...", "carrier": "AIG" } }
/embed/quote?partner_id=xxx&theme=dark as a full-page redirect. Works without any script tag.Single-script embed (with auto-bind attribution)
No iframe, no webhook code required. Drop /embed.js on any page with a Bearer API key on the script src — the form posts to POST /api/v1/leads and the contact is attributed to your partner account under the same spine as /quote/start and direct API calls.
<!-- Coverly Quote Widget (single-script bundle) --> <script src="https://coverly-8.polsia.app/embed.js?partner=ck_live_<your_key>&primary_color=%23e86b2c&theme=dark"></script> <div data-coverly-quote></div>
If you omit data-coverly-quote, the bundle mounts a floating action button (data-coverly-fab) at the bottom-right of the host page. Lead attribution flows through the partner=ck_live_… query string — the bundle adds that key as Authorization: Bearer ck_live_… on the POST. Loaded cross-origin from partner sites — the bundle calls /api/v1/leads directly with the same auth your API key already covers.
Configuration options
| Param | Type | Description |
|---|---|---|
| partner | string | Required. Your ck_live_… API key. Used as Authorization: Bearer on POST /api/v1/leads. |
| primary_color | string | Optional. Default #e86b2c. Hex used for accents + submit button. |
| theme | string | Optional. dark (default) or light. |
/for-brokers/embed?partner_id=… to copy a rendered snippet (with your live key + brand color baked in). The page also shows live embedding status — embedded_at, first_lead_at, and a 30-day lead count.Application API
Submit an insurance application programmatically. Coverly routes it through the carrier's underwriting engine, issues a policy number, and fires a webhook to your endpoint.
Request headers
| Header | Value |
|---|---|
| Content-Type | application/json |
| Authorization | Bearer ck_live_<your_key> |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| quoteId | string | required | Coverly quote ID returned from the widget event quote.completed. |
| carrier | string | required | Carrier name selected by the lead. One of: AIG, Lincoln, Northwestern Mutual, Pacific Life. |
| monthlyPrice | number | Override monthly premium (optional). If omitted, uses the price from the quote response. |
curl -X POST https://coverly-8.polsia.app/api/apply \\ -H "Content-Type: application/json" \\ -H "Authorization: Bearer ck_live_a3f9xxxxxxxxxxxxxxxx" \\ -d '{ "quoteId": "qv_01J9A3K...", "carrier": "AIG", "monthlyPrice": 187 }'
const res = await fetch('https://coverly-8.polsia.app/api/apply', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ck_live_a3f9xxxxxxxxxxxxxxxx' }, body: JSON.stringify({ quoteId: 'qv_01J9A3K...', carrier: 'AIG', monthlyPrice: 187 }) }); const data = await res.json(); console.log(data);
Response
{
"ok": true,
"applicationId": "app_01JB2...",
"underwriting": {
"decision": "approved", // 'approved' | 'declined' | 'review'
"score": 87,
"riskBand": "standard", // 'preferred' | 'standard' | 'substandard'
"reason": "Clean MVR, stable income",
"policyNumber": "CVLY-2026-00942",
"policyIssued": true,
"documentType": "pdf"
}
}Quote API — Health
Generate marketplace health-insurance quotes programmatically. Coverly accepts household + income + coverage preferences, runs the 2-method APTC calculation against 2026 HHS poverty guidelines, and returns a tiered plan grid (Bronze/Silver/Gold/Platinum) across 5 representative carriers.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| zipCode | string | required | 5-digit US ZIP code (e.g. 90210). |
| householdSize | integer | required | Number of members seeking coverage. |
| memberAges | string | required | Comma-separated ages of covered members (e.g. "42,38,12"). |
| householdAgi | number | required | Annual household Adjusted Gross Income in USD. |
| tobacco | boolean | Any covered member uses tobacco. Default false. | |
| employerCoverageAvailable | boolean | Whether employer-sponsored coverage is available. Default false. | |
| currentlyUninsured | boolean | Default false. | |
| planTierPreference | string | bronze|silver|gold|platinum|any. Default any. | |
| hsaEligible | boolean | Restrict results to HSA-eligible (HDHP) plans. | |
| deductibleBand | string | Max deductible in dollars ("500"|"1500"|"3500"|"6500") or "any". | |
| mustHaveRx | string | Freeform Rx / provider requirements (stored on the lead, not on individual plans). | |
| name | string | Lead name. Optional — leads can be anonymous. | |
| string | Lead email. Optional but required to enroll the lead in the nurture sequence. |
curl -X POST https://coverly-8.polsia.app/api/quote/health \\ -H "Content-Type: application/json" \\ -d '{ "zipCode": "90210", "householdSize": 3, "memberAges": "42,38,12", "householdAgi": 85000, "planTierPreference": "silver", "name": "Jane Doe", "email": "jane@example.com" }'
Response
{
"ok": true,
"quote": {
"productLine": "health",
"aptcMonthly": 350,
"aptcAnnual": 4200,
"aiTier": "hot",
"aiScore": 88,
"plans": [
{
"carrier": "Blue Cross Blue Shield",
"metal": "silver",
"monthlyPremiumPre": 485,
"monthlyPremiumPost": 135,
"deductible": 3500,
"moop": 7500,
"network": "Broad PPO",
"hsaEligible": false,
"isBestValue": true
}
]
}
}Webhook Events
Configure webhooks at /for-brokers/webhooks?partner_id=<id>. Every delivery is signed with HMAC-SHA256; failed deliveries retry on an exponential backoff for up to 24 hours.
Events
/api/apply. Payload includes applicationId, contactId, carrier, quoteId.Webhook payload
{
"id": "evt_5f8d3a90-...",
"type": "policy.issued",
"created": 1752248712,
"data": {
"applicationId": "42",
"contactId": "318",
"carrier": "AIG",
"policyNumber": "CVLY-2026-00942",
"monthlyPrice": 187
}
}Request headers
| Header | Format |
|---|---|
| Content-Type | application/json |
| Coverly-Event | The event type, e.g. policy.issued. |
| Coverly-Signature | t=<unix-seconds>,v1=<hex-hmac-sha256>. HMAC input is ${t}.${rawBody} using your endpoint's signing secret. |
| User-Agent | Coverly-Webhooks/1.0 |
Verifying signatures
Every webhook is signed with HMAC-SHA256 over ${timestamp}.${rawBody} using your endpoint's secret. Use a timing-safe comparison and reject requests whose timestamp is more than 5 minutes off — this is the security-critical bit Stripe-style signers miss.
const crypto = require('crypto'); const express = require('express'); function verifyCoverlySignature(rawBody, header, secret) { const parts = Object.fromEntries( header.split(',').map(p => p.split('=')) ); const t = parseInt(parts.t, 10); const v1 = parts.v1; // 1. Reject stale or far-future timestamps (replay-attack defense). const skew = Math.abs(Math.floor(Date.now() / 1000) - t); if (!Number.isFinite(t) || skew > 300) return false; // 2. Recompute HMAC over `${timestamp}.${rawBody}` and timing-safe compare. const expected = crypto .createHmac('sha256', secret) .update(`${t}.${rawBody}`) .digest('hex'); const a = Buffer.from(v1, 'hex'); const b = Buffer.from(expected, 'hex'); return a.length === b.length && crypto.timingSafeEqual(a, b); } app.post('/webhooks/coverly', express.raw({ type: 'application/json' }), (req, res) => { const sig = req.headers['coverly-signature'] || ''; if (!verifyCoverlySignature(req.body, sig, process.env.COVERLY_WEBHOOK_SECRET)) { return res.status(401).send('Invalid signature'); } const event = JSON.parse(req.body.toString('utf8')); // event.id is stable across retries — use it for idempotency. // event.type is one of: lead.created, quote.generated, application.submitted, ... // event.data is event-type-specific (see above). res.status(200).send('ok'); });
import hmac, hashlib, time def verify_coverly_signature(raw_body: bytes, header: str, secret: str) -> bool: parts = dict(p.split('=', 1) for p in header.split(',')) try: t = int(parts['t']) v1 = parts['v1'] except (KeyError, ValueError): return False # 1. Reject timestamps more than 5 minutes off. if abs(int(time.time()) - t) > 300: return False # 2. Recompute HMAC and compare. signed = f"{t}.".encode() + raw_body expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest() return hmac.compare_digest(v1, expected) # In Flask: from flask import request, abort @app.route("/webhooks/coverly", methods=["POST"]) def webhook(): sig = request.headers.get("Coverly-Signature", "") if not verify_coverly_signature(request.get_data(), sig, WEBHOOK_SECRET): abort(401) event = request.get_json() # event["id"] is stable across retries — use it for idempotency. return "ok", 200
/for-brokers/webhooks — your endpoint keeps receiving deliveries during rotation, but deliveries will fail to verify until you update your server with the new secret.Public API
The structured integration surface — push leads, query policies, validate webhooks.
// POST a new lead curl -X POST https://coverly-8.polsia.app/api/v1/leads \\ -H "Authorization: Bearer ck_live_..." \\ -H "Content-Type: application/json" \\ -d '{ "email": "...", "zipCode": "...", "vehicle": {...} }'
Authentication
All API requests require a partner API key. Keys are prefixed by environment:
ck_live_<32-char-key> // Production key ck_test_<32-char-key> // Sandbox key
Request signing (recommended)
For additional security, sign each request body with HMAC-SHA256 and include the signature in the X-Coverly-Signature header.
const crypto = require('crypto'); function signRequest(body, secret) { const bodyStr = typeof body === 'string' ? body : JSON.stringify(body); const sig = crypto.createHmac('sha256', secret).update(bodyStr).digest('hex'); return sig; }
Getting your API key
Generate and manage your API keys via the self-serve portal. Each key is shown once at creation — store it in your password manager or secrets manager.
Key management
Each key is prefixed ck_live_ (production) or ck_test_ (sandbox). Only the first 12 characters of the key body are visible in the portal — the full key is shown once on generation and cannot be recovered. Revoke old keys immediately after rotation.
# Example key prefix — shown in the portal ck_live_a3f9... # Full key (shown once, never stored) ck_live_a3f9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Partner Dashboard Authentication
Partners access the dashboard at /for-brokers/dashboard using a magic-link flow — no passwords required.
Magic-link flow
Request a magic link by email. If the account exists, an email is sent immediately; otherwise the endpoint returns success anyway to prevent email enumeration.
| Field | Type | Required | Description |
|---|---|---|---|
| string | required | The partner email address on file. |
{
"email": "you@agency.com"
}{
"ok": true,
"message": "Magic link sent"
}The email contains a time-limited link. Clicking it triggers the verification endpoint.
Validates the token, sets the partner_session cookie, and redirects to /for-brokers/dashboard. Token expires after 15 minutes.
/for-brokers/login?error=invalid. Unknown email redirects to /for-brokers/login?error=not_found.Session cookie
| Property | Value |
|---|---|
| Name | partner_session |
| Type | JWT (HS256) |
| Expiry | 7 days |
| Flags | httpOnly, sameSite: lax, secure in production |
Logout
Clears the partner_session cookie and redirects to /for-brokers/login.
Error codes
| Code | Meaning |
|---|---|
| invalid | Token was tampered with, malformed, or expired. Request a new magic link. |
| not_found | No partner account exists for that email address. |
Environments
| Environment | Base URL | Description |
|---|---|---|
| ● Sandbox | https://sandbox.coverly-8.polsia.app |
Isolated test environment. No real carriers, no real data. Perfect for integration testing. |
| ● Production | https://coverly-8.polsia.app |
Live environment. Real carrier APIs, real data, real webhooks. |
Sandbox keys start with ck_test_. Production keys start with ck_live_. Keys from one environment do not work in the other.
Changelog
Public v1 API + Self-Serve Key Rotation
Six endpoints under /api/v1: POST /leads, GET /leads, GET /leads/:id, POST /quotes, GET /policies/:id, POST /webhooks/test. Bearer ck_live_... auth, per-key rate limit (60 rpm / 1000 rph) with X-RateLimit-* headers, api_request_log audit row per call. Self-serve rotate at POST /api/partner-keys/:id/rotate — new plaintext once; old key blocked at the verification edge immediately. Environment label (live | test) on every key. Full reference.
Health Insurance Intake + Marketplace Quote Engine
3-step health intake at /quote/health (household basics → income/subsidy → coverage preferences). Marketplace-style plan grid (Bronze/Silver/Gold/Platinum × 5 representative carriers: BCBS, UHC, Ambetter, Oscar, Kaiser). 2-method APTC calculation against 2026 HHS poverty guidelines (8.5% FPL cap vs. 100–250% sliding scale — picks the smaller required contribution). CRM partition via contacts.product_line; partner dashboard shows Life vs. Health leads on a new product-mix tile.
Webhook Delivery Infrastructure
Six lifecycle events (lead.created, quote.generated, application.submitted, application.underwritten, policy.issued, contact.tier_changed) POST to partner URLs with HMAC-SHA256 signing (Coverly-Signature: t=<ts>,v1=<hex>). Exponential-backoff retry (1m, 5m, 30m, 2h, 6h, 24h), max 6 attempts; auto-disable after 20 consecutive failures. Self-serve endpoint management + delivery log + replay at /for-brokers/webhooks.
Self-Serve API Key Management + Partner Attribution
Partner API keys now self-serve at /for-brokers/api-keys?partner_id=<id>. Keys prefixed ck_live_ (32-byte random, sha256 hashed — plaintext never stored). One-time display of full key on generation, copy-to-clipboard UI, revoke flow. Optional partner attribution on POST /api/apply via Authorization: Bearer ck_live_... header — routes leads to partner dashboard. Rate limited to 5 key operations/hour per partner. Docs updated.
Embed Widget Launch
White-label embed widget at /embed/quote. Floating/inline script loader with postMessage height resize. Snippet generator at /for-brokers/embed with syntax-highlighted copy button. Partner notification routing via partner_id.
Competitive Positioning Pages
Four competitor comparison pages (/vs/superagent, /vs/pathwork, /vs/zowie, /vs/kinro) with side-by-side feature matrices, Coverly win arguments, and CTAs.
Underwriting Engine + Application Flow
Full application lifecycle: POST /api/apply, automated underwriting decision (approved/declined/review), policy number issuance, and policy.issued webhook. AI-powered decision scoring with risk band classification.
Lincoln Financial Live Connector
Lincoln Financial — life (term/whole/IUL) + auto connector promoted from stub to live-ready. Env-gated on LINCOLN_API_KEY / LINCOLN_API_URL (and optional LINCOLN_LIFE_API_URL). Falls back to sim when unconfigured.
Carrier API Integration Layer
Pluggable carrier connector architecture (lib/carrier-api/), AIG proof-of-concept connector, stubs for Lincoln, Northwestern Mutual, and Pacific Life. Live wins over simulation, simulation fills gaps until live API keys are configured.