Skip to main content

API Reference

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.

Base URLs

EnvironmentBase URL
Productionhttps://api.openinsure.dev/v1
Sandboxhttps://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.

Authentication

All API requests require auth. The platform currently supports three primary modes:

1. Auth Session Exchange → OpenInsure JWT

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.

2. API Secret Bearer (System/Machine Workflows)

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"'"}'

3. API Key (Machine-to-Machine Integrations)

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-keys
Authorization: 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..."

Rate Limits

EnvironmentLimitWindow
Sandbox100 requestsper minute per API key
Production (standard)1,000 requestsper minute per API key
Production (enterprise)10,000 requestsper minute per API key
AI endpoints (/documents/ingest)20 requestsper minute

Rate limit headers are included in every response:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1719878460

When you exceed the rate limit, the API returns 429 Too Many Requests with a Retry-After header.

Error Format

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"
}

HTTP Status Codes

CodeMeaning
200 OKSuccessful GET or PATCH
201 CreatedSuccessful POST that created a resource
204 No ContentSuccessful DELETE
400 Bad RequestMalformed JSON or missing required fields
401 UnauthorizedMissing or invalid Bearer token / API key
403 ForbiddenValid auth, but SpiceDB ReBAC denied the action
404 Not FoundResource does not exist in your organization
409 ConflictDuplicate resource (e.g., policy number collision)
422 Unprocessable EntityBusiness rule violation (invalid state transition, DA limit exceeded)
429 Too Many RequestsRate limit exceeded
500 Internal Server ErrorUnexpected error — include requestId in support tickets

Core Endpoints

Submissions & Policies

MethodPathDescription
POST/submissionsCreate a new submission (JSON or ACORD 125 PDF)
GET/submissionsList submissions (filterable by status, producer, date)
GET/submissions/:idGet submission detail including triage results
POST/submissions/:id/quoteRate a submission and return a quote
GET/submissions/:id/quote-readinessTriage score (0–100) and binding readiness
POST/submissions/:id/bindBind a quoted submission into a policy
GET/policiesList policies
GET/policies/:idGet policy detail
POST/policies/:id/endorseApply a mid-term endorsement
POST/policies/:id/cancelCancel a policy
POST/policies/:id/reinstateReinstate a cancelled policy
POST/policies/:id/renewInitiate renewal process
GET/policies/:id/timelineFull endorsement and transaction history

Claims

MethodPathDescription
POST/claimsFile FNOL
GET/claimsList claims (filterable by status, adjuster, policy)
GET/claims/:idGet claim detail
POST/claims/:id/investigateStart investigation phase
POST/claims/:id/reservesSet or update reserves
POST/claims/:id/settleCreate settlement offer
POST/claims/:id/denyDeny coverage
POST/claims/:id/closeClose the claim
POST/claims/:id/attachmentsUpload claim document
GET/claims/:id/attachmentsList claim documents

Billing & Payments

MethodPathDescription
GET/invoicesList invoices
GET/invoices/:idGet invoice detail
POST/invoices/:id/payment-intentCreate Stripe PaymentIntent
POST/invoices/:id/payRecord a manual payment
POST/invoices/:id/voidVoid an invoice
POST/invoices/:id/refundIssue a refund
POST/invoices/:id/retryRetry a failed payment

Documents & COI

MethodPathDescription
POST/coi/generateGenerate a Certificate of Insurance
POST/documents/ingestIngest and AI-classify a document
POST/documents/render-pdfRender HTML template to PDF
GET/documents/:idGet document metadata and download URL

Webhooks

MethodPathDescription
GET/webhooksList webhook subscriptions
POST/webhooksCreate a webhook endpoint
DELETE/webhooks/:idDelete a webhook

Producers & Appointments

MethodPathDescription
GET/producersList producers with search + status filter
GET/producers/:idProducer detail with license info
PATCH/producers/:idUpdate producer profile
GET/producers/:id/appointmentsList state/LOB appointments
POST/producers/:id/appointmentsCreate an appointment
PATCH/producers/:id/appointments/:apptIdUpdate appointment status

Deal Rooms & Referrals

MethodPathDescription
GET/deal-roomsList deal rooms for the organization
POST/deal-roomsCreate a deal room for a submission
GET/deal-rooms/:idGet deal room detail + participants
GET/deal-rooms/:id/messagesPaginated message history
POST/deal-rooms/:id/messagesPost a message
GET/deal-rooms/:id/wsUpgrade to WebSocket (returns signed wsUrl)
GET/referralsList referrals (filterable by status, entity)
POST/referralsCreate a referral (optionally linked to a deal room)
POST/referrals/:id/decisionApprove, reject, or request more info

Feature Flags

MethodPathDescription
GET/flagsList all feature flags and per-org overrides
PUT/flags/:keySet a flag value (org_admin scoped)
DELETE/flags/:keyRemove a per-org override (revert to global default)

Analytics & Reporting

MethodPathDescription
GET/analytics/:orgId/portfolioPortfolio KPIs (GWP, loss ratio, binding count)
GET/analytics/:orgId/commissionsCommission summary by period
GET/analytics/:orgId/da-utilizationDA aggregate utilization

Webhooks

OpenInsure sends webhook notifications for all major domain events. All payloads are signed with HMAC-SHA256.

Event Types

EventDescription
policy.boundA new policy was successfully bound
policy.endorsedA mid-term endorsement was applied
policy.cancelledA policy was cancelled
policy.renewedA renewal policy was created
claim.filedA new FNOL was received
claim.reservedClaim reserves were set or updated
claim.settledA settlement was approved
claim.closedA claim was closed
payment.receivedA premium payment was processed
payment.failedA payment attempt failed
da.limit_approachingDA aggregate utilization exceeded 85%
compliance.deadline_approachingA filing deadline is within 30 days

Webhook Payload Format

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

Verifying Webhook Signatures

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 });
}
caution

Always verify webhook signatures before processing the payload. Unverified webhooks are a common attack vector for replay attacks and injection.

Code Examples

bash

# Full submission → quote → bind in bash

API="https://api.openinsure.dev/v1"
TOKEN="your_api_key_here"

# Create submission
SUB_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 quote
QUOTE=$(curl -s -X POST $API/submissions/$SUB_ID/quote \
-H "X-API-Key: $TOKEN")
echo "Premium: $(echo $QUOTE | jq '.grossPremium')"

# Bind
POLICY=$(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')"

TypeScript

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);

Python

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']}")

Policyholder Portal API (/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'.

EndpointMethodDescription
/v1/portal/homeGETAggregated dashboard data (mobile home screen)
/v1/portal/policiesGETList policies for authenticated insured
/v1/portal/policies/:idGETPolicy detail
/v1/portal/policies/:id/coiGETPresigned COI download URL
/v1/portal/policies/:id/mcs90GETPresigned MCS-90 download URL
/v1/portal/invoicesGETList invoices
/v1/portal/invoices/:idGETInvoice detail
/v1/portal/invoices/:id/downloadGETPresigned invoice PDF download
/v1/portal/documentsGETList policy-linked documents
/v1/portal/documents/:id/downloadGETPresigned document download
/v1/portal/profileGETInsured profile
/v1/portal/profilePUTUpdate contact fields (phone, address)
/v1/portal/push-tokensPOSTRegister push notification token

Authentication: Policyholder JWTs are obtained via the OTP login flowPOST /auth/policyholder-otp-request followed by POST /auth/policyholder-token.

OpenAPI Specification

The TypeSpec-generated OpenAPI 3.1 spec is available at:

https://api.openinsure.dev/openapi.json

You can use this spec to:

  • Generate a typed client SDK in any language with tools like openapi-ts, openapi-generator, or Speakeasy
  • Import into Postman, Insomnia, or Bruno for local testing
  • Set up contract testing in your CI pipeline

The spec is regenerated on every deploy and is always in sync with the production API.