diff --git a/mintlify/ramps/platform-tools/sandbox-testing.mdx b/mintlify/ramps/platform-tools/sandbox-testing.mdx
index 16fff472..231850a5 100644
--- a/mintlify/ramps/platform-tools/sandbox-testing.mdx
+++ b/mintlify/ramps/platform-tools/sandbox-testing.mdx
@@ -1,390 +1,80 @@
---
title: "Sandbox Testing"
-description: "Test ramp flows safely without moving real funds"
+description: "Use Grid sandbox magic values to trigger specific ramp outcomes"
icon: "/images/icons/hammer.svg"
"og:image": "/images/og/og-ramps.png"
---
-import SandboxBeneficiaryVerification from '/snippets/sandbox-beneficiary-verification.mdx';
import SandboxExternalAccounts from '/snippets/sandbox-external-accounts.mdx';
+import SandboxBeneficiaryVerification from '/snippets/sandbox-beneficiary-verification.mdx';
import SandboxTransferPatterns from '/snippets/sandbox-transfer-patterns.mdx';
+import SandboxQuotePatterns from '/snippets/sandbox-quote-patterns.mdx';
+import SandboxKybVerification from '/snippets/sandbox-kyb-verification.mdx';
-The Grid Sandbox environment provides a complete testing environment for ramp operations, allowing you to validate on-ramp and off-ramp flows without using real money or cryptocurrency.
-
-## Sandbox overview
-
-Sandbox mirrors production behavior while using simulated funds:
-
-- **Same API endpoints**: Use identical API calls as production
-- **Simulated funding**: Mock bank transfers and crypto deposits
-- **Real webhooks**: Receive actual webhook notifications
-- **No real money**: All transactions use test funds
-- **Isolated environment**: Sandbox data never affects production
-
-
- Sandbox is perfect for development, testing, and demonstrating ramp
- functionality before going live.
-
-
-## Getting started
-
-### Create sandbox credentials
-
-1. Log into the Grid dashboard
-2. Navigate to **Settings** → **API Keys**
-3. Click **Create API Key** and select **Sandbox** environment
-4. Save your API key ID and secret securely
-
-
- Sandbox credentials only work with the sandbox environment. They cannot access
- production data or move real funds.
-
-
-### Configure sandbox webhook
-
-Set up a webhook endpoint for sandbox notifications:
-
-```bash
-curl -X PATCH 'https://api.lightspark.com/grid/2025-10-13/config' \
- -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
- -H 'Content-Type: application/json' \
- -d '{
- "webhookEndpoint": "https://api.yourapp.dev/webhooks/grid"
- }'
-```
-
-
- Use tools like ngrok to expose local webhook endpoints during development:
- `ngrok http 3000`
-
-
-## Testing on-ramps (Fiat → Crypto)
-
-Simulate the complete on-ramp flow in sandbox:
-
-### Step 1: Create a test customer
-
-```bash
-curl -X POST 'https://api.lightspark.com/grid/2025-10-13/customers' \
- -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
- -H 'Content-Type: application/json' \
- -d '{
- "platformCustomerId": "test_user_001",
- "customerType": "INDIVIDUAL",
- "fullName": "Alice Test",
- "email": "alice@example.com",
- "birthDate": "1990-01-15",
- "address": {
- "line1": "123 Test Street",
- "city": "San Francisco",
- "state": "CA",
- "postalCode": "94105",
- "country": "US"
- }
- }'
-```
-
-In sandbox, customers are automatically approved for testing.
-
-### Step 2: Create an external account for the destination wallet
-
-```bash
-curl -X POST 'https://api.lightspark.com/grid/2025-10-13/customers/external-accounts' \
- -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
- -H 'Content-Type: application/json' \
- -d '{
- "customerId": "Customer:sandbox001",
- "currency": "BTC",
- "accountInfo": {
- "accountType": "SPARK_WALLET",
- "address": "spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu"
- }
- }'
-```
+The Grid sandbox environment simulates real on-ramp and off-ramp flows without moving real money or cryptocurrency. You can control test outcomes using special account number patterns and test addresses.
-### Step 3: Create an on-ramp quote (just-in-time funding)
+Ramp flows (create customer, register external account, create and execute quote) use the same endpoints as production — see [Fiat-to-Crypto and Crypto-to-Fiat](/ramps/conversion-flows/fiat-crypto-conversion). This page describes only the sandbox-specific controls used to drive those flows in tests.
-```bash
-curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \
- -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
- -H 'Content-Type: application/json' \
- -d '{
- "source": {
- "customerId": "Customer:sandbox001",
- "currency": "USD"
- },
- "destination": {
- "destinationType": "ACCOUNT",
- "accountId": "ExternalAccount:b23dcbd6-dced-4ec4-b756-3c3a9ea3d456"
- },
- "lockedCurrencySide": "SENDING",
- "lockedCurrencyAmount": 10000,
- "description": "Test on-ramp conversion"
- }'
-```
+## KYC/KYB verification
-The quote response includes payment instructions with a reference code.
-
-### Step 4: Simulate funding
-
-Use the sandbox endpoint to simulate receiving the fiat payment:
-
-```bash
-curl -X POST 'https://api.lightspark.com/grid/2025-10-13/sandbox/send' \
- -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
- -H 'Content-Type: application/json' \
- -d '{
- "reference": "RAMP-ABC123",
- "currencyCode": "USD",
- "currencyAmount": 10000
- }'
-```
-
-
- The reference code must match the one provided in the quote's payment
- instructions.
-
-
-### Step 5: Verify completion
-
-Within seconds, you'll receive a webhook notification confirming the on-ramp completed:
-
-```json
-{
- "transaction": {
- "id": "Transaction:sandbox025",
- "status": "COMPLETED",
- "type": "OUTGOING",
- "sentAmount": {
- "amount": 10000,
- "currency": { "code": "USD" }
- },
- "receivedAmount": {
- "amount": 95000,
- "currency": { "code": "BTC" }
- },
- "settledAt": "2025-10-03T15:02:30Z"
- },
- "type": "OUTGOING_PAYMENT"
-}
-```
+
-## Testing off-ramps (Crypto → Fiat)
-
-Simulate the complete off-ramp flow:
-
-### Step 1: Fund internal account with crypto
-
-Simulate a Bitcoin deposit to the customer's internal account using the sandbox funding endpoint:
-
-```bash
-curl -X POST 'https://api.lightspark.com/grid/2025-10-13/sandbox/internal-accounts/InternalAccount:btc001/fund' \
- -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
- -H 'Content-Type: application/json' \
- -d '{
- "amount": 10000000
- }'
-```
-
-Replace `InternalAccount:btc001` with your actual BTC internal account ID.
-
-
- You'll receive an `ACCOUNT_STATUS` webhook showing the updated balance.
-
-
-### Step 2: Create external bank account
-
-```bash
-curl -X POST 'https://api.lightspark.com/grid/2025-10-13/customers/external-accounts' \
- -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
- -H 'Content-Type: application/json' \
- -d '{
- "customerId": "Customer:sandbox001",
- "currency": "USD",
- "platformAccountId": "test_bank_001",
- "accountInfo": {
- "accountType": "US_ACCOUNT",
- "accountNumber": "123456001",
- "routingNumber": "021000021",
- "accountCategory": "CHECKING",
- "bankName": "Test Bank",
- "beneficiary": {
- "beneficiaryType": "INDIVIDUAL",
- "fullName": "Alice Test",
- "birthDate": "1990-01-15",
- "nationality": "US",
- "address": {
- "line1": "123 Test Street",
- "city": "San Francisco",
- "state": "CA",
- "postalCode": "94105",
- "country": "US"
- }
- }
- }
- }'
-```
-
-
-In sandbox, you can use special account number patterns to test different scenarios. The **last 3 digits** determine the behavior: **002** (insufficient funds), **003** (account closed), **004** (transfer rejected), **005** (timeout/delayed failure). Any other ending succeeds normally. See "Testing transfer failures" below for details.
-
-
-### Step 3: Create and execute off-ramp quote
-
-```bash
-curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \
- -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
- -H 'Content-Type: application/json' \
- -d '{
- "source": {
- "accountId": "InternalAccount:sandbox_btc001"
- },
- "destination": {
- "accountId": "ExternalAccount:sandbox_bank001",
- "currency": "USD"
- },
- "lockedCurrencySide": "SENDING",
- "lockedCurrencyAmount": 5000000,
- "description": "Test off-ramp conversion"
- }'
-```
-
-Then execute the quote:
-
-```bash
-curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes/{quoteId}/execute' \
- -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
-```
-
-
- In sandbox, off-ramp conversions complete instantly. In production, bank
- settlement may take 1-3 business days.
-
-
-## Testing transfer failures
-
-### External account test patterns
+## Adding external accounts
-
-
-## Beneficiary name verification
+### Beneficiary name verification
-## Test scenarios
-
-### Successful conversions
+## Transfer in (funding internal accounts)
-The complete on-ramp and off-ramp flows described in the sections above demonstrate successful conversion scenarios. For quick reference:
+In production, internal accounts are funded by sending a bank transfer to the account's payment instructions or by pulling from an external account. In sandbox, you have two options:
-**On-ramp test (USD → BTC):**
-1. Create customer and quote with payment instructions
-2. Use `/sandbox/send` to simulate funding
-3. Verify completion via webhook
+### Transfer in from an external account
-**Off-ramp test (BTC → USD):**
-1. Fund BTC internal account with `/sandbox/internal-accounts/{accountId}/fund`
-2. Create external bank account (use default account number for success)
-3. Create and execute quote
-4. Verify completion via webhook
+Use the `/transfer-in` endpoint to pull funds from an external account into an internal account. The external account's number suffix determines the outcome:
-### Failed conversions
+
-Test error scenarios systematically using the magic account patterns:
+### Sandbox fund endpoint
-**1. Test external account insufficient funds (002):**
+Instantly add funds to any internal account using `/sandbox/internal-accounts/{accountId}/fund`:
```bash
-# Create account with insufficient funds pattern
-curl -X POST 'https://api.lightspark.com/grid/2025-10-13/customers/external-accounts' \
+curl -X POST https://api.lightspark.com/grid/2025-10-13/sandbox/internal-accounts/{accountId}/fund \
-u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
- -H 'Content-Type: application/json' \
- -d '{
- "customerId": "Customer:sandbox001",
- "currency": "USD",
- "accountInfo": {
- "accountType": "US_ACCOUNT",
- "accountNumber": "000000002",
- "routingNumber": "021000021",
- "accountCategory": "CHECKING",
- "beneficiary": {
- "beneficiaryType": "INDIVIDUAL",
- "fullName": "Test User"
- }
- }
- }'
-
-# Attempt off-ramp to this account - will fail immediately
-curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes/{quoteId}/execute' \
- -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
-# Response: 400 Bad Request with insufficient funds error
+ -H "Content-Type: application/json" \
+ -d '{ "amount": 100000 }'
```
-**2. Test account closed (003):**
+## Creating quotes (cross-currency conversions)
-```bash
-# Create account with closed pattern
-curl -X POST 'https://api.lightspark.com/grid/2025-10-13/customers/external-accounts' \
- -d '{"accountNumber": "000000003", ...}'
+
-# Attempt to use - will fail with account closed error
-```
+### Executing a quote
-**3. Test insufficient balance in internal account:**
+After creating a quote, you need to fund it to trigger execution. There are two ways to do this in sandbox:
-```bash
-# Create quote from empty internal account
-curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \
- -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
- -H 'Content-Type: application/json' \
- -d '{
- "source": {
- "accountId": "InternalAccount:empty_btc"
- },
- "destination": {
- "accountId": "ExternalAccount:bank001",
- "currency": "USD"
- },
- "lockedCurrencySide": "SENDING",
- "lockedCurrencyAmount": 10000000
- }'
+**Prefunded internal account** — If your quote's source is an internal account (off-ramp), fund the account using one of the methods described in [transfer in](#transfer-in-funding-internal-accounts), then call the quote execute endpoint:
-# Execute will fail with insufficient balance error
+```bash
+curl -X POST https://api.lightspark.com/grid/2025-10-13/quotes/{quoteId}/execute \
+ -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
```
-**4. Test invalid wallet address:**
+**Real-time funding via sandbox send** — If your quote uses real-time funding (on-ramp), the quote response includes payment instructions for you to transfer funds to. Use `/sandbox/send` to simulate this payment:
```bash
-# Attempt to create an external account with invalid Spark address
-curl -X POST 'https://api.lightspark.com/grid/2025-10-13/customers/external-accounts' \
+curl -X POST https://api.lightspark.com/grid/2025-10-13/sandbox/send \
-u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
- -H 'Content-Type: application/json' \
+ -H "Content-Type: application/json" \
-d '{
- "currency": "BTC",
- "accountInfo": {
- "accountType": "SPARK_WALLET",
- "address": "invalid_address"
- }
+ "quoteId": "Quote:019542f5-b3e7-1d02-0000-000000000006",
+ "currencyCode": "USD"
}'
-# Response: 400 Bad Request with validation error
```
-## Moving to Production
-
-When you're ready to move to production:
-
-1. Generate production API tokens in the dashboard
-2. Swap those credentials for the sandbox credentials in your environment variables
-3. Remove any sandbox-specific test patterns from your code
-4. Configure production webhook endpoints
-5. Test with small amounts first
-
-## Next steps
+## Transferring out funds
-- [Webhooks](/ramps/platform-tools/webhooks) - Handle real-time notifications
-- [Fiat-to-Crypto Conversion](/ramps/conversion-flows/fiat-crypto-conversion) - Implement production flows
-- [Self-Custody Wallets](/ramps/conversion-flows/self-custody-wallets) - Advanced wallet integration
-- [Platform Configuration](/ramps/onboarding/platform-configuration) - Configure production settings
-- [API Reference](/api-reference) - Complete API documentation
+Use the `/transfer-out` endpoint to push funds from an internal account to an external account in the same currency. The external account's number suffix controls the outcome using the same patterns as [transfer in](#transfer-in-from-an-external-account).