Skip to content
SimpleCold

API docs

Get started

Sandbox (test mode)

On this page

A test API key runs every endpoint against the sandbox. You get the same request and response shapes as live, but the result is decided by the address you send, nothing is checked for real, and no credits are used. Test keys work with a balance of zero.

Use the sandbox to build and test your whole integration: every result category, processing and polling, rate-limit and server errors, bulk jobs with progress, downloads and webhooks.

Test keys

When you create an API key, choose Live or Test. The mode is fixed for the life of the key, and the full key starts with it:

ModeKey starts withWhat it does
Livesc_live_Real verification. Uses credits.
Testsc_test_Sandbox results. No credits, ever.

The dashboard shows the prefix in the key hint (sc_test_…a1b2) and a Test chip next to test keys. Keys created before modes existed have no prefix; they are live keys and keep working unchanged.

Use a test key exactly like a live one:

Request
curl -G "https://simplecold.com/api/v1/api-validation/single" \
  --data-urlencode "[email protected]" \
  -H "Authorization: Bearer sc_test_YOUR_TEST_KEY"

How addresses are matched

  • The result depends on the part before the @. Case and +tags are ignored, so [email protected] behaves like [email protected].
  • Any domain works. The domain only picks realistic mxLocation, siteLocation and siteLanguage values (the same domain always gets the same values, and country domains like .de get that country).
  • Anything not listed is deliverable.
  • Format checks are real. A malformed address is still rejected with 400 before the sandbox runs, and the real format and typo checks still apply: [email protected] comes back as a possible typo with a suggestion. Some providers also have address rules (Gmail addresses can't contain -, for example), so use your own domain or example.com for the special addresses below.
  • The fixed test emails ([email protected] and the rest) return their usual results with a test key too.

Sandbox addresses

AddressstatuscategoryfailTypeTiming
deliverable@… (or any unlisted address)mailbox_founddeliverablenoneInstant
undeliverable@…, invalid@…, nosuchuser@…mailbox_not_foundundeliverablepermanentInstant
disabled@…mailbox_disabledundeliverablepermanentInstant
nomx@…no_mail_serverundeliverablepermanentInstant
catchall@…, acceptall@…accept_allriskynoneInstant
role@…, info@…, sales@…, support@…role_addressriskynoneInstant
disposable@…disposableriskynoneInstant
antispam@…blocked_by_serverriskytemporaryInstant
unverifiable@…no_responseriskytemporaryInstant
…@gmial.com, …@yaho.com, …@hotmial.compossible_typo + suggestionundeliverablepermanentInstant
slow@…processing, then mailbox_foundpending, then deliverablenoneFinal result about 20 s later
slow-undeliverable@…processing, then mailbox_not_foundpending, then undeliverablepermanentFinal result about 20 s later
slow-risky@…processing, then accept_allpending, then riskynoneFinal result about 20 s later
error-429@…–––Standard 429 RATE_LIMITED response
error-500@…–––Standard 500 response
error-timeout@…processing, then mailbox_foundpending, then deliverablenoneAnswers after about 15 s; final result about 20 s later

statusMessage is the same plain sentence a live result with that status has. Statuses and categories explains each one.

Every sandbox response has "sandbox": true in each result (and in bulk job and status responses). Live responses never have the field, so you can assert on it in tests.

Example responses

GET /api-validation/[email protected] · 200
{
  "code": "SUCCESS",
  "message": "Successfully performed operation.",
  "payload": {
    "email": "[email protected]",
    "domain": "example.com",
    "status": "accept_all",
    "statusMessage": "The domain accepts mail for any address, so this mailbox can't be confirmed.",
    "category": "risky",
    "failType": "none",
    "mxLocation": "IE",
    "siteLocation": "US",
    "siteLanguage": "EN",
    "suggestion": null,
    "sandbox": true
  },
  "requestId": "5f3a..."
}
POST /api-validation/batch · 200 (payload)
[
  {
    "email": "[email protected]",
    "domain": "example.com",
    "status": "mailbox_not_found",
    "statusMessage": "This mailbox does not exist.",
    "category": "undeliverable",
    "failType": "permanent",
    "mxLocation": "IE",
    "siteLocation": "US",
    "siteLanguage": "EN",
    "suggestion": null,
    "sandbox": true
  },
  {
    "email": "[email protected]",
    "domain": "gmial.com",
    "status": "possible_typo",
    "statusMessage": "Possible typo. Did you mean [email protected]?",
    "category": "undeliverable",
    "failType": "permanent",
    "mxLocation": null,
    "siteLocation": null,
    "siteLanguage": null,
    "suggestion": "[email protected]",
    "sandbox": true
  },
  {
    "email": "[email protected]",
    "domain": "example.com",
    "status": "processing",
    "statusMessage": "Still checking this mailbox. Ask again in about a minute; you will not be charged again.",
    "category": "pending",
    "failType": "none",
    "mxLocation": null,
    "siteLocation": null,
    "siteLanguage": null,
    "suggestion": null,
    "sandbox": true
  }
]

Polling with slow@

slow@, slow-undeliverable@, slow-risky@ and error-timeout@ answer processing first, the way a slow mail server does. Collect the final result from /single/result, exactly as you would in live mode:

Poll until the result is final
curl -G "https://simplecold.com/api/v1/api-validation/single/result" \
  --data-urlencode "[email protected]" \
  -H "Authorization: Bearer sc_test_YOUR_TEST_KEY"
  • Before about 20 seconds have passed, it answers processing again.
  • After that, it answers the final result once. A further call answers 404 "No check in progress for this email.", as in live mode.
  • If you subscribe to result.ready, the final result is also pushed to your webhook about 20 seconds after the request (see Webhooks).

Simulated errors

These let you test error handling without hitting a real limit or waiting for an outage. They apply to single and batch requests; in a batch, one such address makes the whole request answer with the error, as a real one would.

  • error-429@… answers the standard 429 body and headers (Retry-After, RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset, RateLimit-Policy). Nothing is counted against any limit.

    429
    {
      "code": "RATE_LIMITED",
      "message": "Rate limit exceeded. Maximum 100 sandbox requests per minute per test API key.",
      "data": {
        "policy": "api.sandbox.single",
        "retryAfterSeconds": 10,
        "limit": 100,
        "scope": "apiKey",
        "blocked": false
      },
      "status": 429,
      "error": "Rate limit exceeded. Maximum 100 sandbox requests per minute per test API key.",
      "sandbox": true
    }
  • error-500@… answers the standard 500 body:

    500
    { "code": "FAILURE", "message": "Internal Server Error", "data": null, "sandbox": true }
  • error-timeout@… holds the request for about 15 seconds and then answers processing, like a check that ran past the response deadline. Its final result (deliverable) is available from /single/result about 20 seconds later. Use it to test your client timeout: if your timeout is shorter than 15 seconds, your code sees its own timeout instead.

Bulk in the sandbox

Upload a CSV with a test key and you get a real job:

  • It is capped at 1,000 rows (after duplicates are removed). Larger files answer 400.
  • Each row gets the result of its address from the table above. In a file, slow… and error… addresses get their final result (the 429/500/timeout simulations are request-level only).
  • The job runs for 30 to 60 seconds depending on its size. Status shows real progress the whole time: totalProcessed, progressPercent, etaSeconds, lastUpdatedAt and nextUpdateAt move every 5 seconds, then the status becomes COMPLETED.
  • Downloads work exactly as in live mode: CSV or JSON, all rows or verifiedOnly. While the job runs, rows not reached yet show as processing / pending.
  • totalCreditsUsed is 0. Sandbox jobs never count toward usage, credit history or billing.
  • In the dashboard, sandbox lists are hidden from Bulk verification unless you turn on Test mode, and they carry a Test chip.
  • A test key only sees sandbox jobs and a live key only sees live jobs; asking for the other kind answers 400 "Job not found.".
POST /api-validation/bulk · 200 (payload, shortened)
{
  "id": "c3d0e8a2-7f41-4f0b-9a55-1d2f7e4c9b10",
  "name": "sandbox-leads",
  "totalEmails": 120,
  "totalValidEmails": 0,
  "totalCreditsUsed": 0,
  "status": "IN PROGRESS",
  "sandbox": true
}
GET /api-validation/bulk/:id/status · 200 (payload, 20 s in)
{
  "status": "IN PROGRESS",
  "sandbox": true,
  "totalProcessed": 70,
  "progressPercent": 58.3,
  "etaSeconds": 14,
  "lastUpdatedAt": "2026-10-27T10:00:20.000Z",
  "nextUpdateAt": "2026-10-27T10:00:25.000Z",
  "queuedChunks": 1
}

Webhooks

Sandbox activity sends the same events to your organisation's webhook endpoints: result.ready for slow… and error-timeout addresses, bulk.progress while a sandbox job runs, and bulk.finished when it ends (with creditsUsed: 0). The only difference is the envelope's livemode field:

Sandbox event (shortened)
{
  "id": "evt_9b1f0c2d3e4a5b6c7d8e9f00",
  "type": "bulk.finished",
  "created": 1793001234,
  "orgId": "6a1c4f0e-2a4b-4b6e-9c1d-0e5f4a3b2c1d",
  "livemode": false,
  "data": { "jobId": "c3d0e8a2-7f41-4f0b-9a55-1d2f7e4c9b10", "status": "completed", "creditsUsed": 0 }
}

Live events have "livemode": true. Check it in your handler so test traffic never touches production data. In the dashboard's delivery log, test deliveries carry a test chip.

Limits

The sandbox has its own budgets, separate from your live ones: sandbox traffic never uses up live limits, and live traffic never uses up the sandbox's.

Endpoint (test key)Per test keyPer organisation
single and single/result (shared)100 per minute300 per minute
batch10 per minute30 per minute
bulk upload5 per hour15 per hour
bulk/:id/status120 per minute360 per minute
bulk/:id/download-result30 per minute90 per minute

The batch size limit (50 addresses) and the CSV format rules are the same as live. You can have up to 5 test keys, in addition to 5 live keys.

Going live

  1. Create a live key (sc_live_…) in the dashboard, and keep the test key for your test environment.
  2. Swap the key through configuration (an environment variable), not code. Nothing else changes: same base URL, same endpoints, same shapes.
  3. Remove the sandbox assumptions. Live results never have sandbox, live webhooks have "livemode": true, and real processing results can take longer than 20 seconds.
  4. Make sure you have credits. Live checks reserve credits when a request arrives; see Credits.
  5. Keep sandbox addresses out of production. They are ordinary addresses to a live key and will be checked for real.
Sandbox (test mode) | SimpleCold API docs