# Quantum Partner API — Full Documentation > Generated 2026-06-24 from the partner documentation guides and the OpenAPI spec (https://docs.quantumlends.com/merged-api.json). > For the compact index version see https://docs.quantumlends.com/llms.txt. ## Table of Contents - Quantum Partner API Documentation - Getting Started - Prefill Applications - Embedded Contract Signing - Sandbox Environment - Submit Application API Migration Guide - Mutual TLS Channel Authentication - Webhook Notifications Overview - Webhook Integration Guide - API Reference (structured summary) --- # Quantum Partner API Documentation The Quantum Partner API is a RESTful API that enables you to integrate lending capabilities into your platform. The API provides: - **Application Lifecycle Management** — Submit loan applications, upload supporting documents, retrieve and accept offers, and track applications through underwriting to funding - **Account Management** — Manage booked accounts including requesting draws from lines of credit, processing payments, and viewing transaction history - **Webhooks** — Receive real-time notifications when application status changes, documents are processed, offers are created, and more - **[Getting Started](/getting-started.html)** — Learn how to integrate with the Quantum API, from authentication to your first application - **[Sandbox Environment](/sandbox-environment.html)** — Use magic values to trigger specific outcomes and test your integration end-to-end - **[Prefill Applications](/prefill-applications.html)** — Pre-populate loan applications with known data to streamline the applicant experience - **[Webhooks Overview](/webhooks-overview.html)** — Understand webhook concepts, event types, and the application lifecycle - **[Webhooks Integration Guide](/webhooks-guide.html)** — Technical guide for implementing webhooks with code examples and API reference - **[Migration Guide](/migration-guide.html)** — Upgrade from v2 to v3 of the Quantum Partner API with minimal disruption --- # Getting Started Follow these steps to integrate with the Quantum Partner API: ## Authentication Authentication is handled by a Bearer token. The token is passed in the `Authorization` header as a Bearer token. ```bash Authorization: Bearer ``` ### Obtain API Credentials 1. Contact your Quantum representative 2. You'll receive a unique Bearer token for your organization 3. Include this token in all API requests > **Warning:** Keep your API token secure! Never expose it in client-side code or public repositories. Enterprise partners with mutual-authentication requirements can additionally enroll in [mutual TLS](/mtls.html) for transport-layer authentication on top of the Bearer token. This is optional and most integrations do not need it. ## Submit an Application Use the `POST /api/v3/applications/submit` endpoint to submit a loan application on behalf of your customer. ### Required Fields **Business Information (`business`)** - `legal_name` - The legal business name - `address` - Business address (street, city, state, zip_code) - `phone_number` - Business phone number - `entity_type` - Business structure (e.g., `sole_proprietorship`, `partnership`, `corporation`, `limited_liability_company`) - `start_date` - When the business was established (YYYY-MM-DD) - `naics_code` - Industry classification code - `doing_business_as` (optional) - DBA name if different from legal name **Loan Request (`loan_request`)** - `amount` - Requested loan amount in dollars - `purpose` - Loan purpose (e.g., `working_capital`, `equipment`, `inventory`, `expansion`) - `authorization` - Must be `true` to authorize the credit check **Owner Information (`owners`)** At least one owner is required. Total ownership must equal 100%. - `first_name`, `last_name` - Owner's legal name - `email`, `phone_number` - Contact information - `date_of_birth` - Date of birth (YYYY-MM-DD) - `ssn` - Social Security Number - `ownership_percent` - Percentage of business owned - `is_applicant` - Set to `true` for the primary applicant - `address` - Owner's home address **Additional Questions (`additional_questions`)** - `annual_sales` - Business annual revenue in dollars ### Example Request ```javascript const response = await fetch('https://api.quantum.com/api/v3/applications/submit', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ business: { legal_name: "Acme Inc LLC", doing_business_as: "Widgets Are Us", address: { street: "22 Acacia Ave", city: "Los Angeles", state: "CA", zip_code: "90025" }, phone_number: "650-555-6124", entity_type: "limited_liability_company", naics_code: "312234", start_date: "2008-03-27" }, loan_request: { amount: 50000, purpose: "working_capital", authorization: true }, owners: [{ first_name: "John", last_name: "Doe", email: "john@acmeinc.com", phone_number: "650-555-2134", date_of_birth: "1981-03-17", is_applicant: true, ownership_percent: 100, ssn: "123-45-6789", address: { street: "123 Main St", city: "Los Angeles", state: "CA", zip_code: "90025" } }], additional_questions: { annual_sales: 250000 } }) }); const application = await response.json(); console.log('Application ID:', application.id); ``` > **Note:** Save the `application.id` from the response — you'll need it for uploading documents, checking status, and all subsequent operations. For the complete schema and all available fields, see the [API Reference](/api-reference.html). ## Upload Documents After submitting an application, upload supporting documents using `POST /api/v3/applications/{app_id}/documents`. ### Example Request ```javascript const applicationId = 'your-application-id'; // File objects from an element or other source const bankStatementFile1 = fileInput.files[0]; const bankStatementFile2 = fileInput.files[1]; const driversLicenseFile = photoIdInput.files[0]; // Create FormData with documents const formData = new FormData(); formData.append('bank_statements', bankStatementFile1); formData.append('bank_statements', bankStatementFile2); // Multiple files supported formData.append('photo_id', driversLicenseFile); const uploadResponse = await fetch(`https://api.quantum.com/api/v3/applications/${applicationId}/documents`, { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_TOKEN' // Note: Don't set Content-Type header - browser sets it automatically with boundary }, body: formData }); const documents = await uploadResponse.json(); console.log('Uploaded documents:', documents); ``` > **Note:** You can upload multiple files of the same type by appending them with the same field name. At least one file must be provided. > **Warning:** **File size limits:** Individual files must be under 20MB, and the total request size must not exceed 40MB. **Document Types:** | Type | Description | |------|-------------| | `bank_statements` | Bank statements for the primary business operating account | | `business_tax_return` | Business tax returns (typically last 2 years) | | `personal_tax_return` | Personal tax returns for business owners | | `ytd_financials` | Year-to-date financial statements | | `ye_financials` | Year-end financial statements | | `debt_schedule` | Schedule of existing business debts and obligations | | `photo_id` | Government-issued photo identification (driver's license, passport, etc.) | | `utility_bill` | Utility bill for address verification | | `lease_agreement` | Commercial lease or rental agreement | | `business_license` | Business license or permit | | `sos_record` | Secretary of State registration documents | | `lien_release` | Lien release documentation | | `ssn_card` | Social Security card | | `void_check` | Voided check for bank account verification | | `insurance_document` | Business insurance documentation | | `mortgage_document` | Mortgage or property documentation | | `personal_document` | Other personal documentation | | `formation_documents` | Business formation documents (articles of incorporation, operating agreement, etc.) | | `unknown` | Other/miscellaneous documents | ## Add Bank Accounts Bank accounts can be provided during the initial application submission (in the `bank_accounts` field of the `/submit` payload) or added afterward using the dedicated endpoint. There are two types of bank accounts: - **Aggregator accounts** (Plaid/MX) — Used for bank statement analysis. Connected via a bank aggregation provider. - **Direct-from-customer accounts** — Used for loan disbursement. The customer provides their account and routing numbers directly. ### Add Bank Accounts After Submission Use `POST /api/v3/applications/{app_id}/bank-accounts` to add bank accounts to an existing application. #### Example: Aggregator Account (Plaid) ```javascript const applicationId = 'your-application-id'; const response = await fetch(`https://api.quantum.com/api/v3/applications/${applicationId}/bank-accounts`, { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify([ { source: "plaid_link", aggregator: { provider: "plaid", access_token: "access-sandbox-xxx", primary_account_id: "account-123", transactions: true, balances: true, identities: true } } ]) }); ``` #### Example: Direct-from-Customer Account ```javascript const response = await fetch(`https://api.quantum.com/api/v3/applications/${applicationId}/bank-accounts`, { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify([ { source: "direct_from_customer", direct_from_customer: { account_number: "1234567890", routing_number: "021000021" } } ]) }); ``` > **Note:** You can include both account types in a single request. Aggregator accounts are used for bank statement analysis, while direct-from-customer accounts are used for loan disbursement. > **Warning:** Only one `direct_from_customer` account is allowed per request. The `account_number` must be 4–17 digits and the `routing_number` must be exactly 9 digits. ### Include Bank Accounts at Submission You can also include bank accounts in the initial `POST /api/v3/applications/submit` request by adding a `bank_accounts` array to the payload. The format is the same as shown above. For the complete schema and all available fields, see the [API Reference](/api-reference.html). ## Monitor Application Status Use `GET /api/v3/applications/{app_id}/status` to check the application status as it moves through underwriting. This lightweight endpoint returns only the status, timestamps, and reference ID — ideal for polling without fetching the full application payload. ### Example Request ```javascript const applicationId = 'your-application-id'; const statusResponse = await fetch(`https://api.quantum.com/api/v3/applications/${applicationId}/status`, { headers: { 'Authorization': 'Bearer YOUR_API_TOKEN' } }); const status = await statusResponse.json(); console.log('Status:', status.status); console.log('Last modified:', status.modified); ``` ### Example Response ```json { "id": "550e8400-e29b-41d4-a716-446655440001", "status": "Under Credit Review", "reference_id": "your-internal-id-12345", "created": "2024-01-15T10:30:00Z", "modified": "2024-01-16T14:45:00Z" } ``` > **Note:** Instead of polling, you can receive real-time status updates via webhooks. See the [Webhooks Overview](/webhooks-overview.html) to get started. > **Note:** Need the full application details including decision data? Use `GET /api/v3/applications/{app_id}` instead. ### Application Lifecycle Understanding the application status flow: 1. **submitted** → Application received 2. **In Processing** → Initial processing 3. **Under Credit Review** → Underwriting review 4. **Approved** → Credit decision made 5. **Awaiting Offer Acceptance** → Customer needs to accept offer 6. **Preparing Loan Documents** → Documents being prepared 7. **Awaiting Document Execution** → Customer needs to sign 8. **Awaiting Funding** → Funding in progress 9. **Booked** → Loan is active **Alternative outcomes:** - **Declined** - Application did not meet credit criteria - **Offer Declined** - Customer declined the offer - **Withdrawn** - Application withdrawn ## Review and Accept Offers When approved, retrieve offers using `GET /api/v3/applications/{app_id}/offers` and accept the best offer with `POST /api/v3/applications/{app_id}/offer/{offer_id}/accept`. ## Service the Account Once booked, use the Accounts API to manage draws and payments. --- ## Common Workflows ### Requesting a Draw from a Line of Credit Once a line of credit account is active, you can request draws: ```javascript const drawResponse = await fetch(`https://api.quantum.com/api/v3/accounts/${accountId}/draw-requests`, { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ draw_amount_in_cents: 1000000, // $10,000 reference_id: "partner-draw-12345" }) }); const drawRequest = await drawResponse.json(); // Draw is processed asynchronously - check status or wait for webhook ``` > **Note:** Draw and payment requests are processed asynchronously. You'll receive webhook notifications when the status changes. ### Making a Payment Submit payments for loan repayment or payoff: ```javascript const paymentResponse = await fetch(`https://api.quantum.com/api/v3/accounts/${accountId}/payment-requests`, { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ payment_amount_in_cents: 500000, // $5,000 request_type: "Payment Request", reference_id: "partner-payment-456" }) }); ``` --- ## Error Handling The API uses standard HTTP status codes to indicate success or failure: | Status Code | Description | |-------------|-------------| | 200 | OK - Request succeeded | | 201 | Created - Resource created successfully | | 400 | Bad Request - Invalid request parameters | | 403 | Forbidden - Insufficient permissions | | 404 | Not Found - Resource not found | | 422 | Unprocessable Entity - Validation error | | 500 | Internal Server Error | | 503 | Service Unavailable | **Error Response Format:** ```json { "error": { "code": "VALIDATION_ERROR", "message": "Some fields are missing or invalid.", "details": [ { "field": "application.business.legal_name", "message": "This field is required." } ] } } ``` > **Warning:** Always check the `error.details` array for field-specific validation errors when you receive a 422 response. --- ## Best Practices ### Use Reference IDs Include your own `reference_id` in applications, draw requests, and payment requests to track operations in your system: ```json { "reference_id": "your-internal-id-12345" } ``` ### Handle Asynchronous Operations Draw requests and payment requests are processed asynchronously. Implement webhook handlers to receive status updates rather than polling. ### Validate Before Submitting Ensure all required fields are present and properly formatted before submitting applications to avoid validation errors. ### Store Application IDs Save the `application.id` returned from the submit endpoint - you'll need it for all subsequent operations (uploading documents, checking status, etc.). --- ## Querying These Docs With AI Assistants If you're using an AI assistant (Claude, Cursor, ChatGPT with browsing, etc.) to integrate with the Quantum Partner API, point your tool at one of these machine-readable entry points instead of crawling the HTML site: - **`https://docs.quantumlends.com/llms.txt`** — Compact index following the [llms.txt convention](https://llmstxt.org/). Lists every doc and the canonical API spec with absolute URLs the assistant can follow. - **`https://docs.quantumlends.com/llms-full.txt`** — Single file containing the full body of every guide plus a structured summary of the OpenAPI spec. Useful when you want one fetch to cover the whole integration surface. - **`https://docs.quantumlends.com/merged-api.json`** — Raw OpenAPI 3.1 spec, suitable for codegen or tool definitions. Each guide page also exposes its source markdown via ``, so an assistant that lands on a guide page can fetch the source directly. The API reference page uses an `application/json` alternate that points at the OpenAPI spec. --- ## Next Steps - Review the [complete API Reference](/api-reference.html) for detailed endpoint documentation - Contact your Quantum representative for API credentials and sandbox access - Test your integration in the sandbox environment before going live --- # Prefill Applications The Prefill API allows you to start a loan application on behalf of an applicant by submitting partial data upfront. Rather than requiring the applicant to fill out the entire application from scratch, you prefill known information — such as the applicant's name, email, and business details — and provide them with a link to complete the rest. ## Table of Contents - [How It Works](#how-it-works) - [Creating a Prefilled Application](#creating-a-prefilled-application) - [The Prefill URL](#the-prefill-url) - [Token Expiration](#token-expiration) - [Retrieving a New Prefill Token](#retrieving-a-new-prefill-token) - [Webhook Events](#webhook-events) - [Error Handling](#error-handling) - [Best Practices](#best-practices) --- ## How It Works The prefill flow has four main steps: ![Diagram showing the four-step prefill API flow](/images/prefill-flow-diagram.jpg) ``` 1. YOU CALL THE PREFILL API └─→ POST /api/v3/applications/prefill └─→ Response includes prefill_url and prefill_token 2. APPLICANT RECEIVES LINK ├─→ You send the prefill_url to the applicant directly └─→ OR an email is automatically sent (if email communication is not suppressed) 3. APPLICANT COMPLETES APPLICATION └─→ Applicant opens the prefill_url └─→ Pre-populated fields are already filled in └─→ Applicant completes remaining fields and submits 4. IF TOKEN EXPIRES (applicant didn't complete in time) └─→ prefill_application.token_expired webhook fires └─→ GET /api/v3/applications/{app_id}/prefill to get a new token and deliver the new prefill_url to the applicant └─→ OR an email is automatically sent (if email communication is not suppressed) ``` > **Note:** Whether the applicant receives the link via an automatic email or directly from you depends on your affiliate configuration. If your affiliate is configured to suppress email communication, you are responsible for delivering the `prefill_url` to the applicant. --- ## Creating a Prefilled Application **POST /api/v3/applications/prefill** Send a `POST` request with the applicant and (optionally) business and loan details. Only the owner's `first_name`, `last_name`, and `email` are required — everything else is optional. ### Request Body **Owner Information (`owners`)** — Required, at least one owner. | Field | Type | Required | Description | |-------|------|----------|-------------| | `first_name` | string | Yes | Owner's first name (max 100 chars) | | `last_name` | string | Yes | Owner's last name (max 100 chars) | | `email` | string | Yes | Owner's email address | | `middle_name` | string | No | Owner's middle name | | `name_suffix` | string | No | Name suffix (e.g., "III") | | `job_title` | string | No | Owner's job title | | `phone_number` | string | No | Phone number (format: `XXX-XXX-XXXX`) | | `date_of_birth` | string | No | Date of birth (format: `YYYY-MM-DD`, must be in the past) | | `is_applicant` | boolean | No | Whether this owner is the primary applicant | | `ownership_percent` | integer | No | Ownership percentage (1–100) | | `ssn` | string | No | Social Security Number (`XXXXXXXXX` or `XXX-XX-XXXX`) | | `address` | object | No | Owner's home address | **Business Information (`business`)** — Optional. | Field | Type | Description | |-------|------|-------------| | `legal_name` | string | Legal business name (max 100 chars) | | `doing_business_as` | string | DBA name (max 100 chars) | | `entity_type` | string | Business structure (e.g., `limited_liability_company`, `sole_proprietor`, `partnership`, `s_corporation`, `c_corporation`, `other`) | | `phone_number` | string | Business phone (format: `XXX-XXX-XXXX`) | | `naics_code` | string | NAICS industry code (exactly 6 digits) | | `start_date` | string | Business start date (format: `YYYY-MM-DD`) | | `non_profit` | boolean | Whether the business is a non-profit | | `tax_identification` | string | Business TIN/EIN | | `address` | object | Business address | **Loan Request (`loan_request`)** — Optional. | Field | Type | Description | |-------|------|-------------| | `amount` | integer | Requested loan amount ($10,000–$500,000) | | `purpose` | string | Loan purpose (e.g., `working_capital`, `equipment_purchase`, `business_expansion`, `debt_refinancing`, `inventory_purchase`) | | `authorization` | boolean | Credit check authorization | **Other Fields** | Field | Type | Description | |-------|------|-------------| | `reference_id` | string | Your internal reference ID for this application (max 100 chars) | ### Example Request ```javascript const response = await fetch('https://api.quantum.com/api/v3/applications/prefill', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ owners: [{ first_name: "Jane", last_name: "Smith", email: "jane@smithenterprises.com", phone_number: "415-555-0198", date_of_birth: "1985-06-15", is_applicant: true, ownership_percent: 100 }], business: { legal_name: "Smith Enterprises LLC", entity_type: "limited_liability_company", phone_number: "415-555-0100", naics_code: "541511", start_date: "2018-01-15" }, loan_request: { amount: 75000, purpose: "working_capital" }, reference_id: "PARTNER-REF-20250001" }) }); const prefillApp = await response.json(); console.log('Prefill URL:', prefillApp.prefill_url); ``` ### Example Response (201 Created) ```json { "id": "c9cefb5c-447b-433f-a4b7-16ce14a80eac", "prefill_url": "https://quantumlends.com/ui/apply/sp/c9cefb5c-447b-433f-a4b7-16ce14a80eac?token=nHzIm8OvrkAD6Lt9NLwHjcxn4jIfUFWFJ52bXKaEwB0", "prefill_token": "nHzIm8OvrkAD6Lt9NLwHjcxn4jIfUFWFJ52bXKaEwB0", "owners": [ { "first_name": "Jane", "last_name": "Smith", "email": "jane@smithenterprises.com", "phone_number": "415-555-0198", "date_of_birth": "1985-06-15", "is_applicant": true, "ownership_percent": 100 } ], "business": { "legal_name": "Smith Enterprises LLC", "entity_type": "limited_liability_company", "phone_number": "415-555-0100", "naics_code": "541511", "start_date": "2018-01-15" }, "loan_request": { "amount": 75000, "purpose": "working_capital" }, "created": "2025-06-15 14:30:00", "modified": "2025-06-15 14:30:01", "reference_id": "PARTNER-REF-20250001" } ``` > **Note:** Save the `prefill_url` from the response — this is the link the applicant will use to continue their application. If your affiliate suppresses email communication, you are responsible for delivering this URL to the applicant. > **Note:** The response only includes fields that have values. Null or empty fields are omitted. ### Minimal Request Example Only the owner's name and email are strictly required: ```javascript const response = await fetch('https://api.quantum.com/api/v3/applications/prefill', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ owners: [{ first_name: "Jane", last_name: "Smith", email: "jane@smithenterprises.com" }] }) }); ``` --- ## The Prefill URL The `prefill_url` returned in the response is a one-time-use link that takes the applicant directly to a pre-populated application form. The URL contains an authentication token so the applicant does not need to log in. ``` https://quantumlends.com/ui/apply/sp/{application_id}?token={prefill_token} ``` ### How the Link Reaches the Applicant The delivery method depends on your affiliate configuration: | Configuration | Behavior | |---------------|----------| | **Emails not suppressed** (default) | An email is automatically sent to the applicant's email address with the prefill URL. | | **Emails suppressed** | No email is sent. You are responsible for delivering the `prefill_url` to the applicant through your own channels (email, SMS, in-app notification, etc.). | > **Warning:** If your affiliate configuration suppresses emails, make sure your integration delivers the `prefill_url` to the applicant promptly. The token embedded in the URL will expire after a period of time. --- ## Token Expiration The `prefill_token` embedded in the prefill URL has an expiration time. If the applicant attempts to open the link after the token has expired, they will not be able to access the prefilled application. When an expired token is used, a `prefill_application.token_expired` webhook event is fired regardless of your email configuration. Additionally, the system handles link renewal based on your email settings: | Configuration | What Happens on Expiration | |---------------|---------------------------| | **Emails not suppressed** | A new email is automatically sent to the applicant with a fresh link containing a new token. The `prefill_application.token_expired` webhook is also fired. | | **Emails suppressed** | The `prefill_application.token_expired` webhook is fired. No email is sent — you must retrieve a new token and deliver the updated link to the applicant. | > **Note:** The `prefill_application.token_expired` webhook is particularly important if your affiliate suppresses emails, since it is the only way to know when the applicant needs a new link. However, you can subscribe to this event for visibility into token expirations. --- ## Retrieving a New Prefill Token **GET /api/v3/applications/{app_id}/prefill** When you receive a `prefill_application.token_expired` webhook event, use this endpoint to retrieve a new `prefill_token` and `prefill_url` for the application. This is the recommended way to get a fresh link after token expiration, especially if your affiliate configuration suppresses emails. ### Path Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `app_id` | string (UUID) | Yes | The application ID from the original prefill response or the webhook event payload | ### Example Request ```javascript const appId = 'c9cefb5c-447b-433f-a4b7-16ce14a80eac'; const response = await fetch(`https://api.quantum.com/api/v3/applications/${appId}/prefill`, { method: 'GET', headers: { 'Authorization': 'Bearer YOUR_API_TOKEN' } }); const prefillData = await response.json(); console.log('New Prefill URL:', prefillData.prefill_url); ``` ### Example Response (200 OK) ```json { "id": "c9cefb5c-447b-433f-a4b7-16ce14a80eac", "prefill_url": "https://quantumlends.com/ui/apply/sp/c9cefb5c-447b-433f-a4b7-16ce14a80eac?token=xR7kP2mQvL9wN3jYhT6sA1cBfD4eG8iK0oU5rW2xZ9q", "prefill_token": "xR7kP2mQvL9wN3jYhT6sA1cBfD4eG8iK0oU5rW2xZ9q" } ``` > **Note:** Each call to this endpoint generates a new token — any previously issued tokens for the application are invalidated. Make sure to deliver the latest `prefill_url` to the applicant. ### Handling Token Expiration Flow Here's the recommended flow for handling token expiration: ``` 1. WEBHOOK RECEIVED └─→ prefill_application.token_expired event fires 2. RETRIEVE NEW TOKEN └─→ GET /api/v3/applications/{app_id}/prefill └─→ Response includes new prefill_url and prefill_token 3. DELIVER NEW LINK └─→ Send the updated prefill_url to the applicant ``` --- ## Webhook Events The Prefill API introduces two webhook event types. Subscribe to these events to track prefilled application activity. > **Note:** To subscribe to these events, see the [Webhooks Integration Guide](/webhooks-guide.html). Add `prefill_application.created` and `prefill_application.token_expired` to your subscription's event list. ### `prefill_application.created` Fired when a prefilled application is successfully created. **Payload:** | Field | Description | |-------|-------------| | `id` | The UUID of the created application | | `affiliate_id` | The affiliate ID associated with your account | | `partner_reference_id` | The `reference_id` provided in your prefill request (if any) | ### `prefill_application.token_expired` Fired when an applicant attempts to access a prefill link with an expired token. This is especially useful if your affiliate suppresses emails, since you need to know when to resend the link. **Payload:** | Field | Description | |-------|-------------| | `id` | The UUID of the application | | `affiliate_id` | The affiliate ID associated with your account | | `partner_reference_id` | The `reference_id` provided in your prefill request (if any) | > **Note:** This webhook is deduplicated — you will receive at most one notification per token within a 24-hour window, even if the applicant clicks the expired link multiple times. --- ## Error Handling ### POST /api/v3/applications/prefill | Status Code | Cause | Description | |-------------|-------|-------------| | **201** | Success | Application created. Response contains `prefill_url` and application data. | | **400** | Not configured | Your account does not have an affiliate configured for prefill. Contact your Quantum representative. | | **403** | Unauthorized | Your API credentials do not have permission for this operation. | | **422** | Validation error | One or more fields are missing or invalid. Check the `error.details` array for specifics. | ### GET /api/v3/applications/{app_id}/prefill | Status Code | Cause | Description | |-------------|-------|-------------| | **200** | Success | New token generated. Response contains `prefill_url` and `prefill_token`. | | **403** | Unauthorized | Your API credentials do not have permission for this operation. | | **404** | Not found | The application ID does not exist or does not belong to your account. | ### Example: Validation Error Response ```json { "error": { "code": "VALIDATION_ERROR", "message": "Some fields are missing or invalid", "details": [ { "field": "body.owners.0.email", "message": "value is not a valid email address" } ] } } ``` > **Warning:** If you receive a `400` error indicating that prefill is not configured, contact your Quantum representative to enable it for your account. --- ## Best Practices ### Provide As Much Data As Possible While only `first_name`, `last_name`, and `email` are required, prefilling additional fields reduces friction for the applicant and increases the likelihood of application completion. At minimum, consider including: - Owner's phone number and date of birth - Business legal name and entity type - Loan request amount and purpose ### Use Reference IDs Include a `reference_id` to correlate prefilled applications with records in your system. This ID is returned in webhook payloads and API responses, making it easy to track applications across systems. ```json { "reference_id": "YOUR-INTERNAL-ID-12345" } ``` ### Handle Token Expiration If your affiliate suppresses emails, implement a handler for the `prefill_application.token_expired` webhook event. When received, call `GET /api/v3/applications/{app_id}/prefill` to retrieve a fresh token and deliver the new prefill link to the applicant promptly to avoid drop-off. ### Monitor Webhook Events Subscribe to both `prefill_application.created` and `prefill_application.token_expired` events to maintain visibility into the prefill pipeline. --- ## Next Steps - Review the [API Reference](/api-reference.html) for the complete prefill endpoint schema - Set up [Webhook Subscriptions](/webhooks-guide.html) for prefill events - Contact your Quantum representative to enable prefill for your account --- # Embedded Contract Signing The Embedded Contract Signing SDK lets you render a loan agreement for signature directly inside your own web application, so applicants never leave your experience. You load a small JavaScript SDK, hand it a signing token from the Quantum API, and subscribe to a handful of Quantum-shaped events to track progress. The SDK is a thin, vendor-neutral wrapper. Your integration is written against the Quantum API surface described here — not against whichever document provider Quantum uses under the hood — so the contract you build to stays stable even if the underlying provider changes. ## Table of Contents - [How It Works](#how-it-works) - [Get a Signing Token](#get-a-signing-token) - [Installation](#installation) - [Quick Start](#quick-start) - [Configuration](#configuration) - [Events](#events) - [Error Handling](#error-handling) - [Lifecycle Methods](#lifecycle-methods) - [Embedding & Security](#embedding-and-security) - [Try It Live](#try-it-live) - [Next Steps](#next-steps) --- ## How It Works The embedded signing flow has three steps: ``` 1. YOUR BACKEND REQUESTS A SIGNING TOKEN └─→ GET /api/v3/applications/{app_id}/contracts └─→ Response includes a signing_token for each signer 2. YOUR FRONTEND LOADS THE SDK └─→ Load the SDK from the Quantum CDN └─→ new QuantumContracts(container, { signingToken }) └─→ qc.open() 3. THE APPLICANT SIGNS IN YOUR PAGE └─→ The SDK renders the signing experience inside your container └─→ You receive contract.loading / loaded / ready / completed events └─→ contract.completed fires once the signer finishes ``` The signing token is short-lived and scoped to a single signer on a single contract. Fetch it from your backend (where your API credentials live) and pass it to the browser only when you are ready to render the signing experience. --- ## Get a Signing Token **GET /api/v3/applications/{app_id}/contracts** Call this endpoint from your backend once an application has reached the document-signing stage. The response lists the contract's signers, each with a `signing_token` you hand to the SDK. > **Note:** The `signing_token` is a credential. Request it from your backend using your API token, then deliver it to the browser over a secure channel only when you are about to render the signing experience. Treat it like a session token — never log it, and never embed it in a URL. See the [API Reference](/api-reference.html) for the full request and response schema. --- ## Installation ### CDN (recommended): pinned version with Subresource Integrity Pin to an exact version and verify the bundle with [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) (SRI). The integrity hash is published as a sidecar alongside each release, so you can confirm the bytes you load are the bytes Quantum published. ```html
``` Look up the hash for the version you are pinning: ```bash curl https://cdn.quantumlends.com/packages/contracts-sdk/v1.0.0/index.umd.js.integrity # → sha384- ``` The CDN bundle (UMD) exposes a single global namespace, `QuantumSDK`: ```js const { QuantumContracts, KNOWN_ERROR_CODES, SDK_VERSION } = QuantumSDK; ``` ### CDN (convenience): floating major version If you would rather pick up patch and minor releases automatically, load the floating major-version URL. The trade-off is that you cannot use SRI with a floating tag — the hash changes whenever we publish an update, so the `integrity` attribute would reject the new bundle. ```html ``` > **Warning:** Choose deliberately. Pin + SRI gives you supply-chain protection; the floating tag gives you hands-off updates. Most integrations should pin to an exact version with SRI and bump it on your own schedule. --- ## Quick Start A complete integration is small. The example below loads the SDK from the CDN, opens a contract, and reacts to the lifecycle events. ```html
``` > **Note:** Server-side completion notification is separate from this SDK. Use the `contract.completed` event for in-page UX, but rely on Quantum webhooks for authoritative, server-confirmed completion. See the [Webhooks Integration Guide](/webhooks-guide.html). --- ## Configuration Construct the SDK with a container and a configuration object: ```ts new QuantumContracts(container, config); ``` **`container`** — either the `id` of an element (a string) or an `HTMLElement` reference. The SDK renders the signing experience inside this element. **`config`** options: The signing token for a single signer, obtained from `GET /api/v3/applications/{app_id}/contracts`. Your own identifier for this contract. The SDK never reads or derives it from the provider — it simply echoes the value you pass back in every event payload, so you can correlate events with your own records. Width of the signing container. A `number` is applied as pixels (`600` → `600px`); a `string` is applied verbatim (`"100%"`, `"50vw"`). Defaults to leaving your container's styling untouched. Height of the signing container. Same handling as `width`. A typical full-page signing experience is around `965` pixels. Enables verbose console logging for troubleshooting. The signing token is redacted in debug output. Defaults to `false`. --- ## Events Subscribe with `on(eventName, handler)`. Every payload includes the `contractId` you supplied in the constructor (or `undefined` if you did not supply one). | Event | When it fires | Payload | |-------|---------------|---------| | `contract.loading` | The signing experience is initializing | `{ contractId? }` | | `contract.loaded` | The contract document is loaded and visible | `{ contractId? }` | | `contract.ready` | The document has rendered all of its pages | `{ contractId? }` | | `contract.completed` | The signer has completed all required fields and submitted | `{ contractId? }` | | `contract.error` | An error occurred — inspect `code` | `{ contractId?, code, message? }` | ```js qc.on("contract.loading", (p) => updateStatus("Loading…")); qc.on("contract.loaded", (p) => updateStatus("Awaiting signature")); qc.on("contract.ready", (p) => updateStatus("Document ready")); qc.on("contract.completed", (p) => updateStatus("Signed")); qc.on("contract.error", (p) => showError(p.code, p.message)); ``` > **Note:** The event set is intentionally small and may grow. Additional events (for example, a future `contract.declined`) can be added in a minor release without breaking your existing handlers, since you only receive the events you subscribe to. --- ## Error Handling All failures surface through a single `contract.error` event carrying a `code` and an optional sanitized `message`. This gives you one consistent place to handle problems. | `code` | Meaning | |--------|---------| | `not_found` | The signing token is invalid or its document is no longer available | | `verification_required` | The signer must complete an identity verification step first | | `verification_limit_exceeded` | The signer exhausted their verification attempts; signing cannot proceed | | `loading_error` | The signing experience failed to initialize (network, container, or provider issue) | | `unknown` | The provider signaled a failure without a specific code | ```js qc.on("contract.error", (p) => { switch (p.code) { case "not_found": // Token expired or invalid — fetch a fresh signing_token and retry. break; case "verification_required": case "verification_limit_exceeded": // Surface a verification message to the signer. break; default: // Future codes you don't recognize yet — log and show a generic message. console.error("Signing error:", p.code, p.message); } }); ``` > **Warning:** The `code` list is **not exhaustive** — new codes may be added in any release. Always include a `default` branch when switching on `code` so your handler stays forward-compatible. The runtime export `KNOWN_ERROR_CODES` lists the codes known to the version you loaded. The optional `message` is a human-readable description with provider-specific identifiers and hostnames stripped out. It is safe to log, but it is intended for diagnostics — drive your UX off `code`, not off the message text. --- ## Lifecycle Methods | Method | Description | |--------|-------------| | `open()` | Renders the signing experience and resolves once the session is open. Returns a `Promise`. | | `close()` | Tears down the signing experience and restores a clean container. The instance stays usable — you can `open()` again with the same or a new token. | | `destroy()` | Permanently tears down the instance and unsubscribes all handlers. The instance cannot be reused afterward. | | `on(event, handler)` | Subscribe to an event. Returns the instance, so calls can be chained. | | `once(event, handler)` | Subscribe to the next occurrence of an event only. | | `off(event, handler)` | Remove a previously registered handler. | ```js const qc = new QuantumContracts("contract-container", { signingToken }); qc.on("contract.completed", () => qc.destroy()); // clean up when done await qc.open(); // Later, to swap to a different contract in the same container: qc.close(); qc.open(); // after assigning a new token, if needed ``` > **Note:** Vendor-side failures emit a `contract.error` event rather than rejecting the `open()` promise, so you have a single error channel. It is safe to call `open()` again from inside a `contract.error` handler to retry. --- ## Embedding & Security The SDK renders the signing experience in an `