# Sandbox Environment

This guide covers the Quantum Sandbox environment — what it is, how to use it, and how to trigger specific application outcomes for testing.

## Table of Contents

- [What Is the Sandbox?](#what-is-the-sandbox)
- [Why Use the Sandbox?](#why-use-the-sandbox)
- [Getting Started](#getting-started)
- [What Are Magic Values?](#what-are-magic-values)
- [Declining an Application](#declining-an-application)
- [Controlling Offer Generation](#controlling-offer-generation)
- [Application Lifecycle](#application-lifecycle)
- [Pre-Underwriting Document Requests](#pre-underwriting-document-requests)
- [Closing Document Verifications](#closing-document-verifications)
- [Offer Selection](#offer-selection)
- [Booking](#booking)
- [Document Upload Outcomes](#document-upload-outcomes)
- [End-to-End Test Scenarios](#end-to-end-test-scenarios)
- [Glossary](#glossary)

---

## What Is the Sandbox?

The Sandbox is a fully isolated testing environment hosted at **sandbox.quantumlends.com**. It mirrors the production API so you can develop and validate your integration without affecting real data.

**Think of it like this:** Rather than testing against the production system and hoping nothing breaks, the Sandbox gives you a controlled environment where you can trigger any application outcome on demand.

All Sandbox data is ephemeral and completely isolated from production.

---

## Why Use the Sandbox?

| Benefit | Description |
|---------|-------------|
| **Predictable outcomes** | Trigger specific application results (approvals, declines, offer types) using magic values |
| **Full lifecycle testing** | Walk through the entire application journey, from submission through contract generation |
| **Document workflow validation** | Test document upload, approval, and rejection scenarios |
| **Integration testing** | Validate your webhook integrations against repeatable behavior |
| **Safe experimentation** | No risk to production data or real applications |

---

## Getting Started

### Authentication

Authentication is handled by a Bearer token. Include the token in the `Authorization` header for all API requests.

```
Authorization: Bearer <token>
```

### 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

{% callout type="warning" %}
Keep your API token secure. Never expose it in client-side code or public repositories.
{% /callout %}

### Base URL

All Sandbox API requests should be made to:

```
https://sandbox.quantumlends.com
```

The Sandbox supports the same API endpoints as production. See the [API Reference](/api-reference.html) for the complete endpoint documentation.

{% callout type="info" %}
Want to see the Sandbox in action? Try the [Sandbox Test Runner](/sandbox-test.html) to walk through the full application lifecycle in your browser. You can also download the [Node.js script](/examples/e2e-sandbox-test.mjs) to run the same flow from the command line.
{% /callout %}

---

## What Are Magic Values?

Magic values are special input values that you include in your test application data to trigger specific behaviors in the Sandbox. Think of them as cheat codes — by setting a particular field to a known value, you can deterministically control the outcome of an application.

For example, setting `business.tax_identification` to `111110000` will automatically decline the application with a specific adverse action reason. Setting `business.legal_name` to `3 Loans TL` will generate exactly 3 Term Loan offers.

When you submit an application containing a magic value, the Sandbox detects it and applies the corresponding behavior automatically.

{% callout type="info" %}
Magic values can be combined. For example, you can set an owner's `last_name` to `Auto Approve` and `business.legal_name` to `3 Loans Mix` in the same request to get an auto-approved application with specific offer types.
{% /callout %}

---

## Declining an Application

You can trigger an automatic decline by including specific values in your application data. There are several ways to do this.

### By Business Tax Identification

Set the `business.tax_identification` field to one of the values below. The application will be immediately declined with the corresponding adverse action reason.

**Business Reasons:**

| tax_identification | Adverse Action Reason |
|--------------------|----------------------|
| `111110000` | Adverse business credit report information |
| `111110001` | Applicant's business does not qualify for minimum approval threshold |
| `111110002` | Applicant's business exceeds aggregate exposure limits |
| `111110003` | Application ineligible due to payment history with Quantum |
| `111110004` | Borrower Risk Outside of Tolerance |
| `111111105` | Collection action, lien or judgment |
| `111111106` | Excessive recent credit inquiries |
| `111111107` | Foreclosure, repossession or bankruptcy |
| `111111108` | Limited commercial credit experience |
| `111111109` | Slow or past due in trade or loan payments |
| `111111110` | Unable to verify business banking account information |
| `111111111` | Unable to verify business identity |
| `111111112` | Unable to verify income |
| `111111113` | Unable to verify relationship between business and applicant |
| `111111114` | Use of funds ineligible for loan proceeds |
| `111111115` | Value or type of collateral not sufficient |

**Guarantor Reasons:**

| tax_identification | Adverse Action Reason |
|--------------------|----------------------|
| `111111116` | Inadequate Guarantors Reason |
| `111111117` | Credit scoring does not meet minimum lender thresholds |
| `111111118` | Guarantor Age is less than 18 years old |
| `111111119` | Guarantor does not have a credit file |
| `111111120` | Guarantor has adverse personal credit report information |
| `111111121` | Guarantor has delinquent credit obligations |
| `111111122` | Guarantor has delinquent credit obligations with others |
| `111111123` | Guarantor has excessive obligations in relation to income |
| `111111124` | Guarantor has high number of recent credit inquiries on credit file |
| `111111125` | Guarantor has high utilization of existing credit lines |
| `111111126` | Guarantor has limited credit experience |


When a decline is triggered, the application status is set to `Declined`. You can retrieve the adverse action reasons by calling `GET /api/v3/applications/{app_id}` and inspecting the `decision` object in the response. Business reasons appear in the `business_adverse_action_reasons` array, and guarantor reasons appear in the `inadequate_guarantor_reasons` array:

```json
{
  "decision": {
    "business_adverse_action_reasons": [
      "Adverse business credit report information"
    ],
    "inadequate_guarantor_reasons": [],
    "adverse_action_date": "2025-01-15",
    "adverse_action_sent_date": null,
    "adverse_action_notification_pdf_url": null
  }
}
```

### By Business Tenure

If the business was established less than 1 year ago (based on `business.start_date`), the application is declined.

| Condition | Adverse Action Reason |
|-----------|-----------------------|
| `business.start_date` is less than 1 year ago | Applicant's business tenure insufficient |

### By State

| Field | Value | Adverse Action Reason |
|-------|-------|-----------------------|
| `business.address.state` | `NV` | Lender does not offer credit in the state where applicant is located |

### By Loan Amount

| Field | Value | Adverse Action Reason |
|-------|-------|-----------------------|
| `loan_request.amount` | `199995` | Incomplete Application |
| `loan_request.amount` | `199996` | Insufficient data submitted to render a credit decision |
| `loan_request.amount` | `199997` | Lack of established earnings record with reasonable expectation of continuity |

### By Loan Purpose

| Field | Value | Adverse Action Reason |
|-------|-------|-----------------------|
| `loan_request.purpose` | `other` | Request for personal, family or household use rather than business use |

{% callout type="info" %}
The `loan_request.purpose` value is case-insensitive — `other`, `Other`, and `OTHER` all work. All other decline triggers are case-sensitive and must match exactly as shown.
{% /callout %}

### By Document Filename

When a document is uploaded, the Sandbox checks the filename for magic values.

| Filename | Adverse Action Reason |
|----------|----------------------|
| `Insufficient_cash_flow.pdf` | Insufficient cash flow from business bank account |

{% callout type="info" %}
Filename matching is case-insensitive.
{% /callout %}

### By Bank Connection (Plaid)

When an applicant connects a bank account via Plaid (instead of uploading documents), the Sandbox runs cash flow analysis and then advances the application automatically — by default the application **advances** to the offer stage. To exercise the decline path on a bank connection, submit with the magic loan amount below:

| Field | Value | Adverse Action Reason |
|-------|-------|-----------------------|
| `loan_request.amount` | `199998` | Insufficient cash flow from business bank account |

{% callout type="info" %}
`199998` is distinct from the decline amounts above (`199995`–`199997`): those decline immediately at submission, whereas `199998` lets the application reach Pre-Underwriting and then declines once the bank connection's cash flow analysis completes.
{% /callout %}

---

## Controlling Offer Generation

By default, applications that aren't declined receive **1 Term Loan + 1 Fee Based LOC**. You can control the number and type of offers by setting the `business.legal_name` field:

| legal_name | Offers Generated |
|------------|------------------|
| `3 Loans TL` | 3 Term Loans |
| `3 Loans LOC` | 3 Fee Based Lines of Credit |
| `3 Loans Mix` | 2 Term Loans + 1 Fee Based LOC |
| `2 Loans TL` | 2 Term Loans |
| `2 Loans LOC` | 2 Fee Based Lines of Credit |
| `2 Loans Mix` | 1 Term Loan + 1 Fee Based LOC |
| `1 Loan TL` | 1 Term Loan |
| `1 Loan LOC` | 1 Fee Based LOC |
| `3 Loans TLOC` | 3 Term LOCs |
| `2 Loans TLOC` | 2 Term LOCs |
| `1 Loan TLOC` | 1 Term LOC |
| `3 Loans RBA` | 3 Revenue Based Advances |
| `2 Loans RBA` | 2 Revenue Based Advances |
| `1 Loan RBA` | 1 Revenue Based Advance |
| *(no magic value)* | 1 Term Loan + 1 Fee Based LOC |

{% callout type="info" %}
Matching is case-insensitive. Both `Loan` and `Loans` are accepted. The maximum number of offers is 3.
{% /callout %}

### Offer Details

Each generated offer uses the following values:

**Term Loan:**

| Field | Value |
|-------|-------|
| Type | Term Loan |
| Amortization Type | Standard Full Amortization |
| Loan Amount | Requested amount x 1.05 |
| Term | 12 months |
| Rate | 1.23 |
| Payment Frequency | Monthly |

**Fee Based LOC:**

| Field | Value |
|-------|-------|
| Type | Fee Based LOC |
| Facility Size | Requested amount |
| Term | 18 months |
| Basis Fee | 1.88 |
| Draw Fee | 1.64 |
| Minimum Draw Amount | $1,000 |
| Commitment Fee | 1.46 |

**Term LOC:** A line of credit priced with an interest rate and an amortized payment (unlike the Fee Based LOC, which uses a basis fee).

| Field | Value |
|-------|-------|
| Type | Term Line of Credit |
| Facility Size | Requested amount |
| Rate | 19.99 |
| Term | 18 months |
| Amortized Payment | Facility Size / 18 |
| Draw Fee | 2 |
| Minimum Draw Amount | $1,000 |
| Commitment Fee | 3 |

**RBA (Revenue Based Advance):** Priced by a factor rate rather than an interest rate.

| Field | Value |
|-------|-------|
| Type | Revenue Based Advance |
| Loan Amount | Requested amount |
| Factor Rate | 1.25 |
| Term | 12 months |
| Amortized Payment | Loan Amount x Factor Rate / 12 |
| Payment Frequency | Monthly |

---

## Application Lifecycle

### Default Behavior

By default, non-declined applications are set to **Pre-Underwriting** status. The application waits for a document upload before progressing through the rest of the pipeline.

### Triggering the Offer Pipeline

There are two ways to move the application past Pre-Underwriting:

| Trigger | How It Works |
|---------|--------------|
| **Auto Approve magic value** | Set any owner's `last_name` to `Auto Approve`. The pipeline runs immediately when the application is submitted. |
| **Document upload** | Upload a document while the application is in Pre-Underwriting status. The pipeline runs automatically. |

{% callout type="warning" %}
The `Auto Approve` value is case-sensitive and must match exactly. Decline triggers always take priority — if both a decline trigger and Auto Approve are present, the application is declined.
{% /callout %}

### Pipeline Progression

Once triggered, the application moves through these statuses automatically:

```
1. APPLICATION SUBMITTED
   └─→ Status: Pre-Underwriting

2. PIPELINE TRIGGERED (Auto Approve or document upload)
   └─→ Status: Credit Review
   └─→ Status: Approved (offers generated)
   └─→ Status: Offer Sent
```

### Additional Personal Guarantors

When any owner (including the primary applicant) has `last_name` set to `Additional PGs`, an **Additional PG/s Needed** task is added to the application. This task remains incomplete throughout the entire offer generation pipeline.

| Field | Magic Value | Description |
|-------|-------------|-------------|
| `owners[].last_name` | `Additional PGs` | Adds an incomplete "Additional PG/s Needed" task |

{% callout type="warning" %}
This value is case-sensitive and must match exactly as `Additional PGs`.
{% /callout %}

---

## Pre-Underwriting Document Requests

The requested loan amount (`loan_request.amount`) determines which financial documents Underwriting requests for the application. Larger loans require a more complete documentation package. The Sandbox adds the corresponding document-request tasks when the application is submitted.

There are three tiers:

| Tier | Requested Amount |
|------|------------------|
| Micro | less than $150,000 |
| Mid | $150,000 to $249,999 |
| Big | $250,000 or more |

The documents requested for each tier:

| Document | Micro | Mid | Big |
|----------|:-----:|:---:|:---:|
| 3 months of bank statements | ✓ | ✓ | ✓ |
| Most recent business tax return | | ✓ | ✓ |
| Business tax returns (2 years) | | | ✓ |
| Year-to-date financials | | | ✓ |
| Debt schedule | | ✓ | ✓ |
| Most recent personal tax return | | | ✓ |

These document-request tasks start **incomplete** and are automatically completed when the offer pipeline runs (via the `Auto Approve` magic value or a document upload) — the same lifecycle as the other Pre-Underwriting tasks.

{% callout type="info" %}
Tier boundaries are exact: $150,000 and $249,999 are **Mid**; $250,000 is **Big**. Bank statements are always requested. Decline triggers take priority — a declined application receives no document-request tasks.
{% /callout %}

---

## Closing Document Verifications

These magic values control which closing document verifications remain **incomplete** after offer generation. When present, the corresponding verification is not auto-completed during the pipeline. This determines which offer selection flow the application follows (see [Offer Selection](#offer-selection)).

| Magic Value | Field | Verification |
|-------------|-------|--------------|
| `No-Name` | `business.doing_business_as` | Business Name Verification |
| `Fraudville` | `business.address.city` | Business Address Verification |
| `111002102` | `business.tax_identification` | Linkage Verification |
| `1 License Lane` | `business.address.street` | Driving License (PG Name Verification) |
| `Bounced Check Suite` | `business.address.apt_suite_number` | Voided Check |

---

## Offer Selection

When a customer selects an offer (via `POST /api/v3/applications/{app_id}/offer/{offer_id}/accept`), the next steps depend on whether any closing document verifications are still incomplete.

### Flow A: Incomplete Verifications Present

If any of the closing document magic values above were used, the application goes through an additional document collection step before contract generation:

```
1. OFFER ACCEPTED
   └─→ Status: Loan Closing Review

2. PENDING DOCUMENTS
   └─→ Status: Pending Closing Info (waiting for customer)

3. DOCUMENTS UPLOADED
   └─→ Closing verifications completed
   └─→ Contract generated and sent
   └─→ Status: Documents Sent
```

### Flow B: No Incomplete Verifications

If no closing document magic values were used, the contract is generated immediately after offer acceptance:

```
1. OFFER ACCEPTED
   └─→ Status: Loan Closing Review

2. CONTRACT
   └─→ Contract generated and sent
   └─→ Status: Documents Sent
```

### Contract Void

By default, the contract remains open for the applicant to sign. To test the void scenario, set `loan_request.amount` to `10099` — the contract will be automatically voided after a 10-second delay.

| Field | Magic Value | What Happens |
|-------|-------------|--------------|
| `loan_request.amount` | `10099` | Contract is voided after a 10-second delay |

---

## Booking

Once the contract is sent (**Documents Sent**), the applicant signs it — see [Embedded Signing](/embedded-signing.html) for how contract signing works in the Sandbox. After signing, the application moves into **Funding & Boarding**, the final stage before the loan is booked. (The partner API surfaces this status as `Awaiting Funding`.)

In production, booking is handled by a downstream system and typically takes **a day or more** to finalize, after which the application advances to **Booked**. The Sandbox compresses that wait: once an application enters **Funding & Boarding**, it advances to **Booked** automatically after about **15 seconds** — with no further action required, so you can exercise the full happy path without waiting a day.

```
1. CONTRACT SIGNED
   └─→ Status: Funding & Boarding

2. BOOKING (automatic, ~15s later)
   └─→ Status: Booked
```

{% callout type="info" %}
**Booked** is the terminal status of the Sandbox happy path. In production this final transition typically takes **a day or more**; the Sandbox shortens it to ~15 seconds so you can test end-to-end quickly. No action is required on your end.
{% /callout %}

---

## Document Upload Outcomes

When a document is uploaded via `POST /api/v3/applications/{app_id}/documents`, the Sandbox checks the filename and automatically approves or rejects the document.

| Filename | Outcome | Rejection Reason |
|----------|---------|------------------|
| `good_doc.pdf` | Approved | — |
| `illegible.pdf` | Rejected | ILLEGIBLE_OR_BLURRY |
| `wrong_time_period.pdf` | Rejected | OTHER (wrong time period) |
| `missing_pages.pdf` | Rejected | INCOMPLETE |
| `wrong_doc.pdf` | Rejected | WRONG_TYPE_UPLOADED |
| `redacted_info.pdf` | Rejected | MISSING_INFORMATION |

{% callout type="info" %}
Filename matching is case-insensitive. Documents with filenames that don't match any magic value are not automatically processed.
{% /callout %}

---

## End-to-End Test Scenarios

These scenarios walk you through common integration test cases. Each one combines magic values to exercise a specific path through the application lifecycle.

### Scenario 1: Quick Approval with Default Offers

Submit an application with any owner's `last_name` set to `Auto Approve`. The application will automatically progress through Pre-Underwriting, Credit Review, Approved, and Offer Sent — generating a default set of offers (1 Term Loan + 1 Fee Based LOC).

```
Submit → Pre-Underwriting → Credit Review → Approved → Offer Sent
```

### Scenario 2: Decline by Tax ID

Submit an application with `business.tax_identification` set to `111110000`. The application will be immediately declined with the adverse action reason "Adverse business credit report information."

```
Submit → Declined
```

### Scenario 3: Custom Offer Types

Submit an application with:
- Any owner's `last_name` set to `Auto Approve`
- `business.legal_name` set to `3 Loans Mix`

The application will be approved with 2 Term Loan offers and 1 Fee Based LOC offer.

```
Submit → Pre-Underwriting → Credit Review → Approved (3 offers) → Offer Sent
```

### Scenario 4: Closing Document Collection Flow

Submit an application with:
- Any owner's `last_name` set to `Auto Approve`
- `business.doing_business_as` set to `No-Name`

After the application reaches Offer Sent, accept an offer. The application will enter Pending Closing Info status. Then upload a document to complete the closing verifications and trigger contract generation. Once the contract is sent, sign it (see [Embedded Signing](/embedded-signing.html)) — the application then moves to **Funding & Boarding** and, about 15 seconds later, automatically advances to the terminal **Booked** status (see [Booking](#booking)).

```
Submit → ... → Offer Sent → Offer Accepted → Loan Closing Review
  └─→ Pending Closing Info → (document upload) → Documents Sent
  └─→ (contract signed) → Funding & Boarding → (~15s) → Booked
```

### Scenario 5: Document Rejection

After triggering the offer pipeline, upload a document with the filename `illegible.pdf`. The Sandbox will automatically reject the document with the reason `ILLEGIBLE_OR_BLURRY`.

### Scenario 6: Contract Void

Submit an application with:
- Any owner's `last_name` set to `Auto Approve`
- `loan_request.amount` set to `10099`

After the application reaches Offer Sent and an offer is accepted, the contract will be generated, sent, and then voided after a 10-second delay.

```
Submit → ... → Offer Sent → Offer Accepted → Loan Closing Review
  └─→ Contract generated → Contract sent → Contract voided (10s delay)
```

---

## Glossary

| Term | Definition |
|------|------------|
| **Magic Value** | A special input value that triggers a specific behavior in the Sandbox |
| **Adverse Action** | A decline decision with a specific regulatory reason |
| **Pre-Underwriting** | The initial status where the application waits for document uploads before progressing |
| **Offer Pipeline** | The automated sequence that generates offers and moves the application through Credit Review, Approved, and Offer Sent |
| **Funding & Boarding** | The final stage before a loan is booked; surfaced as `Awaiting Funding` in the partner API |
| **Booked** | Terminal status of the happy path — the loan has been booked |

---

## Questions?

For the complete API endpoint documentation, see the [API Reference](/api-reference.html).

For assistance with Sandbox setup or questions about your integration, contact your Quantum partner representative.
