Skip to content

AI-ready page export

FirstCyber Quote Service

Service Snapshot

ItemSandboxProduction
Base URLhttps://api-sandbox.k2cyber.co/quote/firstcyberhttps://api.k2cyber.co/quote/firstcyber
AuthOAuth 2.0 bearer token + Integration key headerOAuth 2.0 bearer token + Integration key header
Token URLhttps://api-sandbox.k2cyber.co/auth/tokenhttps://api.k2cyber.co/auth/token
Scopequotequote
FormatsJSON UTF-8JSON UTF-8

Credentials

Both client credentials and integration keys are generated via the Partner Portal. Use the client credentials flow with your client_id and client_secret to obtain bearer tokens. Tokens should be cached and refreshed before expiry. Integration keys should be attached in an x-integration-key header with every request.

Sandbox Environment

The sandbox environment is available for integration development and testing with isolated synthetic data. Start your integration in sandbox and validate thoroughly before moving to production.

Authentication

Every public FirstCyber Quote endpoint requires both headers below on every request.

HeaderValueDescription
AuthorizationBearer <token>OAuth 2.0 client-credentials bearer token. Token URL: https://api-sandbox.k2cyber.co/auth/token (sandbox) or https://api.k2cyber.co/auth/token (production). Scope: quote.
x-integration-key<INTEGRATION-KEY>Integration key issued via the Partner Portal. Required on every request alongside the bearer token.

Cache Your Bearer Tokens

Bearer Tokens last for 1 hour. We encourage you to cache your Bearer token and reuse it across multiple requests.

For each HTTP request to any K2 Cyber API endpoint:

  1. Generate a bearer token (if not already exists)
bash
curl -X POST https://api.k2cyber.co/auth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=<CLIENT_ID>&client_secret=<CLIENT_SECRET>&grant_type=client_credentials&scope=quote"
javascript
const body = new URLSearchParams({
  client_id: process.env.K2_CLIENT_ID,
  client_secret: process.env.K2_CLIENT_SECRET,
  grant_type: "client_credentials",
  scope: "quote",
});

const response = await fetch("https://api.k2cyber.co/auth/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body,
});

const { access_token, expires_in } = await response.json();
python
import os
import requests

response = requests.post(
    "https://api.k2cyber.co/auth/token",
    data={
        "client_id": os.environ["K2_CLIENT_ID"],
        "client_secret": os.environ["K2_CLIENT_SECRET"],
        "grant_type": "client_credentials",
        "scope": "quote",
    },
)
token = response.json()
  1. Make an API Request with an integration key and the access_token
bash
curl -X POST https://api.k2cyber.co/quote/firstcyber/submit \
  -H "x-integration-key: <INTEGRATION-KEY>" \
  -H "Authorization: Bearer eyJhbGc..." \
  -H "Content-Type: application/json" \
  -d '{...}'
javascript
const response = await fetch("https://api.k2cyber.co/quote/firstcyber/submit", {
  method: "POST",
  headers: {
    "x-integration-key": process.env.K2_INTEGRATION_KEY,
    Authorization: `Bearer ${access_token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    /* underwriting fields — see POST /submit */
  }),
});

const result = await response.json();
python
import os
import requests

response = requests.post(
    "https://api.k2cyber.co/quote/firstcyber/submit",
    headers={
        "x-integration-key": os.environ["K2_INTEGRATION_KEY"],
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json",
    },
    json={
        # underwriting fields — see POST /submit
    },
)
result = response.json()

See our Partner Portal section for information on how to obtain these credentials.

Downloads

ResourceDescription
OpenAPI Spec (YAML)OpenAPI 3.1 specification for code generation and API clients
Postman CollectionPre-configured Postman collection with example requests

Using the Postman Collection

  1. Import the collection into Postman
  2. Set the base_url variable to https://api-sandbox.k2cyber.co/quote/firstcyber
  3. Set the integration-key variable to your integration key from the Partner Portal (sent as x-integration-key on every request)
  4. Set the access_token variable with your OAuth token, or use Postman's Get New Access Token — the collection is preconfigured for OAuth 2.0 client credentials (scope=quote, token URL https://api-sandbox.k2cyber.co/auth/token) with your client_id and client_secret
  5. Start testing endpoints immediately

Endpoints

POST /submit

Submit underwriting information for a new piece of business and receive a bindable quote. Returns a quote identifier, status, rated coverage details, and a checkout link.

High risk operations

If the insured derives revenue from any of the operations listed below, set question_highrisk to true:

  • Pornography
  • Gambling
  • Cannabis
  • Cryptocurrency or Blockchain Technology
  • Debt Collection
  • Professional Data Processing / Aggregation, Storage, or Hosting
  • Digital Tracking or Surveillance Services
  • Managed Service or Security Service Provider (MSP or MSSP)
  • Cyber Security Products or Services
  • Managed, Accountable Care, or Nursing Care
  • Sale of Firearms and Ammunition

A quote.created event will be emitted with the quote ID once the quote has finished rating. The quote proposal PDF becomes available to download from GET /document/quote-proposal/stream/{id} once the quote.proposalReady webhook event has been emitted for the same quote ID. See the webhook documentation for more details on these events.

bash
curl -X POST https://api.k2cyber.co/quote/firstcyber/submit \
  -H "x-integration-key: <INTEGRATION-KEY>" \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "broker_email": "broker@example.com",
    "insured_name": "Acme Widgets LLC",
    "insured_location": {
      "address_line1": "510 Meadowmont Village Circle #142",
      "address_line2": "Suite 200",
      "address_city": "Chapel Hill",
      "address_state": "NC",
      "address_zip": "27514"
    },
    "insured_taxid": "12-3456789",
    "claims": {
      "claims_count": 0,
      "claims_amount": 0
    },
    "year_founded": 2018,
    "effective_date": "2025-11-01",
    "revenue": 5000000,
    "naics": 722515,
    "question_highrisk": false,
    "agg_limit": 1000000,
    "retention": 2500,
    "website": {
      "has_website": true,
      "domainName": "https://example.com"
    },
    "insured_contact": {
      "first_name": "John",
      "last_name": "Doe",
      "email": "john.doe@example.com",
      "phone": "123-456-7890",
      "preferred_method": "Email"
    }
  }'
javascript
const response = await fetch("https://api.k2cyber.co/quote/firstcyber/submit", {
  method: "POST",
  headers: {
    "x-integration-key": process.env.K2_INTEGRATION_KEY,
    Authorization: `Bearer ${access_token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    broker_email: "broker@example.com",
    insured_name: "Acme Widgets LLC",
    insured_location: {
      address_line1: "510 Meadowmont Village Circle #142",
      address_line2: "Suite 200",
      address_city: "Chapel Hill",
      address_state: "NC",
      address_zip: "27514",
    },
    insured_taxid: "12-3456789",
    claims: {
      claims_count: 0,
      claims_amount: 0,
    },
    year_founded: 2018,
    effective_date: "2025-11-01",
    revenue: 5000000,
    naics: 722515,
    question_highrisk: false,
    agg_limit: 1000000,
    retention: 2500,
    website: {
      has_website: true,
      domainName: "https://example.com",
    },
    insured_contact: {
      first_name: "John",
      last_name: "Doe",
      email: "john.doe@example.com",
      phone: "123-456-7890",
      preferred_method: "Email",
    },
  }),
});

const result = await response.json();
python
import os
import requests

response = requests.post(
    "https://api.k2cyber.co/quote/firstcyber/submit",
    headers={
        "x-integration-key": os.environ["K2_INTEGRATION_KEY"],
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json",
    },
    json={
        "broker_email": "broker@example.com",
        "insured_name": "Acme Widgets LLC",
        "insured_location": {
            "address_line1": "510 Meadowmont Village Circle #142",
            "address_line2": "Suite 200",
            "address_city": "Chapel Hill",
            "address_state": "NC",
            "address_zip": "27514",
        },
        "insured_taxid": "12-3456789",
        "claims": {
            "claims_count": 0,
            "claims_amount": 0,
        },
        "year_founded": 2018,
        "effective_date": "2025-11-01",
        "revenue": 5000000,
        "naics": 722515,
        "question_highrisk": False,
        "agg_limit": 1000000,
        "retention": 2500,
        "website": {
            "has_website": True,
            "domainName": "https://example.com",
        },
        "insured_contact": {
            "first_name": "John",
            "last_name": "Doe",
            "email": "john.doe@example.com",
            "phone": "123-456-7890",
            "preferred_method": "Email",
        },
    },
)
result = response.json()

GET /status/{id}

Retrieve the latest status and rated details for a previously created quote.

bash
curl -X GET "https://api.k2cyber.co/quote/firstcyber/status/123e4567-e89b-12d3-a456-426614174000" \
  -H "x-integration-key: <INTEGRATION-KEY>" \
  -H "Authorization: Bearer <TOKEN>"
javascript
const response = await fetch(
  "https://api.k2cyber.co/quote/firstcyber/status/123e4567-e89b-12d3-a456-426614174000",
  {
    method: "GET",
    headers: {
      "x-integration-key": process.env.K2_INTEGRATION_KEY,
      Authorization: `Bearer ${access_token}`,
    },
  },
);

const status = await response.json();
python
import os
import requests

response = requests.get(
    "https://api.k2cyber.co/quote/firstcyber/status/123e4567-e89b-12d3-a456-426614174000",
    headers={
        "x-integration-key": os.environ["K2_INTEGRATION_KEY"],
        "Authorization": f"Bearer {access_token}",
    },
)
status = response.json()

PUT /update/{id}

Update contact information on a quote prior to binding. Quote must be in ready_to_bind or approved_rate status.

bash
curl -X PUT "https://api.k2cyber.co/quote/firstcyber/update/123e4567-e89b-12d3-a456-426614174000" \
  -H "x-integration-key: <INTEGRATION-KEY>" \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "insured_contact": {
      "first_name": "Jane",
      "last_name": "Smith",
      "email": "jane.smith@example.com",
      "phone": "123-456-7890",
      "preferred_method": "Email"
    },
    "it_manager_same": false,
    "it_manager_contact": {
      "first_name": "Bob",
      "last_name": "Johnson",
      "email": "bob.johnson@example.com",
      "title": "IT Manager"
    }
  }'
javascript
const response = await fetch(
  "https://api.k2cyber.co/quote/firstcyber/update/123e4567-e89b-12d3-a456-426614174000",
  {
    method: "PUT",
    headers: {
      "x-integration-key": process.env.K2_INTEGRATION_KEY,
      Authorization: `Bearer ${access_token}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      insured_contact: {
        first_name: "Jane",
        last_name: "Smith",
        email: "jane.smith@example.com",
        phone: "123-456-7890",
        preferred_method: "Email",
      },
      it_manager_same: false,
      it_manager_contact: {
        first_name: "Bob",
        last_name: "Johnson",
        email: "bob.johnson@example.com",
        title: "IT Manager",
      },
    }),
  },
);

const result = await response.json();
python
import os
import requests

response = requests.put(
    "https://api.k2cyber.co/quote/firstcyber/update/123e4567-e89b-12d3-a456-426614174000",
    headers={
        "x-integration-key": os.environ["K2_INTEGRATION_KEY"],
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json",
    },
    json={
        "insured_contact": {
            "first_name": "Jane",
            "last_name": "Smith",
            "email": "jane.smith@example.com",
            "phone": "123-456-7890",
            "preferred_method": "Email",
        },
        "it_manager_same": False,
        "it_manager_contact": {
            "first_name": "Bob",
            "last_name": "Johnson",
            "email": "bob.johnson@example.com",
            "title": "IT Manager",
        },
    },
)
result = response.json()

PUT /bind/{id}

Bind a quote and issue the policy. The bind operation is processed asynchronously — the response returns immediately with status: accepted and the quote identifier, and a policy.created webhook event is emitted once bind completes.

See the webhook documentation for more details on the policy.created event.

bash
curl -X PUT "https://api.k2cyber.co/quote/firstcyber/bind/123e4567-e89b-12d3-a456-426614174000" \
  -H "x-integration-key: <INTEGRATION-KEY>" \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{}'
javascript
const response = await fetch(
  "https://api.k2cyber.co/quote/firstcyber/bind/123e4567-e89b-12d3-a456-426614174000",
  {
    method: "PUT",
    headers: {
      "x-integration-key": process.env.K2_INTEGRATION_KEY,
      Authorization: `Bearer ${access_token}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({}),
  },
);

const result = await response.json();
python
import os
import requests

response = requests.put(
    "https://api.k2cyber.co/quote/firstcyber/bind/123e4567-e89b-12d3-a456-426614174000",
    headers={
        "x-integration-key": os.environ["K2_INTEGRATION_KEY"],
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json",
    },
    json={},
)
result = response.json()

GET /document/quote-preview/stream/{id}

Retrieve the quote preview PDF for a quote.

bash
curl -X GET "https://api.k2cyber.co/quote/firstcyber/document/quote-preview/stream/123e4567-e89b-12d3-a456-426614174000" \
  -H "x-integration-key: <INTEGRATION-KEY>" \
  -H "Authorization: Bearer <TOKEN>" \
  -o quote-preview.pdf
javascript
const response = await fetch(
  "https://api.k2cyber.co/quote/firstcyber/document/quote-preview/stream/123e4567-e89b-12d3-a456-426614174000",
  {
    method: "GET",
    headers: {
      "x-integration-key": process.env.K2_INTEGRATION_KEY,
      Authorization: `Bearer ${access_token}`,
    },
  },
);

const buffer = Buffer.from(await response.arrayBuffer());
require("fs").writeFileSync("quote-preview.pdf", buffer);
python
import os
import requests

response = requests.get(
    "https://api.k2cyber.co/quote/firstcyber/document/quote-preview/stream/123e4567-e89b-12d3-a456-426614174000",
    headers={
        "x-integration-key": os.environ["K2_INTEGRATION_KEY"],
        "Authorization": f"Bearer {access_token}",
    },
)
with open("quote-preview.pdf", "wb") as f:
    f.write(response.content)

GET /document/quote-proposal/stream/{id}

Retrieve the rated quote-proposal PDF for a quote. Available once the quote has finished rating (approved_rate, ready_to_bind, underwriter_review, or issued). Returns 409 while the quote is still rating or in a bind/issue transition state.

The proposal is ready to download as soon as the quote.proposalReady webhook event has been emitted for the corresponding quote_id. If you are not subscribed to that event, see the Quote Proposal Availability table below for the equivalent quote statuses. Full document availability across statuses is summarized in the Quote Status Flow.

Quote Proposal Availability

The quote proposal is generated once the quote has finished rating. It can be retrieved when the quote is in one of the following statuses:

StatusProposal Available
approved_rateYes
ready_to_bindYes
underwriter_reviewYes
issuedYes
pendingNot yet — quote is still being rated
boundNot available — quote has transitioned past proposal
declinedNot available
failedNot available
bash
curl -X GET "https://api.k2cyber.co/quote/firstcyber/document/quote-proposal/stream/123e4567-e89b-12d3-a456-426614174000" \
  -H "x-integration-key: <INTEGRATION-KEY>" \
  -H "Authorization: Bearer <TOKEN>" \
  -o quote-proposal.pdf
javascript
const response = await fetch(
  "https://api.k2cyber.co/quote/firstcyber/document/quote-proposal/stream/123e4567-e89b-12d3-a456-426614174000",
  {
    method: "GET",
    headers: {
      "x-integration-key": process.env.K2_INTEGRATION_KEY,
      Authorization: `Bearer ${access_token}`,
    },
  },
);

const buffer = Buffer.from(await response.arrayBuffer());
require("fs").writeFileSync("quote-proposal.pdf", buffer);
python
import os
import requests

response = requests.get(
    "https://api.k2cyber.co/quote/firstcyber/document/quote-proposal/stream/123e4567-e89b-12d3-a456-426614174000",
    headers={
        "x-integration-key": os.environ["K2_INTEGRATION_KEY"],
        "Authorization": f"Bearer {access_token}",
    },
)
with open("quote-proposal.pdf", "wb") as f:
    f.write(response.content)

Polling

If your integration retrieves the proposal immediately after POST /submit, expect a brief window where 409 Not Ready is returned while rating completes. Poll GET /status/{id} until quote_status is ready_to_bind (or another proposal-eligible status above) before requesting the proposal.

GET /document/policy-preview/stream/{id}

Retrieve the pre-issuance policy preview PDF for a quote. Available once the quote has finished rating (approved_rate, ready_to_bind, or underwriter_review). Once the quote is issued, the preview is no longer available — use GET /document/policy/stream/{id} to retrieve the issued policy packet instead.

Policy Preview Availability

The policy preview can be retrieved when the quote is in one of the following statuses. See also the consolidated document availability matrix.

StatusPreview Available
approved_rateYes
ready_to_bindYes
underwriter_reviewYes
issuedNo — use GET /document/policy/stream/{id} instead
pendingNot yet — quote is still being rated
boundNot available — quote has transitioned past preview
declinedNot available
failedNot available
bash
curl -X GET "https://api.k2cyber.co/quote/firstcyber/document/policy-preview/stream/123e4567-e89b-12d3-a456-426614174000" \
  -H "x-integration-key: <INTEGRATION-KEY>" \
  -H "Authorization: Bearer <TOKEN>" \
  -o policy-preview.pdf
javascript
const response = await fetch(
  "https://api.k2cyber.co/quote/firstcyber/document/policy-preview/stream/123e4567-e89b-12d3-a456-426614174000",
  {
    method: "GET",
    headers: {
      "x-integration-key": process.env.K2_INTEGRATION_KEY,
      Authorization: `Bearer ${access_token}`,
    },
  },
);

const buffer = Buffer.from(await response.arrayBuffer());
require("fs").writeFileSync("policy-preview.pdf", buffer);
python
import os
import requests

response = requests.get(
    "https://api.k2cyber.co/quote/firstcyber/document/policy-preview/stream/123e4567-e89b-12d3-a456-426614174000",
    headers={
        "x-integration-key": os.environ["K2_INTEGRATION_KEY"],
        "Authorization": f"Bearer {access_token}",
    },
)
with open("policy-preview.pdf", "wb") as f:
    f.write(response.content)

GET /document/policy/stream/{id}

Retrieve the issued policy PDF. Only available once the quote is in issued status.

The policy document is ready to download as soon as the policy.created webhook event has been emitted for the corresponding quote_id. If you are not subscribed to that event, poll GET /status/{id} until quote_status is issued before requesting the document.

bash
curl -X GET "https://api.k2cyber.co/quote/firstcyber/document/policy/stream/123e4567-e89b-12d3-a456-426614174000" \
  -H "x-integration-key: <INTEGRATION-KEY>" \
  -H "Authorization: Bearer <TOKEN>" \
  -o policy.pdf
javascript
const response = await fetch(
  "https://api.k2cyber.co/quote/firstcyber/document/policy/stream/123e4567-e89b-12d3-a456-426614174000",
  {
    method: "GET",
    headers: {
      "x-integration-key": process.env.K2_INTEGRATION_KEY,
      Authorization: `Bearer ${access_token}`,
    },
  },
);

const buffer = Buffer.from(await response.arrayBuffer());
require("fs").writeFileSync("policy.pdf", buffer);
python
import os
import requests

response = requests.get(
    "https://api.k2cyber.co/quote/firstcyber/document/policy/stream/123e4567-e89b-12d3-a456-426614174000",
    headers={
        "x-integration-key": os.environ["K2_INTEGRATION_KEY"],
        "Authorization": f"Bearer {access_token}",
    },
)
with open("policy.pdf", "wb") as f:
    f.write(response.content)

Quote Status Values

The quote_status field indicates the current state of a quote in the processing workflow:

StatusDescription
pendingQuote is being processed or awaiting review
approved_rateQuote has been approved with a rate, but broker is not onboarded to K2 Cyber
ready_to_bindQuote is ready to be bound
boundQuote has been bound
issuedPolicy has been successfully issued
declinedQuote was declined by underwriting
underwriter_reviewQuote requires manual underwriter review
failedQuote processing failed due to an error

Happy path: For straight-through processing, quotes follow this progression:

pending → ready_to_bind → bound → issued

See the Quote Status Flow for alternate outcomes and document availability by status.

Error Handling

HTTP Status Codes

CodeMeaning
200Success
202Accepted - request accepted for asynchronous processing (bind endpoint)
400Bad Request - validation error or declined quote
401Unauthorized - invalid or expired token
404Not Found - quote ID doesn't exist
500Internal Server Error

Error Response Format

json
{
  "status": "error",
  "error": {
    "message": "Detailed error message"
  }
}

Declined Quote Format

json
{
  "status": "declined",
  "error": {
    "message": "Reason for decline"
  }
}

Best Practices

Token Management

  • Cache tokens and reuse until near expiry
  • Implement token refresh logic before expiration
  • Store tokens securely (never in client-side code or logs)

Error Handling

  • Implement exponential backoff for 5xx errors
  • Log all error responses with quote IDs for troubleshooting
  • Handle both error and declined status appropriately in your UI

Status Polling

  • Poll /status/{id} endpoint to check quote processing status
  • Use reasonable polling intervals (e.g., every 2-3 seconds)
  • Stop polling once status is ready_to_bind, approved_rate, underwriter_review, or failed
  • Status flow: pendingready_to_bindboundissued

Validation

  • Validate email formats before submission
  • Ensure NAICS codes are 6 digits
  • Format dates as YYYY-MM-DD
  • Use proper EIN format for tax IDs

Integration Checklist

  • [ ] Obtain OAuth credentials from Partner Portal
  • [ ] Implement token acquisition and refresh logic
  • [ ] Test submit endpoint with sample data
  • [ ] Implement status polling for async quote processing
  • [ ] Handle declined quotes gracefully
  • [ ] Test update endpoint for contact changes
  • [ ] Test bind endpoint for policy issuance
  • [ ] Implement proper error logging with quote IDs
  • [ ] Complete integration testing before production deployment

Support — For technical questions about the FirstCyber Quote Service, email ray@k2cyber.ai. Include the quote_id and timestamp in all support requests.

Maintained by the K2 Cyber Insurance engineering team.