Sandbox
Sandbox lets you test the entire scan lifecycle — creation, status transitions, webhooks, and result retrieval — without consuming credits or calling the solver. Use a sandbox API key and every scan automatically runs in sandbox.
How it works
When you create a scan with a sandbox API key (prefixed zs_test_), the API:
- Validates your input — the same validation rules as production apply. Malformed DTOs, missing required fields, and invalid combinations are rejected with real error messages.
- Discards the validated payload — after validation passes, the constructed solver payload is thrown away.
- Selects a pre-computed fixture — from a library of 20 fixture scenarios, the API picks the one whose traits best match your input (see Fixture selection).
- Pre-stages the result — the fixture's payload and result are saved immediately, so the result is ready before status transitions even begin.
- Simulates status transitions — the scan progresses through
pending → processing → completedon a fixed timeline (see Status simulation).
The response shape, webhook events, and scan lifecycle are identical to production. The only difference is the result data comes from a fixture rather than the solver.
Zero cost
Sandbox scans are completely free:
- They don't consume credits — the
costfield isnull - They don't count towards your tier's scan limit — monthly usage meters exclude sandbox scans entirely
- They don't trigger metered billing on Stripe
- They bypass all limit checks — no overage flags, no hard caps
This makes sandbox ideal for integration testing, CI pipelines, and development workflows.
Studio scans not supported
Sandbox does not support studio scans. If your request includes customProfileId or sets inputMethod to "studio", the API will reject it with a 400 Bad Request error:
{
"statusCode": 400,
"message": "Sandbox does not support studio scans. Remove customProfileId and set inputMethod to 'known'."
}
Use inputMethod: "known" with yearlyKwh and loadProfile instead. This covers all hardware combinations (existing PV, new PV, battery) and both optimization goals.
Fixture selection
Instead of running the solver, sandbox selects a pre-computed fixture that best matches your input. The selection is based on four traits extracted from your CreateScan request:
- Name
hasExistingPv- Type
- boolean
- Description
Whether
existingPvcontains at least one entry.
- Name
hasNewPv- Type
- boolean
- Description
Whether
newPvcontains at least one entry.
- Name
hasBattery- Type
- boolean
- Description
Whether
electricBatteryis provided.
- Name
goal- Type
- string
- Description
The optimization goal:
"cost-savings"or"self-sufficiency".
Each fixture is scored against your input: +1 for every matching trait, -1 for every mismatch. The highest-scoring fixture is selected.
Available fixtures
| Fixture | Existing PV | New PV | Battery | Goal |
|---|---|---|---|---|
| Basic — cost savings | — | — | — | cost-savings |
| Basic — self-sufficiency | — | — | — | self-sufficiency |
| Existing PV | ✓ | — | — | cost-savings |
| Existing PV | ✓ | — | — | self-sufficiency |
| Existing PV + export | ✓ | — | — | cost-savings |
| New PV | — | ✓ | — | cost-savings |
| New PV | — | ✓ | — | self-sufficiency |
| Existing + New PV | ✓ | ✓ | — | cost-savings |
| Existing + New PV | ✓ | ✓ | — | self-sufficiency |
| Battery only | — | — | ✓ | cost-savings |
| Battery only | — | — | ✓ | self-sufficiency |
| Existing PV + Battery | ✓ | — | ✓ | cost-savings |
| Existing PV + Battery | ✓ | — | ✓ | self-sufficiency |
| New PV + Battery | — | ✓ | ✓ | cost-savings |
| New PV + Battery | — | ✓ | ✓ | self-sufficiency |
| New PV + Fixed Battery | — | ✓ | ✓ | cost-savings |
| New PV + Fixed Battery | — | ✓ | ✓ | self-sufficiency |
| Existing + New PV + Battery | ✓ | ✓ | ✓ | cost-savings |
| Existing + New PV + Battery | ✓ | ✓ | ✓ | self-sufficiency |
| Existing + New PV + Battery + export | ✓ | ✓ | ✓ | cost-savings |
The fixture result is always a successful, optimal scan with realistic numbers. It won't reflect your exact input values (e.g. panel count, battery size) — it reflects the closest pre-computed scenario.
Status simulation
Sandbox scans progress through the same statuses as production scans, on a fixed timeline:
| Elapsed time | Status | Webhook event |
|---|---|---|
| 0 – 5 seconds | pending | scan.pending (fired on creation) |
| 5 – 10 seconds | processing | scan.processing |
| 10+ seconds | completed | scan.completed |
The status syncer checks every 2 seconds. After approximately 10 seconds your scan will be marked completed and the pre-staged result becomes available via GET /v1/scans/:id/result.
Sandbox scans never transition to error. They always complete successfully.
Webhooks require sandbox endpoints
Sandbox scans only dispatch events to sandbox webhook endpoints. Production webhook endpoints will not receive events from sandbox scans, and vice versa.
To receive webhook events during testing:
- Register a webhook endpoint in the dashboard under Organization → API
- Make sure it's set to sandbox (not live)
- Subscribe to the events you want (
scan.pending,scan.processing,scan.completed)
The event payload, signing, and delivery behavior are identical to production. See [[Webhooks]] for full details on payload format and signature verification.
Identifying sandbox scans
Sandbox scans are scoped to the API key mode used to create them. When you list scans via GET /v1/scans with a sandbox API key, you only see sandbox scans. With a live key, you only see production scans.
From a behavioral perspective, sandbox scans are distinguishable by:
- Completing in ~10 seconds — much faster than production scans
- No credits consumed — your usage meters and billing are unaffected
- Fixture-based results — the result data comes from pre-computed scenarios, not the solver
What's identical to production
Everything except the solver and billing:
- Input validation (real
400errors for bad input) - Webhook event flow (
pending → processing → completed) - Webhook signing and delivery
- API response shape and fields
- Scan lifecycle (create → poll/webhook → retrieve result)
- Access control and project scoping
- Result retrieval via
GET /v1/scans/:id/result