Interactive API Explorer (Scalar)
Browse, search, and try every endpoint in the interactive Scalar explorer. Access requires Cloudflare Access authentication.
The OpenInsure API is a versioned REST API built with Hono on Cloudflare Workers. The OpenAPI specification is auto-generated from TypeSpec route definitions and is always in sync with the deployed API.
Interactive API Explorer (Scalar)
Browse, search, and try every endpoint in the interactive Scalar explorer. Access requires Cloudflare Access authentication.
| Environment | Base URL |
|---|---|
| Production | https://api.openinsure.dev/v1 |
| Sandbox | https://sandbox.api.openinsure.dev/v1 |
| Local (Wrangler dev) | http://localhost:8787/v1 |
The sandbox environment uses isolated test data. Stripe is in test mode (use 4242 4242 4242 4242 as the test card). Policies bound in the sandbox are never reported to carriers.
All API requests require auth. The platform currently supports three primary modes:
If the caller already has an auth session token (from the D1-backed auth worker at auth-dev.openinsure.dev), exchange it for an OpenInsure API JWT:
curl -X POST https://api.openinsure.dev/auth/better/exchange \ -H "Authorization: Bearer <session-token>"Response contains a signed OpenInsure JWT (token) used as Authorization: Bearer <token> for all subsequent API calls.
For internal automation and demo/smoke scripts, the configured API_SECRET can be used directly as a bearer token:
curl https://api.openinsure.dev/v1/policies?orgId=<org-uuid> \ -H "Authorization: Bearer $API_SECRET"You can also exchange API_SECRET for a short-lived demo JWT:
curl -X POST https://api.openinsure.dev/auth/demo \ -H "Content-Type: application/json" \ -d '{"secret":"'"$API_SECRET"'"}'Long-lived API keys for server-to-server integrations. Created in the Admin UI under Settings → API Keys or via the API:
POST /v1/api-keysAuthorization: Bearer <admin_jwt>Content-Type: application/json
{ "name": "Applied Epic Integration", "scopes": ["submissions:write", "policies:read", "coi:generate"], "expiresAt": null // null = never expires (rotate manually)}
# Response:{ "key": "oik_live_1a2b3c4d5e6f7g8h...", // shown ONCE — store securely "id": "key_01J8...", "name": "Applied Epic Integration", "scopes": ["submissions:write", "policies:read", "coi:generate"]}Use the API key in the X-API-Key header (or Authorization: Bearer):
curl https://api.openinsure.dev/v1/submissions \ -H "X-API-Key: oik_live_1a2b3c4d5e6f7g8h..."| Environment | Limit | Window |
|---|---|---|
| Sandbox | 100 requests | per minute per API key |
| Production (standard) | 1,000 requests | per minute per API key |
| Production (enterprise) | 10,000 requests | per minute per API key |
AI endpoints (/documents/ingest) | 20 requests | per minute |
Rate limit headers are included in every response:
X-RateLimit-Limit: 1000X-RateLimit-Remaining: 847X-RateLimit-Reset: 1719878460When you exceed the rate limit, the API returns 429 Too Many Requests with a Retry-After header.
All errors return a consistent JSON structure:
{ "error": "validation_failed", "message": "Missing required field: effective_date", "details": { "field": "effective_date", "rule": "required" }, "requestId": "req_01J8K3M4N5P6Q7R8"}| Code | Meaning |
|---|---|
200 OK | Successful GET or PATCH |
201 Created | Successful POST that created a resource |
204 No Content | Successful DELETE |
400 Bad Request | Malformed JSON or missing required fields |
401 Unauthorized | Missing or invalid Bearer token / API key |
403 Forbidden | Valid auth, but SpiceDB ReBAC denied the action |
404 Not Found | Resource does not exist in your organization |
409 Conflict | Duplicate resource (e.g., policy number collision) |
422 Unprocessable Entity | Business rule violation (invalid state transition, DA limit exceeded) |
429 Too Many Requests | Rate limit exceeded |
500 Internal Server Error | Unexpected error — include requestId in support tickets |
| Method | Path | Description |
|---|---|---|
POST | /submissions | Create a new submission (JSON or ACORD 125 PDF) |
GET | /submissions | List submissions (filterable by status, producer, date) |
GET | /submissions/:id | Get submission detail including triage results |
POST | /submissions/:id/quote | Rate a submission and return a quote |
GET | /submissions/:id/quote-readiness | Triage score (0–100) and binding readiness |
POST | /submissions/:id/bind | Bind a quoted submission into a policy |
GET | /policies | List policies |
GET | /policies/:id | Get policy detail |
POST | /policies/:id/endorse | Apply a mid-term endorsement |
POST | /policies/:id/cancel | Cancel a policy |
POST | /policies/:id/reinstate | Reinstate a cancelled policy |
POST | /policies/:id/renew | Initiate renewal process |
GET | /policies/:id/timeline | Full endorsement and transaction history |
| Method | Path | Description |
|---|---|---|
POST | /claims | File FNOL |
GET | /claims | List claims (filterable by status, adjuster, policy) |
GET | /claims/:id | Get claim detail |
POST | /claims/:id/investigate | Start investigation phase |
POST | /claims/:id/reserves | Set or update reserves |
POST | /claims/:id/settle | Create settlement offer |
POST | /claims/:id/deny | Deny coverage |
POST | /claims/:id/close | Close the claim |
POST | /claims/:id/attachments | Upload claim document |
GET | /claims/:id/attachments | List claim documents |
| Method | Path | Description |
|---|---|---|
GET | /invoices | List invoices |
GET | /invoices/:id | Get invoice detail |
POST | /invoices/:id/payment-intent | Create Stripe PaymentIntent |
POST | /invoices/:id/pay | Record a manual payment |
POST | /invoices/:id/void | Void an invoice |
POST | /invoices/:id/refund | Issue a refund |
POST | /invoices/:id/retry | Retry a failed payment |
| Method | Path | Description |
|---|---|---|
POST | /coi/generate | Generate a Certificate of Insurance |
POST | /documents/ingest | Ingest and AI-classify a document |
POST | /documents/render-pdf | Render HTML template to PDF |
GET | /documents/:id | Get document metadata and download URL |
| Method | Path | Description |
|---|---|---|
GET | /webhooks | List webhook subscriptions |
POST | /webhooks | Create a webhook endpoint |
DELETE | /webhooks/:id | Delete a webhook |
| Method | Path | Description |
|---|---|---|
GET | /producers | List producers with search + status filter |
GET | /producers/:id | Producer detail with license info |
PATCH | /producers/:id | Update producer profile |
GET | /producers/:id/appointments | List state/LOB appointments |
POST | /producers/:id/appointments | Create an appointment |
PATCH | /producers/:id/appointments/:apptId | Update appointment status |
| Method | Path | Description |
|---|---|---|
GET | /deal-rooms | List deal rooms for the organization |
POST | /deal-rooms | Create a deal room for a submission |
GET | /deal-rooms/:id | Get deal room detail + participants |
GET | /deal-rooms/:id/messages | Paginated message history |
POST | /deal-rooms/:id/messages | Post a message |
GET | /deal-rooms/:id/ws | Upgrade to WebSocket (returns signed wsUrl) |
GET | /referrals | List referrals (filterable by status, entity) |
POST | /referrals | Create a referral (optionally linked to a deal room) |
POST | /referrals/:id/decision | Approve, reject, or request more info |
| Method | Path | Description |
|---|---|---|
GET | /flags | List all feature flags and per-org overrides |
PUT | /flags/:key | Set a flag value (org_admin scoped) |
DELETE | /flags/:key | Remove a per-org override (revert to global default) |
| Method | Path | Description |
|---|---|---|
GET | /analytics/:orgId/portfolio | Portfolio KPIs (GWP, loss ratio, binding count) |
GET | /analytics/:orgId/commissions | Commission summary by period |
GET | /analytics/:orgId/da-utilization | DA aggregate utilization |
OpenInsure sends webhook notifications for all major domain events. All payloads are signed with HMAC-SHA256.
| Event | Description |
|---|---|
policy.bound | A new policy was successfully bound |
policy.endorsed | A mid-term endorsement was applied |
policy.cancelled | A policy was cancelled |
policy.renewed | A renewal policy was created |
claim.filed | A new FNOL was received |
claim.reserved | Claim reserves were set or updated |
claim.settled | A settlement was approved |
claim.closed | A claim was closed |
payment.received | A premium payment was processed |
payment.failed | A payment attempt failed |
da.limit_approaching | DA aggregate utilization exceeded 85% |
compliance.deadline_approaching | A filing deadline is within 30 days |
{ "id": "evt_01J8K3M4N5P6Q7R8S9T0", "type": "policy.bound", "orgId": "org_01J8...", "timestamp": "2026-03-08T14:32:00Z", "data": { "policyId": "pol_01J8...", "policyNumber": "GL-2026-000142", "grossPremium": 14250, "effectiveDate": "2026-04-01" }}import { createHmac, timingSafeEqual } from 'crypto';
function verifyWebhook(rawBody: string, signature: string, secret: string): boolean { const expected = createHmac('sha256', secret).update(rawBody).digest('hex'); return timingSafeEqual(Buffer.from(signature), Buffer.from(`sha256=${expected}`));}
// In your webhook handler:const sig = request.headers.get('X-OpenInsure-Signature');if (!verifyWebhook(rawBody, sig, process.env.WEBHOOK_SECRET)) { return new Response('Invalid signature', { status: 401 });}# Full submission → quote → bind in bash
API="https://api.openinsure.dev/v1"TOKEN="your_api_key_here"
# Create submissionSUB_ID=$(curl -s -X POST $API/submissions \ -H "X-API-Key: $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "insuredName": "Pacific Coast Builders", "naicsCode": "236220", "annualRevenue": 8000000, "requestedLimit": 1000000, "effectiveDate": "2026-05-01", "state": "OR", "lineOfBusiness": "GL" }' | jq -r '.id')
# Get quoteQUOTE=$(curl -s -X POST $API/submissions/$SUB_ID/quote \ -H "X-API-Key: $TOKEN")echo "Premium: $(echo $QUOTE | jq '.grossPremium')"
# BindPOLICY=$(curl -s -X POST $API/submissions/$SUB_ID/bind \ -H "X-API-Key: $TOKEN" \ -H "Content-Type: application/json" \ -d '{"installmentPlan":"MONTHLY_10"}')echo "Policy: $(echo $POLICY | jq -r '.policyNumber')"import { OpenInsureClient } from '@openinsure/api-client';
const client = new OpenInsureClient({ apiKey: process.env.OI_API_KEY, baseUrl: 'https://api.openinsure.dev/v1',});
async function bindPolicy() { // Create submission const submission = await client.submissions.create({ insuredName: 'Pacific Coast Builders', naicsCode: '236220', annualRevenue: 8_000_000, requestedLimit: 1_000_000, effectiveDate: '2025-08-01', state: 'OR', lineOfBusiness: 'GL', });
// Quote const quote = await client.submissions.quote(submission.id); console.log(`Gross premium: $${quote.grossPremium.toFixed(2)}`);
// Check DA flags before binding if (!quote.bindable) { console.error('DA flags:', quote.daFlags); return; }
// Bind const policy = await client.submissions.bind(submission.id, { installmentPlan: 'MONTHLY_10', });
console.log(`Bound: ${policy.policyNumber}`); return policy;}
bindPolicy().catch(console.error);import os, httpx
BASE = "https://api.openinsure.dev/v1"HEADERS = {"X-API-Key": os.environ["OI_API_KEY"]}
with httpx.Client(base_url=BASE, headers=HEADERS) as client: # Create submission sub = client.post("/submissions", json={ "insuredName": "Pacific Coast Builders", "naicsCode": "236220", "annualRevenue": 8_000_000, "requestedLimit": 1_000_000, "effectiveDate": "2026-05-01", "state": "OR", "lineOfBusiness": "GL", }).raise_for_status().json()
# Quote quote = client.post(f"/submissions/{sub['id']}/quote").raise_for_status().json() print(f"Premium: ${quote['grossPremium']:,.2f}")
# Bind policy = client.post(f"/submissions/{sub['id']}/bind", json={ "installmentPlan": "MONTHLY_10" }).raise_for_status().json() print(f"Policy: {policy['policyNumber']}")/v1/portal/*)The portal API serves policyholder-facing data for both the web portal and mobile app. All endpoints require a Bearer JWT with typ: 'insured'.
| Endpoint | Method | Description |
|---|---|---|
/v1/portal/home | GET | Aggregated dashboard data (mobile home screen) |
/v1/portal/policies | GET | List policies for authenticated insured |
/v1/portal/policies/:id | GET | Policy detail |
/v1/portal/policies/:id/coi | GET | Presigned COI download URL |
/v1/portal/policies/:id/mcs90 | GET | Presigned MCS-90 download URL |
/v1/portal/invoices | GET | List invoices |
/v1/portal/invoices/:id | GET | Invoice detail |
/v1/portal/invoices/:id/download | GET | Presigned invoice PDF download |
/v1/portal/documents | GET | List policy-linked documents |
/v1/portal/documents/:id/download | GET | Presigned document download |
/v1/portal/profile | GET | Insured profile |
/v1/portal/profile | PUT | Update contact fields (phone, address) |
/v1/portal/push-tokens | POST | Register push notification token |
Authentication: Policyholder JWTs are obtained via the OTP login flow — POST /auth/policyholder-otp-request followed by POST /auth/policyholder-token.
The TypeSpec-generated OpenAPI 3.1 spec is available at:
https://api.openinsure.dev/openapi.jsonYou can use this spec to:
openapi-ts, openapi-generator, or SpeakeasyThe spec is regenerated on every deploy and is always in sync with the production API.