Coverly Public API

The v1 surface brokers and carrier partners use to push leads, request quotes, query policy details, and validate webhook delivery — programmatically, without the web UI.

Base URL: https://coverly-8.polsia.app/api/v1. For api-tester conveniences, every endpoint accepts JSON bodies and returns JSON. Every authenticated request appends a row to api_request_log (path, status, latency) for audit.

Authentication

Every v1 request must include a Bearer token in the Authorization header. Each key belongs to exactly one partner — the response data is scoped to your contacts only.

Request header
Authorization: Bearer ck_live_a3f9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Keys are produced by POST /api/partner-keys on the partner portal. You can also reuse the new POST /api/partner-keys/:id/rotate endpoint to swap a key while keeping its row.

Open API key management → Generate, label as Live or Test, rotate, revoke. The plaintext is shown once.

Rate limits

Per-key sliding windows, surfaced as standard HTTP headers on every response:

  • 60 requests/minute per key
  • 1000 requests/hour per key
Headers returned on every response
X-RateLimit-Limit:          60
X-RateLimit-Remaining:      57
X-RateLimit-Reset:          42          // seconds until the minute window resets
X-RateLimit-Limit-Hour:     1000
X-RateLimit-Remaining-Hour: 973
X-RateLimit-Reset-Hour:     1820
Retry-After: 42           // present on 429 only

On exceed, the request returns 429 rate_limited with a Retry-After header (same value as the relevant reset seconds).

Versioning

v1 endpoints live under /api/v1. Breaking changes (payload shape, headers, auth) bump the prefix (/api/v2); additive changes (new optional field, new endpoint) never break existing integrations.

Today: only v1. There is no public beta channel — preview flags, if any, are off-by-default and announced in the changelog.

POST /leads

Push a new lead or upsert an existing one (matched on lowercase email). Returns the assigned contact id, AI tier + score, and the partner the lead is attributed to.

POST /api/v1/leads

Request body

FieldTypeRequiredDescription
namestringLead name. Defaults to "Anonymous".
emailstringrequiredLead email — used as the upsert key.
phonestringE.164 preferred.
zipCodestringrequired5-digit ZIP used for carrier routing.
requestedProductstringlife (default — auto) or health.
vehicleobject{ year, make, model }. Required for life leads.
healthobjectFor health leads: { householdSize, memberAges, householdAgi, tobacco, planTierPreference, ... }
cURL
curl -X POST https://coverly-8.polsia.app/api/v1/leads \
  -H "Authorization: Bearer ck_live_a3f9xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Jane Doe",
    "email": "jane@example.com",
    "zipCode": "90210",
    "vehicle": { "year": 2021, "make": "Toyota", "model": "Camry" }
  }'

Response

{
  "id": 1842,
  "ai_tier": "warm",
  "ai_score": 63,
  "ai_insight": "Lead created via /api/v1/leads — pending qualification.",
  "status": "new",
  "product_line": "life",
  "partner_id": 42
}

GET /leads

Paginated list of your leads (scoped to the authenticated partner).

GET /api/v1/leads

Query parameters

ParamTypeDescription
statusstringFilter by new|contacted|converted|lost.
limitintegerDefault 25, max 100.
pageinteger1-indexed. Defaults to 1.
cURL
curl -G https://coverly-8.polsia.app/api/v1/leads \
  -H "Authorization: Bearer ck_live_a3f9xxxxxxxxxxxxxxxx" \
  --data-urlencode "status=converted" \
  --data-urlencode "page=1" \
  --data-urlencode "limit=50"

Response

{
  "rows": [
    {
      "id": 1842,
      "name": "Jane Doe",
      "email": "jane@example.com",
      "ai_tier": "warm",
      "ai_score": 63,
      "status": "contacted",
      "selected_carrier": "AIG",
      "monthly_premium": 187,
      "bound_application_id": 412,
      "bound_policy_number": "CVLY-2026-00942",
      "created_at": "2026-07-17T11:42:09Z"
    }
  ],
  "total": 127,
  "page": 1,
  "limit": 25
}

GET /leads/:id

Lead detail — contact, intake payload, applications, and event log. Returns 403 if the contact belongs to another partner, 404 if missing.

GET /api/v1/leads/:id
cURL
curl https://coverly-8.polsia.app/api/v1/leads/1842 \
  -H "Authorization: Bearer ck_live_a3f9xxxxxxxxxxxxxxxx"

Response

{
  "contact": { "id": 1842, "ai_tier": "warm", "...": "..." },
  "intake":  { "payload": { "..." }, "company": "...", "source": "..." },
  "applications": [
    {
      "id": 412,
      "carrier": "AIG",
      "product_type": "auto",
      "monthly_premium": 187,
      "status": "bound",
      "policy_number": "CVLY-2026-00942",
      "policy_issued_at": "2026-07-17T13:11:00Z"
    }
  ],
  "events": [
    {
      "id": 4120,
      "event_type": "carrier_selected",
      "carrier_selected": "AIG",
      "monthly_price": 187,
      "created_at": "2026-07-17T12:55:00Z"
    }
  ],
  "bound_application_id": 412,
  "bound_policy_number":  "CVLY-2026-00942"
}

POST /quotes

Re-run quote generation for an existing lead. Returns the same enriched carrier/AI shape as the web /quote/results page.

POST /api/v1/quotes

Request body

FieldTypeRequiredDescription
lead_idintegerrequiredContact id from POST /leads.
force_refreshbooleanInformational — engine regenerates from scratch each call.
cURL
curl -X POST https://coverly-8.polsia.app/api/v1/quotes \
  -H "Authorization: Bearer ck_live_a3f9xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "lead_id": 1842 }'

Response (life / auto)

{
  "lead_id": 1842,
  "product_line": "life",
  "ai_tier": "warm",
  "ai_score": 63,
  "ai_insight": "Lead qualifies for coverage.",
  "ai_angle": "Save up to $500 on auto insurance.",
  "carriers": [
    { "name": "AIG",       "monthly_price": 187, "is_live": true  },
    { "name": "Lincoln",   "monthly_price": 201, "is_live": false },
    { "name": "Northwestern", "monthly_price": 214, "is_live": false },
    { "name": "Pacific Life", "monthly_price": 229, "is_live": false }
  ],
  "refreshed_at": "2026-07-17T13:14:22Z"
}
Health leads return a plans[] grid (Bronze/Silver/Gold/Platinum) and an aptc_monthly subsidy estimate instead of carriers[] — same shape used by /quote/health/results.

GET /policies/:id

Application + policy detail by application id. Returns a ready-to-use certificateUrl (HMAC-signed) when a policy number has been issued.

GET /api/v1/policies/:id
cURL
curl https://coverly-8.polsia.app/api/v1/policies/412 \
  -H "Authorization: Bearer ck_live_a3f9xxxxxxxxxxxxxxxx"

Response

{
  "applicationId": 412,
  "contactId": 1842,
  "policyNumber": "CVLY-2026-00942",
  "carrier": "AIG",
  "productType": "auto",
  "productLine": "life",
  "monthlyPremium": 187,
  "coverageAmount": 250000,
  "issuedAt": "2026-07-17T13:11:00Z",
  "status": "bound",
  "underwritingDecision": "approved",
  "certificateUrl": "https://coverly-8.polsia.app/policies/412/certificate.pdf?token=…"
}

POST /webhooks/test

Fire a real, signed webhook event for the authenticated partner so you can validate the delivery pipeline end to end (signing, retries, delivery log). Default event_type is lead.created.

POST /api/v1/webhooks/test

Request body (optional)

FieldTypeDescription
event_typestringOne of lead.created, quote.generated, application.submitted, application.underwritten, application.signed, policy.issued, contact.tier_changed.
cURL
curl -X POST https://coverly-8.polsia.app/api/v1/webhooks/test \
  -H "Authorization: Bearer ck_live_a3f9xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "event_type": "policy.issued" }'

Response

{
  "ok": true,
  "dispatched_to": 2,
  "event_type": "policy.issued",
  "note": "Inspect /for-brokers/webhooks for the signed delivery."
}
Subscriptions are checked per your dashboard config. If no endpoint is subscribed, dispatched_to is 0 with a warning field.

Errors

All errors share the same envelope: { "error": "<code>", "message"?: ..., "retry_after"?: ... }

400 Bad Request
validation

Missing or malformed JSON body, invalid email, missing ZIP / vehicle.

401 Unauthorized
unauthorized

Missing Authorization: Bearer header, malformed token, or revoked key.

403 Forbidden
not_your_lead

The contact or application id belongs to another partner.

404 Not Found
not_found

No contact, application, or policy with that id.

429 Too Many Requests
rate_limited

60 rpm / 1000 rph exceeded. Retry-After header included.

500 Server Error
server_error

Retry with backoff. If persistent, contact partners@coverly.app with the request id.

Need outbound webhooks too?

Public API covers your inbound integrations (push leads, query policy details). For outbound events (Coverly → your endpoint), see the Webhooks dashboard and reference.

Open Webhooks reference → Subscribe to lead.created, policy.issued, etc. with HMAC-signed delivery and retry.