# Home Source: /index.md ## What's Documented - **FirstCyber Quote Service** · RESTful API for commercial cyber insurance quoting and binding - **Partner Portal** · Credential management, webhook keys, and integration keys - **Webhook Management** · Event subscriptions, delivery logs, and signature verification ## Need Help? - Email [ray@k2cyber.ai](mailto:ray@k2cyber.ai) for integration questions. - Join the partner Slack workspace for real-time assistance. - Review the [Changelog](/changelog) for breaking changes and maintenance windows. --- # Quick Start Source: /quick-start.md # Quick Start Kick off your integration with K2 using the checklist below. Each step links to deeper guidance when you need it. ## 1. Request Access to the Partner Portal ::: info Onboarding Contact your K2 partner manager or email [ray@k2cyber.ai](mailto:ray@k2cyber.ai). Provide your organization name, primary technical contact, and target launch timeline. ::: - Receive Partner Portal invitations for your administrators. - Confirm who will own production cutover and 24/7 escalation duties. ## 2. Set Up Environments | Environment | Purpose | API Base URL | Partner Portal | |-------------|---------|--------------|----------------| | Sandbox | Integration development and testing | `https://api-sandbox.k2cyber.co` | `https://partners-sandbox.k2cyber.co` | | Production | Live traffic | `https://api.k2cyber.co` | `https://partners.k2cyber.co` | ::: tip Sandbox Environment The sandbox environment uses isolated synthetic data for integration development and testing. Use sandbox credentials to validate your integration before moving to production. ::: Store credentials securely in your secrets manager and rotate them according to your security policy. ### Choose Your Product Type During sandbox registration at [partners-sandbox.k2cyber.co](https://partners-sandbox.k2cyber.co/), you'll select a product type — **Broker Bill** or **Direct Bill**. This choice is associated with your API key for that account. ::: info Good to Know — Product Type and Sandbox Binding - **Direct Bill** supports the full quote-to-bind flow in the sandbox. When creating quotes, use `direct@k2cyber.ai` as the broker email. - **Broker Bill** supports quoting and rating in the sandbox. Binding is available in production only. If you'd like to test both, you can register a second account at [partners-sandbox.k2cyber.co](https://partners-sandbox.k2cyber.co/) with the other product type — it only takes a minute. ::: ### Check Eligibility Before submitting test quotes, use the [Appetite Checker](https://appetite-test.k2cyber.co/) to verify that your test business profile is within K2 Cyber's appetite. Enter a NAICS code and revenue to see whether the combination is eligible and what coverage limits are available. This can save time if you're seeing unexpected decline responses. ## 3. Configure Authentication for API Requests In order to integrate with K2 Cyber's API, each HTTP request must include the following headers: - `x-integration-key: ` - `authorization: Bearer ` These are configured via the [Partner Portal](/services/partner-portal). ## 4. Develop Your Integration - **Integration Walkthrough**: Follow a complete quote-to-policy lifecycle with copy-pasteable examples in the [Integration Walkthrough](/guides/integration-walkthrough). - **REST API**: For full endpoint reference, see the [Quote API](/services/firstcyber-quote-service). - **Webhook Portal**: For details on subscribing to real-time events, see the documentation for managing [Webhooks via our Portal](/services/webhook-portal). ::: tip Set Up Webhooks Early We recommend subscribing to webhook events before you begin testing so you can observe the full policy lifecycle. Events like `policy.created` notify your system when asynchronous processes complete — no polling needed. Visit the [Webhook Management Portal](/services/webhook-portal) to configure subscriptions. ::: ## 5. Build & Test 1. Develop and test your integration using the Partner Portal credentials. 2. Register a webhook endpoint and send test events to confirm signature validation. 3. Walk through the Partner Portal checklist until every item is flagged **Complete**. Capture screenshots or logs as evidence for your go-live review. ### Sandbox Reference Keep these in mind when working in the sandbox: | Area | Note | Details | |------|------|---------| | Product type | Selected at registration, tied to your API key | Register a second account at [partners-sandbox.k2cyber.co](https://partners-sandbox.k2cyber.co/) if you need a different product type | | Binding | Broker Bill accounts support quoting and rating only | Register a Direct Bill account to test the full bind flow | | Broker email | Direct Bill accounts use a designated test email | Use `direct@k2cyber.ai` as the broker email | | Declines | Quotes may be declined based on NAICS or revenue | Use the [Appetite Checker](https://appetite-test.k2cyber.co/) to verify eligibility | ## 6. Go Live - Submit the production access request inside the Partner Portal. - Confirm monitoring and on-call contacts using your internal runbook. - Review the [Changelog](/changelog) and subscribe to release notifications. Once approved, production credentials appear in the portal and the K2 support team coordinates your launch window. ## Support - Email: [ray@k2cyber.ai](mailto:ray@k2cyber.ai) - Slack: Invite-only partner workspace (request during onboarding) - Status Page: --- Interactive OpenAPI reference blocks are omitted from this markdown export. Use the [OpenAPI Spec (YAML)](/specs/firstcyber-quote.yaml) for schemas, parameters, and code generation. --- # Integration Walkthrough Source: /guides/integration-walkthrough.md # Integration Walkthrough Follow a complete FirstCyber quote from authentication through policy issuance. Each step shows a copy-pasteable request and response. For endpoint field reference, see the [FirstCyber Quote Service](/services/firstcyber-quote-service). ::: tip Sandbox first Run this walkthrough against `https://api-sandbox.k2cyber.co` with sandbox Partner Portal credentials. For Direct Bill bind testing, use `direct@k2cyber.ai` as the broker email. See [Quick Start](/quick-start) for environment setup. ::: ## 1. Authenticate Obtain a bearer token with your M2M client credentials. Cache the token and refresh before `expires_in` elapses. **Request** ::: code-group ```bash [cURL] curl -X POST https://api-sandbox.k2cyber.co/auth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "client_id=&client_secret=&grant_type=client_credentials&scope=quote" ``` ```javascript [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-sandbox.k2cyber.co/auth/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body, }); const { access_token, expires_in } = await response.json(); ``` ```python [Python] import os import requests response = requests.post( "https://api-sandbox.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() ``` ::: **Response** ```json { "access_token": "eyJhbGc...", "token_type": "bearer", "expires_in": 3600 } ``` Every subsequent request needs both: - `Authorization: Bearer ` - `x-integration-key: ` Credentials come from the [Partner Portal](/services/partner-portal). ## 2. Submit a quote Send underwriting information to create a quote. The response includes a `quote_id` and an initial `quote_status`. **Request** ::: code-group ```bash [cURL] curl -X POST https://api-sandbox.k2cyber.co/quote/firstcyber/submit \ -H "x-integration-key: " \ -H "Authorization: Bearer " \ -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_city": "Chapel Hill", "address_state": "NC", "address_zip": "27514" }, "claims": { "claims_count": 0, "claims_amount": 0 }, "effective_date": "2025-11-01", "revenue": 5000000, "naics": 722515, "question_highrisk": false, "agg_limit": 1000000, "retention": 2500 }' ``` ```javascript [JavaScript] const response = await fetch("https://api-sandbox.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_city: "Chapel Hill", address_state: "NC", address_zip: "27514", }, claims: { claims_count: 0, claims_amount: 0 }, effective_date: "2025-11-01", revenue: 5000000, naics: 722515, question_highrisk: false, agg_limit: 1000000, retention: 2500, }), }); const result = await response.json(); ``` ```python [Python] import os import requests response = requests.post( "https://api-sandbox.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_city": "Chapel Hill", "address_state": "NC", "address_zip": "27514", }, "claims": {"claims_count": 0, "claims_amount": 0}, "effective_date": "2025-11-01", "revenue": 5000000, "naics": 722515, "question_highrisk": False, "agg_limit": 1000000, "retention": 2500, }, ) result = response.json() ``` ::: **Response** ::: code-group ```json [200 Success] { "status": "approved", "data": { "created_at": "2025-10-14T12:00:00.000Z", "message": "Please note that the minimum retention is $2,500 based on the information provided.", "quote_id": "123e4567-e89b-12d3-a456-426614174000", "quote_status": "ready_to_bind", "checkout_link": "https://checkout.k2cyber.co/...", "product_details": { "product_name": "FirstCyber Standard" }, "policy_term": { "premium_only": "$5,000.00", "policy_fee": "$300.00", "agg_limit": "$1,000,000.00", "retention": "$2,500.00", "effective_date": "2025-11-01", "expiration_date": "2026-11-01", "include_tria": true, "prior_acts": "None" }, "personal_cyber": { "premium": "$500.00", "count": 1, "limit": "$25,000.00", "retention": "$250.00" }, "coverage_details": { "info_privacy_network_limit": "$1,000,000.00", "regulatory_limit": "$1,000,000.00", "pci_dss_limit": "$100,000.00", "business_interruption_limit": "$500,000.00", "vendor_bi_limit": "$250,000.00", "cyber_extortion_limit": "$500,000.00", "funds_transfer_limit": "$100,000.00", "fraudulent_instruction_limit": "$100,000.00", "invoice_manipulation_limit": "$100,000.00", "media_liability_limit": "$1,000,000.00", "system_failure_limit": "$500,000.00", "vendor_system_failure_limit": "$250,000.00", "incident_response_limit": "$50,000.00", "data_recovery_limit": "$50,000.00", "utility_fraud_limit": "$25,000.00", "reputational_harm_limit": "$25,000.00", "business_interruption_restoration_period": 30, "business_interruption_waiting_period": 8, "vendor_bi_restoration_period": 30, "vendor_bi_waiting_period": 8, "vendor_system_failure_restoration_period": 30, "vendor_system_failure_waiting_period": 8, "system_failure_restoration_period": 30, "system_failure_waiting_period": 8 } } } ``` ```json [400 Declined] { "status": "declined", "error": { "message": "Risk does not meet underwriting guidelines" } } ``` ```json [400 Validation] { "status": "error", "error": { "message": "Validation error details" } } ``` ::: Save `data.quote_id` — every later step uses it. Rating may still be in progress when submit returns with `quote_status: "pending"`; poll or wait for webhooks until the quote is ready. ## 3. Wait for rating (poll or webhook) Choose one approach based on your architecture. ### Option A — Poll status Call `GET /status/{id}` every 5–10 seconds until `quote_status` is `ready_to_bind` (or another terminal / decision state). **Request** ::: code-group ```bash [cURL] curl -X GET "https://api-sandbox.k2cyber.co/quote/firstcyber/status/123e4567-e89b-12d3-a456-426614174000" \ -H "x-integration-key: " \ -H "Authorization: Bearer " ``` ```javascript [JavaScript] const response = await fetch( "https://api-sandbox.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 [Python] import os import requests response = requests.get( "https://api-sandbox.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() ``` ::: **Response** ```json { "status": "approved", "data": { "quote_id": "123e4567-e89b-12d3-a456-426614174000", "quote_status": "ready_to_bind" } } ``` Stop polling once status is `ready_to_bind`, `approved_rate`, `underwriter_review`, `declined`, or `failed`. See [Quote Status Values](/services/firstcyber-quote-service#quote-status-values) and the [Quote Status Flow](/reference/quote-status-flow). ### Option B — Subscribe to webhooks Subscribe early in the [Webhook Portal](/services/webhook-portal) so you receive lifecycle events without polling: | Event | Meaning | | --------------------------------------------------------------------- | ------------------------------------------ | | [`quote.created`](/services/webhook-portal#quote-created) | Quote finished rating; status is available | | [`quote.proposalReady`](/services/webhook-portal#quote-proposalready) | Quote proposal PDF is ready to download | | [`policy.created`](/services/webhook-portal#policy-created) | Policy issued after bind | ## 4. Download the quote proposal (optional) Once rating completes and the proposal is available (`quote.proposalReady`, or status `ready_to_bind` / `approved_rate` / `underwriter_review`), download the PDF. **Request** ::: code-group ```bash [cURL] curl -X GET "https://api-sandbox.k2cyber.co/quote/firstcyber/document/quote-proposal/stream/123e4567-e89b-12d3-a456-426614174000" \ -H "x-integration-key: " \ -H "Authorization: Bearer " \ -o quote-proposal.pdf ``` ```javascript [JavaScript] const response = await fetch( "https://api-sandbox.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 [Python] import os import requests response = requests.get( "https://api-sandbox.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) ``` ::: **Response** ::: code-group ```text [200 PDF] Binary PDF stream (Content-Type: application/pdf) ``` ```text [409 Not Ready] Quote proposal document is not ready yet ``` ::: A `409` means the PDF is not ready yet — wait for the webhook or poll status, then retry. Full availability rules are in [`GET /document/quote-proposal/stream/{id}`](/services/firstcyber-quote-service#get-document-quote-proposal-stream-id). ## 5. Bind the quote When status is `ready_to_bind`, you may initiate a bind. The API returns `202 Accepted` immediately; issuance continues asynchronously. **Request** ::: code-group ```bash [cURL] curl -X PUT "https://api-sandbox.k2cyber.co/quote/firstcyber/bind/123e4567-e89b-12d3-a456-426614174000" \ -H "x-integration-key: " \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{}' ``` ```javascript [JavaScript] const response = await fetch( "https://api-sandbox.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 [Python] import os import requests response = requests.put( "https://api-sandbox.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() ``` ::: **Response** ```json [202 Accepted] { "status": "accepted", "data": { "id": "123e4567-e89b-12d3-a456-426614174000" } } ``` ## 6. Get the policy document After [`policy.created`](/services/webhook-portal#policy-created) fires — or `GET /status/{id}` returns `issued` — download the policy PDF. **Request** ::: code-group ```bash [cURL] curl -X GET "https://api-sandbox.k2cyber.co/quote/firstcyber/document/policy/stream/123e4567-e89b-12d3-a456-426614174000" \ -H "x-integration-key: " \ -H "Authorization: Bearer " \ -o policy.pdf ``` ```javascript [JavaScript] const response = await fetch( "https://api-sandbox.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 [Python] import os import requests response = requests.get( "https://api-sandbox.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) ``` ::: **Response** ::: code-group ```text [200 PDF] Binary PDF stream (Content-Type: application/pdf) ``` ```text [409 Not Ready] Policy document is not ready yet ``` ::: ## Next steps - Full endpoint reference: [FirstCyber Quote Service](/services/firstcyber-quote-service) - Status transitions and document matrix: [Quote Status Flow](/reference/quote-status-flow) - Webhook delivery and signatures: [Webhook Portal](/services/webhook-portal) - Specs and Postman: [Downloads](/resources/downloads) --- Interactive OpenAPI reference blocks are omitted from this markdown export. Use the [OpenAPI Spec (YAML)](/specs/firstcyber-quote.yaml) for schemas, parameters, and code generation. --- # FirstCyber Quote Service Source: /services/firstcyber-quote-service.md # FirstCyber Quote Service ## Service Snapshot | Item | Sandbox | Production | |------|---------|------------| | Base URL | `https://api-sandbox.k2cyber.co/quote/firstcyber` | `https://api.k2cyber.co/quote/firstcyber` | | Auth | OAuth 2.0 bearer token + Integration key header | OAuth 2.0 bearer token + Integration key header | | Token URL | `https://api-sandbox.k2cyber.co/auth/token` | `https://api.k2cyber.co/auth/token` | | Scope | `quote` | `quote` | | Formats | JSON UTF-8 | JSON UTF-8 | ::: tip 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 1. Generate a bearer token **Request** ::: code-group ```bash [cURL] curl -X POST https://api.k2cyber.co/auth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "client_id=&client_secret=&grant_type=client_credentials&scope=quote" ``` ```javascript [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 [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() ``` ::: **Response** ```json { "access_token": "eyJhbGc...", "token_type": "bearer", "expires_in": 3600 } ``` 2. Make an API Request with an integration key and the `access_token` **Request** ::: code-group ```bash [cURL] curl -X POST https://api.k2cyber.co/quote/firstcyber/submit \ -H "x-integration-key: " \ -H "Authorization: Bearer eyJhbGc..." \ -H "Content-Type: application/json" \ -d '{...}' ``` ```javascript [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 [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() ``` ::: ::: tip 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. See our [Partner Portal](/services/partner-portal) section for information on how to obtain these credentials. ::: ### Downloads | Resource | Description | |----------|-------------| | [OpenAPI Spec (YAML)](/specs/firstcyber-quote.yaml) | OpenAPI 3.1 specification for code generation and API clients | | [Postman Collection](/specs/K2-Cyber-Quote-Service.postman_collection.json) | Pre-configured Postman collection with example requests | ## Endpoints ### POST /submit {#post-submit} Create a new bindable quote by submitting all required underwriting information. #### Endpoint ``` POST /submit ``` #### Required Fields | Field | Type | Description | |-------|------|-------------| | `broker_email` | string (email) | Broker's email address | | `insured_name` | string | Legal name of the insured entity | | `insured_location` | object | Physical address of insured | | `claims` | object | Prior claims history | | `effective_date` | string (date) | Policy effective date (YYYY-MM-DD) | | `revenue` | number/string | Annual revenue | | `naics` | number | 6-digit NAICS code | | `question_highrisk` | boolean | High-risk operations flag | | `agg_limit` | number/string | Aggregate policy limit | | `retention` | number/string | Policy retention/deductible | | `website` | object | Website information | | `insured_contact` | object | Primary contact information | #### High Risk Operations If the insured derives revenue from any of the operations listed below then the High-risk operations flag should be "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 #### Address Object ```json { "address_line1": "510 Meadowmont Village Circle #142", "address_line2": "Suite 200", "address_city": "Chapel Hill", "address_state": "NC", "address_zip": "27514" } ``` #### Claims Object ```json { "claims_count": 1, "claims_amount": 1000000 } ``` #### Website Object ```json { "has_website": true, "domainName": "https://example.com" } ``` #### Contact Object ```json { "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com", "phone": "123-456-7890", "preferred_method": "Email" } ``` #### Example **Request** ::: code-group ```bash [cURL] curl -X POST https://api.k2cyber.co/quote/firstcyber/submit \ -H "x-integration-key: " \ -H "Authorization: Bearer " \ -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_city": "Chapel Hill", "address_state": "NC", "address_zip": "27514" }, "claims": { "claims_count": 0, "claims_amount": 0 }, "effective_date": "2025-11-01", "revenue": 5000000, "naics": 722515, "question_highrisk": false, "agg_limit": 1000000, "retention": 2500 }' ``` ```javascript [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_city: "Chapel Hill", address_state: "NC", address_zip: "27514", }, claims: { claims_count: 0, claims_amount: 0 }, effective_date: "2025-11-01", revenue: 5000000, naics: 722515, question_highrisk: false, agg_limit: 1000000, retention: 2500, }), }); const result = await response.json(); ``` ```python [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_city": "Chapel Hill", "address_state": "NC", "address_zip": "27514", }, "claims": {"claims_count": 0, "claims_amount": 0}, "effective_date": "2025-11-01", "revenue": 5000000, "naics": 722515, "question_highrisk": False, "agg_limit": 1000000, "retention": 2500, }, ) result = response.json() ``` ::: **Response** ::: code-group ```json [200 Success] { "status": "approved", "data": { "created_at": "2025-10-14T12:00:00.000Z", "message": "Please note that the minimum retention is $2,500 based on the information provided.", "quote_id": "123e4567-e89b-12d3-a456-426614174000", "quote_status": "ready_to_bind", "checkout_link": "https://checkout.k2cyber.co/...", "product_details": { "product_name": "FirstCyber Standard" }, "policy_term": { "premium_only": "$5,000.00", "policy_fee": "$300.00", "agg_limit": "$1,000,000.00", "retention": "$2,500.00", "effective_date": "2025-11-01", "expiration_date": "2026-11-01", "include_tria": true, "prior_acts": "None" }, "personal_cyber": { "premium": "$500.00", "count": 1, "limit": "$25,000.00", "retention": "$250.00" }, "coverage_details": { "info_privacy_network_limit": "$1,000,000.00", "regulatory_limit": "$1,000,000.00", "pci_dss_limit": "$100,000.00", "business_interruption_limit": "$500,000.00", "vendor_bi_limit": "$250,000.00", "cyber_extortion_limit": "$500,000.00", "funds_transfer_limit": "$100,000.00", "fraudulent_instruction_limit": "$100,000.00", "invoice_manipulation_limit": "$100,000.00", "media_liability_limit": "$1,000,000.00", "system_failure_limit": "$500,000.00", "vendor_system_failure_limit": "$250,000.00", "incident_response_limit": "$50,000.00", "data_recovery_limit": "$50,000.00", "utility_fraud_limit": "$25,000.00", "reputational_harm_limit": "$25,000.00", "business_interruption_restoration_period": 30, "business_interruption_waiting_period": 8, "vendor_bi_restoration_period": 30, "vendor_bi_waiting_period": 8, "vendor_system_failure_restoration_period": 30, "vendor_system_failure_waiting_period": 8, "system_failure_restoration_period": 30, "system_failure_waiting_period": 8 } } } ``` ```json [400 Declined] { "status": "declined", "error": { "message": "Risk does not meet underwriting guidelines" } } ``` ```json [400 Validation] { "status": "error", "error": { "message": "Validation error details" } } ``` ::: 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}`](#get-document-quote-proposal-stream-id) once the [`quote.proposalReady`](/services/webhook-portal#quote-proposalready) webhook event has been emitted for the same quote ID. See the [webhook documentation](/services/webhook-portal#quote-created) for more details on these events. ### GET /status/{id} {#get-status-id} Retrieve current status and details for an existing quote. #### Endpoint ``` GET /status/{id} ``` #### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `id` | UUID | Yes | Quote ID returned from submit endpoint | #### Example **Request** ::: code-group ```bash [cURL] curl -X GET "https://api.k2cyber.co/quote/firstcyber/status/123e4567-e89b-12d3-a456-426614174000" \ -H "x-integration-key: " \ -H "Authorization: Bearer " ``` ```javascript [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 [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() ``` ::: **Response** ```json { "status": "approved", "data": { "quote_id": "123e4567-e89b-12d3-a456-426614174000", "quote_status": "ready_to_bind" } } ``` Returns the same structure as the submit response, with updated status and details. ### PUT /update/{id} {#put-update-id} Update contact information for a quote before binding. #### Endpoint ``` PUT /update/{id} ``` #### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `id` | UUID | Yes | Quote ID to update | #### Example **Request** ::: code-group ```bash [cURL] curl -X PUT "https://api.k2cyber.co/quote/firstcyber/update/123e4567-e89b-12d3-a456-426614174000" \ -H "x-integration-key: " \ -H "Authorization: Bearer " \ -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 [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 [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() ``` ::: **Response** ```json { "status": "success", "data": { "id": "123e4567-e89b-12d3-a456-426614174000", "status": "pending" } } ``` #### Request Body All fields are optional: ```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" } } ``` ### PUT /bind/{id} {#put-bind-id} Finalize a quote and issue the policy. #### Endpoint ``` PUT /bind/{id} ``` #### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `id` | UUID | Yes | Quote ID to bind | #### Example **Request** ::: code-group ```bash [cURL] curl -X PUT "https://api.k2cyber.co/quote/firstcyber/bind/123e4567-e89b-12d3-a456-426614174000" \ -H "x-integration-key: " \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{}' ``` ```javascript [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 [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() ``` ::: **Response** ```json [202 Accepted] { "status": "accepted", "data": { "id": "123e4567-e89b-12d3-a456-426614174000" } } ``` #### Request Body Empty object (no additional data required): ```json {} ``` The bind operation is processed asynchronously. The response returns immediately with a 202 Accepted status and the quote ID. A `policy.created` event will be emitted with the policy ID once the bind operation completes. See the [webhook documentation for more details on this event.](/services/webhook-portal#policy-created) ### GET /document/quote-preview/stream/{id} {#get-document-quote-preview-stream-id} Retrieve the quote preview document as a PDF byte stream. #### Endpoint ``` GET /document/quote-preview/stream/{id} ``` #### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `id` | UUID | Yes | Quote ID returned from submit endpoint | #### Example **Request** ::: code-group ```bash [cURL] curl -X GET "https://api.k2cyber.co/quote/firstcyber/document/quote-preview/stream/123e4567-e89b-12d3-a456-426614174000" \ -H "x-integration-key: " \ -H "Authorization: Bearer " \ -o quote-preview.pdf ``` ```javascript [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 [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) ``` ::: **Response** ::: code-group ```text [200 PDF] Binary PDF stream (Content-Type: application/pdf) ``` ```text [400 Error] Error message describing what went wrong ``` ::: ### GET /document/quote-proposal/stream/{id} {#get-document-quote-proposal-stream-id} Retrieve the rated quote proposal document as a PDF byte stream. The quote proposal is the rated, customer-facing PDF that accompanies a fully rated quote — distinct from the quote preview (which can be available earlier in the lifecycle). The proposal is ready to download as soon as the [`quote.proposalReady`](/services/webhook-portal#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](#quote-proposal-availability) table below for the equivalent quote statuses. Full document availability across statuses is summarized in the [Quote Status Flow](/reference/quote-status-flow#document-availability-by-status). #### Endpoint ``` GET /document/quote-proposal/stream/{id} ``` #### Parameters | Parameter | Type | Required | Description | | --------- | ---- | -------- | -------------------------------------- | | `id` | UUID | Yes | Quote ID returned from submit endpoint | #### 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: | Status | Proposal Available | | -------------------- | ---------------------------------------------------- | | `approved_rate` | Yes | | `ready_to_bind` | Yes | | `underwriter_review` | Yes | | `issued` | Yes | | `pending` | Not yet — quote is still being rated | | `bound` | Not available — quote has transitioned past proposal | | `declined` | Not available | | `failed` | Not available | #### Example **Request** ::: code-group ```bash [cURL] curl -X GET "https://api.k2cyber.co/quote/firstcyber/document/quote-proposal/stream/123e4567-e89b-12d3-a456-426614174000" \ -H "x-integration-key: " \ -H "Authorization: Bearer " \ -o quote-proposal.pdf ``` ```javascript [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 [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) ``` ::: **Response** ::: code-group ```text [200 PDF] Binary PDF stream (Content-Type: application/pdf) ``` ```text [409 Not Ready] Quote proposal is not ready, please try again later. ``` ```text [404 Not Found] Quote not found ``` ```text [400 / 500 Error] Error message describing what went wrong ``` ::: ::: tip 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} {#get-document-policy-preview-stream-id} Retrieve the pre-issuance policy preview as a PDF byte stream. The policy preview shows what the issued policy packet will look like before bind and issue complete. It is distinct from the quote proposal (rated quote terms) and the issued policy document (final packet after issue). #### Endpoint ``` GET /document/policy-preview/stream/{id} ``` #### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `id` | UUID | Yes | Quote ID returned from submit endpoint | #### 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](/reference/quote-status-flow#document-availability-by-status). | Status | Preview Available | | ------ | ----------------- | | `approved_rate` | Yes | | `ready_to_bind` | Yes | | `underwriter_review` | Yes | | `issued` | No — use [`GET /document/policy/stream/{id}`](#get-document-policy-stream-id) instead | | `pending` | Not yet — quote is still being rated | | `bound` | Not available — quote has transitioned past preview | | `declined` | Not available | | `failed` | Not available | #### Example **Request** ::: code-group ```bash [cURL] curl -X GET "https://api.k2cyber.co/quote/firstcyber/document/policy-preview/stream/123e4567-e89b-12d3-a456-426614174000" \ -H "x-integration-key: " \ -H "Authorization: Bearer " \ -o policy-preview.pdf ``` ```javascript [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 [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) ``` ::: **Response** ::: code-group ```text [200 PDF] Binary PDF stream (Content-Type: application/pdf) ``` ```text [400 Issued] This quote is issued into a policy. Use the policy document endpoint (/document/policy/stream/:id) to get the policy packet. ``` ```text [409 Not Ready] Policy preview is not ready, please try again later. ``` ```text [404 Not Found] Quote not found ``` ```text [500 Error] Internal Server Error: Could not get policy preview ``` ::: ### GET /document/policy/stream/{id} {#get-document-policy-stream-id} Retrieve the policy document as a PDF byte stream. Policy Documents are available once a quote is in an 'issued' status. The policy document is ready to download as soon as the [`policy.created`](/services/webhook-portal#policy-created) webhook event has been emitted for the corresponding `quote_id`. If you are not subscribed to that event, poll [`GET /status/{id}`](#get-status-id) until `quote_status` is `issued` before requesting the document. #### Endpoint ``` GET /document/policy/stream/{id} ``` #### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `id` | UUID | Yes | Quote ID returned from submit endpoint | #### Example **Request** ::: code-group ```bash [cURL] curl -X GET "https://api.k2cyber.co/quote/firstcyber/document/policy/stream/123e4567-e89b-12d3-a456-426614174000" \ -H "x-integration-key: " \ -H "Authorization: Bearer " \ -o policy.pdf ``` ```javascript [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 [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) ``` ::: **Response** ::: code-group ```text [200 PDF] Binary PDF stream (Content-Type: application/pdf) ``` ```text [400 Not Issued] No policy document is available because this quote is not issued into a policy. ``` ```text [404 Not Found] Quote not found ``` ```text [409 Not Ready] Policy packet is not ready, please try again later. ``` ```text [500 Error] Internal Server Error: Could not get policy document ``` ::: ## Quote Status Values The `quote_status` field indicates the current state of a quote in the processing workflow: | Status | Description | |--------|-------------| | `pending` | Quote is being processed or awaiting review | | `approved_rate` | Quote has been approved with a rate, but broker is not onboarded to K2 Cyber | | `ready_to_bind` | Quote is ready to be bound | | `bound` | Quote has been bound | | `issued` | Policy has been successfully issued | | `declined` | Quote was declined by underwriting | | `underwriter_review` | Quote requires manual underwriter review | | `failed` | Quote 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](/reference/quote-status-flow) for alternate outcomes and document availability by status. ## Error Handling #### HTTP Status Codes | Code | Meaning | |------|---------| | 200 | Success | | 202 | Accepted - request accepted for asynchronous processing (bind endpoint) | | 400 | Bad Request - validation error or declined quote | | 401 | Unauthorized - invalid or expired token | | 404 | Not Found - quote ID doesn't exist | | 500 | Internal 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**: `pending` → `ready_to_bind` → `bound` → `issued` #### 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](mailto:ray@k2cyber.ai). Include the `quote_id` and timestamp in all support requests. --- Interactive OpenAPI reference blocks are omitted from this markdown export. Use the [OpenAPI Spec (YAML)](/specs/firstcyber-quote.yaml) for schemas, parameters, and code generation. --- # Partner Portal Source: /services/partner-portal.md # Partner Portal The Partner Portal is your central hub for managing the credentials required to integrate with K2 Cyber's API: - **Integration Keys** - Required for API authentication via `x-integration-key` header - **M2M Clients** - OAuth 2.0 clients for generating bearer tokens - **Webhook Keys** - Registration tokens for Webhook Portal access ## Environments | Environment | Partner Portal URL | API Base URL | |-------------|-------------------|--------------| | Sandbox | `https://partners-sandbox.k2cyber.co` | `https://api-sandbox.k2cyber.co` | | Production | `https://partners.k2cyber.co` | `https://api.k2cyber.co` | ::: tip Sandbox First Start your integration in the sandbox environment. Once validated, request production credentials to go live. ::: ## Getting Started ### Registration New partners can self-register through the portal: 1. Visit the Partner Portal landing page 2. Click **Register** 3. Provide your organization details: - Company name - Contact name - Email address - Password 4. Submit your registration Your registration enters a **pending** status and is queued for approval by the K2 team. You'll receive an email notification when your account is approved. ### First Login Once approved: 1. Navigate to the Partner Portal URL 2. Enter your email and password 3. You'll be directed to your Partner Dashboard ## Partner Dashboard The dashboard provides access to: - **Integration Keys**: Create and rotate API authentication keys - **M2M Clients**: Create and manage Machine-to-Machine OAuth clients - **Webhook Keys**: Generate registration tokens for Webhook Portal access ## Managing Credentials Every HTTP request to K2 Cyber's API must include the following headers: - `x-integration-key: ` - `Authorization: Bearer ` ### Integration Keys Each API request requires an Integration Key to be sent in a `x-integration-key` header. Each partner can have two integration keys: **primary** and **secondary**. #### Create Integration Keys 1. Navigate to **Integration Keys** section 2. Click **Create Primary Key** or **Create Secondary Key** 3. Copy the generated secret immediately 4. Store it securely The two-key system allows zero-downtime key rotation: 1. Create a secondary key 2. Deploy it to your application 3. Verify it works 4. Rotate the primary key 5. Update your application to use the new primary ::: warning One-Time Display The integration key secret is displayed only once during creation. If you lose it, you must create a new key. ::: ### M2M Clients The K2 Cyber API uses the OAuth 2.0 Client Credentials flow for authorizing API requests. M2M Clients are used to generate bearer tokens that are required in the `Authorization: Bearer ` header. When you create an M2M Client through the portal, you will receive a `client_id` and `client_secret` to be used by your project. #### Create an M2M Client 1. Navigate to **M2M Clients** section in your dashboard 2. Click **Create Client** 3. Enter a descriptive name (e.g., "Production API Client") 4. Optionally add a description 5. Click **Create** 6. Copy both the **Client ID** and **Client Secret** immediately ::: warning One-Time Display The client secret is displayed only once during creation. If you lose it, you must create a new M2M client. ::: #### M2M Client Properties Each M2M client displays: - **Client Name**: Human-readable name for identification - **Client ID**: Unique identifier (format: `m2m-{partnerId}-{uuid}`) - **Client Description**: Optional notes about the client's purpose - **Secret Last Four**: Last 4 characters of the secret for reference - **Status**: Either `active` or `inactive` - **Created Date**: When the client was created #### Activate/Deactivate M2M Clients To temporarily disable a client without deleting it: 1. Find the client in your M2M Clients list 2. Click the deactivate icon 3. The client status changes to **inactive** Inactive clients cannot obtain new access tokens. Existing tokens remain valid until expiry. To reactivate: 1. Find the inactive client 2. Click the activate icon 3. The client status changes to **active** ### Webhook Keys Webhook keys are **registration tokens** that allow team members to create accounts in the [Webhook Portal](/services/webhook-portal). They are used to associate new webhook portal users with your partner organization. ::: info Not for Signature Verification Webhook keys are **not** signing secrets. The signing secrets for verifying webhook deliveries are generated separately when you create an endpoint in the Webhook Portal. ::: #### Create a Webhook Key 1. Navigate to **Webhook Keys** section 2. Click **Create Key** 3. Copy the generated key immediately 4. Share it with team members who need webhook portal access Team members use this key during Webhook Portal registration to associate their account with your organization. ::: warning One-Time Display The webhook key is displayed only once during creation. If you lose it, you must create a new key. ::: #### Webhook Key Status Each webhook key has a status: - **Active**: The key is valid and can be used - **Revoked**: The key is disabled and no longer valid #### Revoke/Reactivate a Webhook Key To revoke a key: 1. Find the key in your Webhook Keys list 2. Click the revoke icon 3. The key status changes to **revoked** You can reactivate a revoked key if needed by clicking the reactivate icon. ## Authentication Flow For each HTTP request to any K2 Cyber API endpoint: ### 1. Generate a Bearer Token **Request** ::: code-group ```bash [cURL] curl -X POST https://api.k2cyber.co/auth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "client_id=&client_secret=&grant_type=client_credentials&scope=quote" ``` ```javascript [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 [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() ``` ::: **Response** ```json { "access_token": "eyJhbGc...", "token_type": "bearer", "expires_in": 3600 } ``` ### 2. Make API Requests Include both the integration key and the bearer token in your requests: **Request** ::: code-group ```bash [cURL] curl -X POST https://api.k2cyber.co/quote/firstcyber/submit \ -H "x-integration-key: " \ -H "Authorization: Bearer eyJhbGc..." \ -H "Content-Type: application/json" \ -d '{...}' ``` ```javascript [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 FirstCyber Quote Service */ }), }); const result = await response.json(); ``` ```python [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 FirstCyber Quote Service }, ) result = response.json() ``` ::: ## Key Management Best Practices - **Rotate credentials regularly**: Schedule quarterly rotations as part of your security hygiene - **Secure storage**: Never commit credentials to source control or store them in client-side code - **Monitor usage**: Review credential activity and immediately revoke any compromised credentials - **Document ownership**: Keep an internal record of which services use which credentials - **Deactivate unused clients**: Keep your credential list clean by deactivating clients no longer in use ## Password Management ### Reset Password If you forget your password: 1. Click **Forgot Password** on the login page 2. Enter your email address 3. Check your inbox for a password reset link 4. Click the link and enter a new password 5. Your password is updated immediately ## Account Status Your partner account can have these statuses: - **Pending**: Registration submitted, awaiting approval - **Active**: Account approved and operational - **Denied**: Registration denied - **Disabled**: Account temporarily disabled Only **active** accounts can authenticate and use K2 APIs. ## User Roles The Partner Portal supports two roles: - **Partner**: Standard access to view and manage keys - **Admin**: Elevated access with additional privileges for team management Contact K2 support if you need to adjust user roles or permissions. ## Support & Troubleshooting ### Common Issues **Can't log in after registration** - Check your email for an approval notification - Verify your account status is **active** - Ensure you're using the correct email and password **Lost my integration key or M2M client secret** - Credentials cannot be retrieved after creation - Generate a new credential and update your application - Deactivate or revoke the lost credential for security **Partner registration is pending approval** - Registration requests are reviewed by the K2 team - You'll receive an email notification when approved - Contact support if your request has been pending more than 2 business days ### Contact Support For assistance: - **Email**: [ray@k2cyber.ai](mailto:ray@k2cyber.ai) - **Include**: Your partner ID, email address, and detailed description of the issue - **Response time**: Support responds during business hours (9am-6pm ET, Monday-Friday) ## Getting Help - **Documentation**: Review endpoint documentation in this portal - **API Specs**: See [Downloads](/resources/downloads) for OpenAPI specifications and Postman collections, or: - [FirstCyber OpenAPI Spec](/specs/firstcyber-quote.yaml) - [FirstCyber Postman Collection](/specs/K2-Cyber-Quote-Service.postman_collection.json) - **Status Page**: Check `status.k2cyber.co` for service health ## Security & Compliance The Partner Portal implements: - **Encrypted storage**: All credentials are hashed and encrypted at rest - **HTTPS enforcement**: All traffic uses TLS 1.2 or higher - **Session management**: Sessions expire after inactivity - **Audit logging**: All key operations are logged for compliance Your use of the Partner Portal is subject to K2's Terms of Service and Privacy Policy. --- Interactive OpenAPI reference blocks are omitted from this markdown export. Use the [OpenAPI Spec (YAML)](/specs/firstcyber-quote.yaml) for schemas, parameters, and code generation. --- # Webhook Management Portal Source: /services/webhook-portal.md # Webhook Management Portal A web-based portal for managing webhook endpoints, subscribing to event notifications, rotating signing secrets, and monitoring delivery health. ## Environments | Environment | Webhook Portal URL | Partner Portal URL | |-------------|-------------------|-------------------| | Sandbox | `https://webhook-portal-sandbox.k2cyber.co` | `https://partners-sandbox.k2cyber.co` | | Production | `https://webhook-portal.k2cyber.co` | `https://partners.k2cyber.co` | ::: tip Sandbox First Start your webhook integration in the sandbox environment. Test your endpoint signature verification and event handling before moving to production. ::: ## Overview The Webhook Management Portal provides a user-friendly interface for: - Creating and managing HTTPS webhook endpoints (each with its own signing secret) - Subscribing to specific event types - Viewing delivery logs and monitoring endpoint health - Rotating endpoint signing secrets for security ## Getting Started ### Access Methods There are two ways to access the Webhook Portal: #### Option 1: Partner Admin Login (Recommended) If you're already a registered Partner Portal user: 1. Navigate to the Webhook Portal URL 2. Click **Sign In** 3. Enter your Partner Portal email and password 4. Your account is automatically provisioned for webhook access This is the recommended approach for partner administrators who already have Partner Portal credentials. #### Option 2: Register with a Webhook Key For team members who need webhook access but don't have Partner Portal accounts: 1. Obtain a **Webhook Key** from your partner administrator (created in the [Partner Portal](/services/partner-portal)) 2. Navigate to the Webhook Portal URL 3. Click **Need an account? Register** 4. Enter your email, password, and the webhook key 5. Complete registration The webhook key associates your account with your organization's partner account. ::: info Webhook Keys vs Signing Secrets The **Webhook Key** from the Partner Portal is only used for registration. The **signing secrets** used to verify webhook deliveries are generated separately when you create an endpoint in this portal. ::: ## Managing Endpoints ### Create an Endpoint 1. Navigate to **Endpoints** in the dashboard 2. Click **Add Endpoint** 3. Enter your webhook URL (must be HTTPS) 4. Optionally add a description for reference 5. Click **Create Endpoint** ::: warning Signing Secret - One-Time Display When you create an endpoint, a **signing secret** is generated and displayed **once**. This is the secret you'll use to verify webhook signatures in your application. Copy and store it securely immediately—it cannot be retrieved later. ::: After creating an endpoint, you'll be prompted to subscribe to events immediately, or you can skip and add subscriptions later. ### Endpoint Requirements - Must use HTTPS (no HTTP or self-signed certificates in production) - Must be publicly accessible - Should respond with HTTP 2xx status codes within **8 seconds** - Must validate webhook signatures (see Signature Verification below) ### Subscribe to Events After creating an endpoint, subscribe to the events you want to receive: 1. Click **Add Events** or **Manage Subscriptions** from the endpoint actions menu 2. Select from available event types 3. Click **Subscribe** #### Available Event Types ::: warning Beta Event Types Only three event types are currently available. More event types will be added soon, and event names and payloads are subject to change during the beta period. ::: | Event Type | Description | |------------|-------------| | `quote.created` | Triggered when a new quote is created | | `quote.proposalReady` | Triggered when a quote proposal PDF is ready to download | | `policy.created` | Triggered when a new policy is created | | `broker.added` | Triggered when a new broker is added to the system | You can subscribe to multiple events on a single endpoint. ## Event Topics Catalog The Webhook Portal dashboard features an **Event Topics Catalog** that displays: - All available event types grouped by category - Detailed descriptions for each event - Example payloads you can copy for testing Use this catalog to understand the structure of webhook payloads before implementing your receiver. ## Webhook Delivery Format ### HTTP Headers Each webhook delivery includes these headers: | Header | Description | |--------|-------------| | `Content-Type` | Always `application/json` | | `Event-ID` | Unique event identifier for idempotency | | `Timestamp` | ISO 8601 timestamp of the delivery attempt | | `X-Platform-Signature` | HMAC-SHA256 signature for verification | ### Sample Payloads #### quote.created ```json { "type": "quote.created", "timestamp": "2025-10-21T15:02:31Z", "brokerageId": "your-brokerage-id", "data": { "quoteId": "QTE-10293", "partnerId": "your-partner-id", "customer": { "name": "Jane Doe", "email": "jane.doe@email.com" }, "product": "Cyber Insurance", "requestedDate": "2025-10-21", "status": "quoted" } } ``` #### quote.proposalReady Fired when the rated quote proposal PDF has finished generating and is ready to be retrieved via [`GET /document/quote-proposal/stream/{id}`](/services/firstcyber-quote-service#get-document-quote-proposal-stream-id). ```json { "type": "quote.proposalReady", "timestamp": "2025-10-21T15:02:31Z", "brokerageId": "your-brokerage-id", "data": { "quoteId": "QTE-10293", "partnerId": "your-partner-id" } } ``` #### policy.created ```json { "type": "policy.created", "timestamp": "2025-10-21T15:02:31Z", "brokerageId": "your-brokerage-id", "data": { "policyId": "POL-10293", "partnerId": "your-partner-id", "customer": { "name": "Jane Doe", "email": "jane.doe@email.com" }, "product": "Cyber Insurance", "effectiveDate": "2025-11-01", "status": "active" } } ``` #### broker.added ```json { "type": "broker.added", "timestamp": "2025-10-21T15:02:31Z", "brokerageId": "your-brokerage-id", "data": { "brokerId": "BRK-78945", "brokerageName": "Acme Insurance Brokers", "contact": { "name": "John Smith", "email": "john.smith@acme-insurance.example" }, "licensedStates": ["CA", "NY", "TX"], "addedDate": "2025-10-21" } } ``` ## Signature Verification Verify the `X-Platform-Signature` header to ensure webhooks are genuinely from K2. Use the **signing secret** that was generated when you created the endpoint. ### Signature Format The signature is computed as: ``` HMAC-SHA256(secret, "${Event-ID}.${Timestamp}.${RequestBody}") ``` The signature input is the concatenation of: 1. The `Event-ID` header value 2. A period (`.`) 3. The `Timestamp` header value 4. A period (`.`) 5. The raw request body (JSON string) ### Verification Example ::: code-group ```javascript [JavaScript] const crypto = require('crypto'); function verifyWebhookSignature(req, secret) { const eventId = req.headers['event-id']; const timestamp = req.headers['timestamp']; const signature = req.headers['x-platform-signature']; const body = JSON.stringify(req.body); // Reconstruct the signed payload const signedPayload = `${eventId}.${timestamp}.${body}`; // Compute expected signature const expectedSignature = crypto .createHmac('sha256', secret) .update(signedPayload) .digest('hex'); // Use timing-safe comparison return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expectedSignature) ); } // Express.js example const ENDPOINT_SIGNING_SECRET = process.env.WEBHOOK_SIGNING_SECRET; app.post('/webhooks', express.json(), (req, res) => { if (!verifyWebhookSignature(req, ENDPOINT_SIGNING_SECRET)) { console.error('Invalid webhook signature'); return res.status(401).send('Invalid signature'); } // Signature valid - process the webhook const eventId = req.headers['event-id']; const eventType = req.body.type; console.log(`Received ${eventType} event: ${eventId}`); // Acknowledge receipt immediately res.status(200).send('OK'); // Process asynchronously if needed... }); ``` ```python [Python] import hmac import hashlib import os from flask import Flask, request, jsonify app = Flask(__name__) ENDPOINT_SIGNING_SECRET = os.environ.get('WEBHOOK_SIGNING_SECRET') def verify_webhook_signature(request): event_id = request.headers.get('Event-ID') timestamp = request.headers.get('Timestamp') signature = request.headers.get('X-Platform-Signature') body = request.get_data(as_text=True) # Reconstruct the signed payload signed_payload = f"{event_id}.{timestamp}.{body}" # Compute expected signature expected_signature = hmac.new( ENDPOINT_SIGNING_SECRET.encode(), signed_payload.encode(), hashlib.sha256 ).hexdigest() # Use timing-safe comparison return hmac.compare_digest(signature, expected_signature) @app.route('/webhooks', methods=['POST']) def handle_webhook(): if not verify_webhook_signature(request): return jsonify({'error': 'Invalid signature'}), 401 # Process the webhook event_type = request.json.get('type') event_id = request.headers.get('Event-ID') print(f"Received {event_type} event: {event_id}") return jsonify({'status': 'ok'}), 200 ``` ::: ## Delivery & Retry Logic ### Timeout K2 expects a **2xx HTTP response** within **8 seconds**. If your endpoint takes longer to respond, the delivery will be marked as failed and retried. ### Retry Behavior Failed deliveries are retried with **exponential backoff**: | Attempt | Delay Before Retry | |---------|-------------------| | 1 | Immediate | | 2 | 1 second | | 3 | 2 seconds | | 4 | 4 seconds | | 5 | 8 seconds | | 6 | 16 seconds | | 7 | 32 seconds | | 8+ | 60 seconds (max) | - Maximum of **10 delivery attempts** per event - After 10 failed attempts, the delivery is marked as permanently failed ### Circuit Breaker To protect both your endpoint and our system, K2 implements a circuit breaker: - If an endpoint fails **5 times within 5 minutes**, it is automatically **disabled** - Disabled endpoints stop receiving webhook deliveries - You can re-enable endpoints from the Webhook Portal after resolving the issue ::: warning Endpoint Disabled If your endpoint is consistently failing, check the delivery logs for error details. Common issues include: - Endpoint not responding within 8 seconds - Endpoint returning non-2xx status codes - Network connectivity issues - Invalid SSL certificates ::: ## Monitoring Delivery Logs The **Logs** page shows: - Recent delivery attempts for all endpoints - Success/failure status - HTTP response codes and response times - Retry attempt counts - Event payloads for debugging Use the `Event-ID` header value to track specific deliveries and implement idempotency in your system. ## Rotating Endpoint Signing Secrets To rotate an endpoint's signing secret: 1. Navigate to the endpoint in your Endpoints list 2. Click the actions menu (⋮) and select **Rotate Secret** 3. Click **Rotate Secret** to confirm 4. Copy the new signing secret immediately 5. Update your application's signature verification with the new secret ::: tip Grace Period When you rotate a secret, both the old and new secrets are valid for a short grace period. Update your application to accept the new secret during this period, then remove the old secret configuration. ::: ## Managing Subscriptions From the endpoint details or actions menu: - **Add Events**: Subscribe to new event types - **Manage Subscriptions**: View and manage current subscriptions - **Remove Subscription**: Unsubscribe from an event type by clicking the delete icon ## Deleting Endpoints To remove an endpoint: 1. Navigate to the Endpoints page 2. Click the actions menu (⋮) for the endpoint 3. Select **Delete Endpoint** ::: danger Permanent Deletion Deleting an endpoint removes all subscriptions and delivery history. This action cannot be undone. ::: ## Best Practices ### Security - Store signing secrets securely (use environment variables or secrets management) - Always verify webhook signatures before processing - Never commit secrets to version control - Rotate secrets quarterly or after team changes ### Reliability - Implement idempotency using the `Event-ID` header to handle duplicate deliveries - Respond with 2xx **immediately** (within 8 seconds), then process asynchronously - Log all webhook deliveries for debugging and auditing - Use a queue or background job processor for time-consuming operations ### Handling Failures - Monitor for circuit breaker events (endpoint disabled notifications) - Set up alerts for repeated delivery failures - Check delivery logs regularly for error patterns - Test endpoints after deploying changes ### Idempotency Webhooks may be delivered more than once due to retries. Use the `Event-ID` header to ensure you only process each event once: ::: code-group ```javascript [JavaScript] const processedEvents = new Set(); // Use Redis/database in production app.post('/webhooks', (req, res) => { const eventId = req.headers['event-id']; // Check if already processed if (processedEvents.has(eventId)) { console.log(`Event ${eventId} already processed, skipping`); return res.status(200).send('OK'); } // Mark as processed processedEvents.add(eventId); // Process the event... res.status(200).send('OK'); }); ``` ```python [Python] processed_events = set() # Use Redis/database in production @app.route('/webhooks', methods=['POST']) def handle_webhook(): event_id = request.headers.get('Event-ID') if event_id in processed_events: print(f"Event {event_id} already processed, skipping") return jsonify({'status': 'ok'}), 200 processed_events.add(event_id) # Process the event... return jsonify({'status': 'ok'}), 200 ``` ::: ## Support If you encounter issues with webhook delivery: 1. Check the **Logs** page for error details 2. Verify your endpoint is accessible and responding within 8 seconds 3. Confirm you're validating signatures correctly (including Event-ID and Timestamp in the signature) 4. Check if your endpoint was disabled by the circuit breaker 5. Contact support at [ray@k2cyber.ai](mailto:ray@k2cyber.ai) with the Event-ID --- Interactive OpenAPI reference blocks are omitted from this markdown export. Use the [OpenAPI Spec (YAML)](/specs/firstcyber-quote.yaml) for schemas, parameters, and code generation. --- # Quote Status Flow Source: /reference/quote-status-flow.md # Quote Status Flow ### Status descriptions | Status | Description | | -------------------- | ---------------------------------------------------------------------------- | | `pending` | Quote is being processed or awaiting review. Transient status. | | `approved_rate` | Quote has been approved with a rate, but broker is not onboarded to K2 Cyber | | `ready_to_bind` | Quote is ready to be bound | | `bound` | Quote has been bound. Transient status. | | `issued` | Policy has been successfully issued | | `declined` | Quote was declined by underwriting | | `underwriter_review` | Quote requires manual underwriter review | | `failed` | Quote processing failed due to an error | ### Happy Path
pending ready_to_bind bound issued
Straight-through processing moves through `pending` → `ready_to_bind` → `bound` → `issued`. Bind returns `202 Accepted` immediately; `issued` arrives asynchronously when the policy packet is ready (or when [`policy.created`](/services/webhook-portal#policy-created) fires). ### Alternate outcomes (from pending)
underwriter_review

Manual review required. May later become ready_to_bind or declined.

approved_rate

Rated successfully, but the broker is not onboarded. Cannot bind until onboarded.

declined

Does not meet underwriting guidelines. Terminal.

failed

Processing error. Terminal.

### Document availability by status | Status | Quote Preview | Quote Proposal | Policy Preview | Policy Document | | --------------- | ------------- | -------------- | -------------- | --------------- | | `pending` | — | — | — | — | | `ready_to_bind` | Yes | Yes | Yes | — | | `issued` | Yes | Yes | — | Yes | Endpoint details: - [`GET /document/quote-preview/stream/{id}`](/services/firstcyber-quote-service#get-document-quote-preview-stream-id) - [`GET /document/quote-proposal/stream/{id}`](/services/firstcyber-quote-service#get-document-quote-proposal-stream-id) - [`GET /document/policy-preview/stream/{id}`](/services/firstcyber-quote-service#get-document-policy-preview-stream-id) - [`GET /document/policy/stream/{id}`](/services/firstcyber-quote-service#get-document-policy-stream-id) Webhook signals that often replace polling: - [`quote.proposalReady`](/services/webhook-portal#quote-proposalready) — proposal PDF ready - [`policy.created`](/services/webhook-portal#policy-created) — issued policy packet ready --- Interactive OpenAPI reference blocks are omitted from this markdown export. Use the [OpenAPI Spec (YAML)](/specs/firstcyber-quote.yaml) for schemas, parameters, and code generation. --- # Downloads Source: /resources/downloads.md # Downloads API specifications, Postman collections, and AI-ready markdown exports for K2 Cyber partner integrations. ## FirstCyber Quote Service | Resource | Description | |----------|-------------| | [OpenAPI Spec (YAML)](/specs/firstcyber-quote.yaml) | OpenAPI 3.1 specification for code generation and API clients | | [Postman Collection](/specs/K2-Cyber-Quote-Service.postman_collection.json) | Pre-configured Postman collection with example requests | ::: info 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 `access_token` variable with your OAuth token 4. Start testing endpoints immediately ::: ## Markdown exports Each documentation page is also available as markdown for use with AI coding tools (Cursor, Claude, Copilot, and similar). Use **Copy as Markdown** in the page outline, or open a page’s `.md` twin directly (for example `/services/firstcyber-quote-service.md`). | Index | Description | |-------|-------------| | [/llms.txt](/llms.txt) | Index of documentation pages with links to each `.md` export | | [/llms-full.txt](/llms-full.txt) | Concatenated markdown of all documentation pages | --- Interactive OpenAPI reference blocks are omitted from this markdown export. Use the [OpenAPI Spec (YAML)](/specs/firstcyber-quote.yaml) for schemas, parameters, and code generation. --- # Changelog Source: /changelog.md # Changelog The changelog highlights notable updates to customer-facing APIs and portals. ## 2026-07-28 (v1.15.0) ### Improved - **Documentation** – Site-wide navigation and layout updates ## 2026-06-30 (v1.14.2) ### New - **FirstCyber Quote API** – New `GET /quote/firstcyber/document/policy-preview/stream/:id` endpoint - Retrieve the pre-issuance policy preview document as a PDF byte stream. - Available at the same time `quote.proposalReady` event is published. ## 2026-05-19 (v1.14.0) ### New - **FirstCyber Quote API** – New `GET /quote/firstcyber/document/quote-proposal/stream/:id` endpoint - Retrieve the rated quote proposal document as a PDF byte stream. - Available once the quote has finished rating (`approved_rate`, `ready_to_bind`, `underwriter_review`, or `issued`). - Updated OpenAPI spec, documentation, and Postman collection with the new endpoint. - **Webhook Portal** – New `quote.proposalReady` webhook event - Fires when the rated quote proposal PDF has finished generating and is ready to retrieve via [`GET /quote/firstcyber/document/quote-proposal/stream/:id`](/services/firstcyber-quote-service#get-document-quote-proposal-stream-id). - Subscribe in the [Webhook Portal](/services/webhook-portal#available-event-types) and see the sample payload in the [Webhook Portal documentation](/services/webhook-portal#quote-proposalready). ### Improved - **FirstCyber Quote API** – `year_founded` is now optional on `POST /quote/firstcyber/submit` ## 2026-04-08 (v1.13.0) ### New - **Quick Start** – Updated onboarding guide with product type selection guidance, sandbox reference table, and links to the [Appetite Checker](https://appetite-test.k2cyber.co/) - New "Choose Your Product Type" section explains Broker Bill vs. Direct Bill and sandbox binding behavior - New "Check Eligibility" section links to the Appetite Checker for NAICS/revenue lookups - New "Sandbox Reference" table consolidates sandbox-specific notes in one place - Added webhook setup tip in the "Develop Your Integration" section ### Improved - **Changelog** – Standardized release notes format across all entries - Consistent category headers: New, Improved, Fixed, Breaking Changes - Examples and migration guidance now displayed in distinct callout blocks ## 2026-04-02 (v1.12.1) ### New - **FirstCyber Quote API** – `message` field added to quote response - `POST /quote/firstcyber/submit` response now includes an optional `message` field in the `data` object. ::: info Context Provides additional customer-facing information for successful quotes, if needed. For instance, if a submission includes a retention lower than the minimum, the response will indicate the adjusted value. ::: ::: details Example If a submission has a $500 retention but the minimum is $5,000, the `message` field will return: *"Please note that the minimum retention is $5,000 based on the information provided."* ::: ## 2026-02-18 (v1.11.0) ### New - **FirstCyber Quote API** – `reputational_harm_limit` field added to coverage details - `POST /quote/firstcyber/submit` and `GET /quote/firstcyber/status/:id` responses now include optional `reputational_harm_limit` in the `coverage_details` object - String value (e.g. `"$25,000"`) representing the reputational harm coverage limit - Updated OpenAPI spec and documentation ## 2026-02-11 (v1.10.0) ### New - **FirstCyber Quote API** – `policy_fee` field added to quote response - `POST /quote/firstcyber/submit` and `GET /quote/firstcyber/status/:id` responses now include `policy_fee` in the `policy_term` object - String value (e.g. `"$300.00"`) representing the policy fee amount - Updated OpenAPI spec and documentation ## 2026-01-29 (v1.9.0) ### New - **FirstCyber Quote API** – New `GET /quote/firstcyber/document/policy/stream/:id` endpoint - Get a policy document after a quote is issued. - Updated documentation and postman document with new endpoint. ## 2026-01-20 (v1.8.0) ### Breaking Changes - **FirstCyber Quote API** – `GET /quote/firstcyber/status/:id` — The `quote_status` field has new values - **Status replacements**: - `published` → replaced by `ready_to_bind` and `approved_rate` - `pendingBindAndIssue` → replaced by `bound` - **New status flow**: `pending` → `ready_to_bind` → `bound` → `issued` - **Additional new statuses**: `declined`, `underwriter_review` - **Retained statuses**: `pending`, `issued`, `failed` ::: info Migration Guidance For minimum changes to maintain the same functionality: - Replace `published` with `ready_to_bind` in your code - Replace `pendingBindAndIssue` with `bound` in your code ::: - **FirstCyber Quote API** – `PUT /quote/firstcyber/bind/:id` — Response structure has been simplified - Now returns a 202 and "accepted" status - An event will be emitted with the policy ID once bind completes ::: details Example Response ```json { "status": "accepted", "data": { "id": "afe35e49-f801-4d4a-963d-0a17a3e4833d" } } ``` ::: ### New - **API Downloads Section** – Downloadable OpenAPI specs and Postman collections added to documentation - FirstCyber Quote Service: OpenAPI spec (YAML) and Postman collection with pre-configured requests - PersonalCyber Quote Service: OpenAPI spec (YAML) - Download links added to each service documentation page - Partner Portal "Getting Help" section updated with direct download links - Postman collection includes example requests, auto-token saving, and collection variables ## 2025-12-12 (v1.7.0) ### New - **PostHog Analytics** – Production-ready PostHog analytics tracking - Client-side analytics initialization with SSR-safe implementation - Automatic pageview tracking on initial load and VitePress route navigation - Session recording with privacy-first configuration (all text and inputs masked) - Autocapture enabled for automatic event tracking - Analytics disabled in development by default (can be enabled via `VITE_POSTHOG_KEY`) - Graceful degradation when PostHog key is not configured - Environment variables: `VITE_POSTHOG_KEY` and `VITE_POSTHOG_HOST` (optional, set at build time) ## 2025-12-11 (v1.6.0) ### Breaking Changes - **Webhook Portal** – Corrected webhook header names and signature verification - `Event-ID` (not `X-K2-Event-Id`) - `Timestamp` (not `X-K2-Delivery-Timestamp`) - `X-Platform-Signature` (not `X-K2-Signature`) - Signature is computed over `${Event-ID}.${Timestamp}.${RequestBody}`, not just the body ### Fixed - **Webhook Portal** – Corrected timeout and retry logic - **Corrected timeout**: 8 seconds (not 30 seconds) - **Corrected retry logic**: Exponential backoff (1s, 2s, 4s... up to 60s), max 10 attempts - **Added circuit breaker documentation**: Endpoints disabled after 5 failures in 5 minutes ### Improved - **Partner Portal Documentation** – Comprehensive rewrite based on current implementation - Clarified credential management workflow (Integration Keys, M2M Clients, Webhook Keys) - **Important clarification**: Webhook Keys are registration tokens for Webhook Portal access only—they are NOT signing secrets - Updated authentication flow examples with correct endpoints - Added detailed explanations for each credential type and its purpose - Improved troubleshooting section with common issues - **Webhook Portal Documentation** – Major update to reflect actual implementation - Documented two access methods: Partner Admin Login (recommended) and Webhook Key Registration - **Important clarification**: Signing secrets for webhook verification are generated per-endpoint, not from Partner Portal webhook keys - Updated event types to accurately reflect the 3 currently available events - Added complete code examples for Node.js and Python signature verification - Added idempotency implementation example - **Quick Start Guide** – Added Partner Portal URLs to environment table ## 2025-12-11 ### Breaking Changes - **FirstCyber Quote API** – An Integration key in the `x-integration-key` header is now REQUIRED when using endpoints within the quote service - Sandbox and Production Integration keys (as well as m2m clients to get OAuth2.0 Bearer Tokens) can be generated @ https://partners-sandbox.k2cyber.co and https://partners.k2cyber.co respectively. ### Improved - Updated quick start and partner portal documentation to reflect current implementation around credential management and usage. ## 2025-12-08 ### New - **Sandbox Environment** – The sandbox environment is now live for integration development and testing - API Sandbox: `https://api-sandbox.k2cyber.co` - Partner Portal Sandbox: `https://partners-sandbox.k2cyber.co` - Webhook Portal Sandbox: `https://webhook-portal-sandbox.k2cyber.co` - Isolated synthetic data for safe testing - Updated documentation across Quick Start, FirstCyber Quote Service, Personal Cyber Quote Service, Partner Portal, and Webhook Portal - OpenAPI specs now default to sandbox URLs for "Try it out" functionality ## 2025-11-17 ### New - **FirstCyber Quote API** – `GET /document/quote-preview/:id` endpoint added to firstcyber-quote.yaml and corresponding documentation ## 2025-11-06 ### New - **Partner Portal** – M2M (Machine-to-Machine) Client documentation - New authentication method using Stytch for API access - Includes setup instructions, usage examples, and comparison with Integration Keys - M2M clients automatically provisioned with `quote` scope - Support for activate/deactivate operations ## 2025-10-23 ### Improved - **PersonalCyber Quote Service** – Temporarily disabled from navigation (content preserved for future re-enablement) ## 2025-10-22 ### Fixed - **Partner Portal** – Corrected Partner Portal URL ## 2025-10-21 ### Improved - **PersonalCyber Quote Service** – Updated documentation to match FirstCyber format - Removed test environment URL (api-test.k2cyber.co) in favor of "Sandbox Environment - Coming Soon" message - Consistent messaging across all quote service documentation ## 2025-10-17 ### Improved - **FirstCyber Quote API** – `POST /quote/firstcyber/submit`: taxid is no longer required to create a quote. ## 2025-02-15 ### New - **FirstCyber Quote API** – Admin quote endpoint documentation and sample payloads ### Improved - **Webhook Portal** – Improved insight panels with retry counts ## 2025-01-12 ### New - **Partner Portal** – Integration key rotation workflow and deployment-ready Dockerfile ## 2024-12-03 ### New - Initial publication of the customer documentation portal. --- Interactive OpenAPI reference blocks are omitted from this markdown export. Use the [OpenAPI Spec (YAML)](/specs/firstcyber-quote.yaml) for schemas, parameters, and code generation.