Creating test scans
Walk through the full scan lifecycle in sandbox — from creating a test API key to retrieving results — without spending any credits.
This tutorial assumes you have an Enhub account with a project. If not, sign up and create a project first.
Create a test API key
Navigate to Organization → API in the dashboard and create a new API key. Make sure to set the key to sandbox (not production).
Test API keys are prefixed with zs_test_ and automatically route all scan requests through sandbox. Copy the key — you'll need it for every request in this tutorial.
You can have both test and production keys active simultaneously. They don't interfere with each other.
Example test key
zs_test_abc123def456...
Register a test webhook
Still in Organization → API, register a webhook endpoint. Set it to sandbox so it receives events from test scans.
Subscribe to all scan events to see the full lifecycle:
scan.pendingscan.processingscan.completed
Save the signing secret (whsec_...) — you'll need it to verify webhook payloads. See [[Webhooks]] for signature verification details.
Sandbox webhook endpoints only receive events from test scans. Production endpoints are completely separate.
Webhook events you'll receive
{
"event": "scan.pending",
"data": {
"id": "clx...",
"status": "pending",
"runUuid": "test_a1b2c3d4-...",
"createdAt": "2025-01-15T10:30:00.000Z"
}
}
Create a test scan
Submit a scan creation request using your test API key. The request body is identical to a production scan — sandbox is activated entirely by the API key.
The API validates your input using the same rules as production. If something is wrong (missing fields, invalid combinations), you'll get a real 400 Bad Request error.
On success, the scan is created with status: pending. Behind the scenes, a pre-computed fixture result is already staged — the scan will complete automatically in about 10 seconds.
Create a test scan
curl -X POST https://api.enhub.nl/v1/scans \
-H "x-api-key: zs_test_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"yearlyKwh": 3500,
"goal": "cost-savings",
"inputMethod": "known",
"location": {
"lat": 52.09,
"lon": 5.12
},
"newPv": [{
"maxPanelCount": 12,
"panelPowerWatts": 440
}]
}'
Response
{
"id": "clx...",
"status": "pending",
"createdAt": "2025-01-15T10:30:00.000Z"
}
Watch status transitions
Test scans progress through statuses on a fixed timeline:
| Time after creation | Status |
|---|---|
| 0 – 5 seconds | pending |
| 5 – 10 seconds | processing |
| 10+ seconds | completed |
You can either poll the scan status or wait for webhook events (if you registered a test webhook in the previous step).
Polling works exactly like production — call GET /v1/scans/:id/status and check the status field.
Poll scan status
curl https://api.enhub.nl/v1/scans/{scan-id}/status \
-H "x-api-key: zs_test_your_key_here"
Status progression
{
"id": "clx...",
"status": "pending",
"createdAt": "2025-01-15T10:30:00.000Z"
}
Retrieve the result
Once the scan status is completed, fetch the full result. The response structure is identical to a production scan result — it contains financial analysis, solar production data, battery metrics, and everything else your integration needs to parse.
The result comes from a pre-computed fixture, not the solver. The numbers are realistic but won't match your exact input parameters. This is intentional — sandbox tests your integration, not the calculation accuracy.
Get scan result
curl https://api.enhub.nl/v1/scans/{scan-id}/result \
-H "x-api-key: zs_test_your_key_here"
Try different configurations
Sandbox includes 7 fixture scenarios. The API automatically selects the best match based on your input traits. Try different combinations to see how fixture selection works:
Basic scan (no PV, no battery)
Omit existingPv, newPv, and electricBattery to get the simplest fixture.
With battery storage
Add electricBattery to your request to match a fixture that includes battery analysis.
With a custom studio profile
Set customProfileId to any string value. The profile won't be loaded from the database — sandbox uses a bundled fixture profile instead. Your input is still validated, but a 404 for a non-existent profile ID is silently ignored.
Validation errors are always real. If your DTO is structurally invalid, you'll get the same 400 Bad Request you'd see in production — even in sandbox.
With battery
curl -X POST https://api.enhub.nl/v1/scans \
-H "x-api-key: zs_test_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"yearlyKwh": 3500,
"goal": "cost-savings",
"inputMethod": "known",
"location": {
"lat": 52.09,
"lon": 5.12
},
"newPv": [{
"maxPanelCount": 12,
"panelPowerWatts": 440
}],
"electricBattery": {
"capacityKwh": 10,
"powerKw": 5
}
}'
With existing + new PV
curl -X POST https://api.enhub.nl/v1/scans \
-H "x-api-key: zs_test_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"yearlyKwh": 4500,
"goal": "cost-savings",
"inputMethod": "known",
"location": {
"lat": 52.09,
"lon": 5.12
},
"existingPv": [{
"panelCount": 8,
"panelPowerWatts": 350
}],
"newPv": [{
"maxPanelCount": 6,
"panelPowerWatts": 440
}]
}'