Download the results
Downloads a bulk job's results as a CSV or JSON file. The file keeps every original column and adds the result columns at the end.
/api/v1/api-validation/bulk/:id/download-resultParameters
Path
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The job id returned by the upload. |
Query
Both query parameters are required. Leaving either out returns a 400.
| Parameter | Type | Required | Description |
|---|---|---|---|
output | string | Yes | json returns a JSON array. Any other value (use csv) returns CSV. |
verifiedOnly | string | Yes | true returns only rows with the reason code mailbox_found. Any other value (use false) returns every row. |
When you can download
| Job status | What you get |
|---|---|
COMPLETED | The full result file. |
CANCELED | Every row. Rows that never got a result show the reason code no_response (result Risky). |
IN PROGRESS, CANCELING | A snapshot of the results so far. Rows not checked yet show the reason code processing (result Processing). |
QUEUE, FAILED | A 400 with Results are not available for this job. |
Wait for COMPLETED before you act on the file. See Get the job status.
Examples
curl -o results.csv \
"https://simplecold.com/api/v1/api-validation/bulk/b0f6c2a4-6a3e-4c55-9d1e-2f8a7c1b9e40/download-result?output=csv&verifiedOnly=false" \
-H "Authorization: Bearer YOUR_API_KEY"import { writeFile } from "node:fs/promises";
const API = "https://simplecold.com/api/v1";
const auth = { Authorization: `Bearer ${process.env.SIMPLECOLD_API_KEY}` };
async function download(id, { output = "csv", verifiedOnly = false } = {}) {
const params = new URLSearchParams({ output, verifiedOnly: String(verifiedOnly) });
const res = await fetch(`${API}/api-validation/bulk/${id}/download-result?${params}`, {
headers: auth,
});
if (!res.ok) throw new Error(`download failed: ${res.status} ${await res.text()}`);
return res.text(); // raw CSV, or a JSON array as text
}
await writeFile("results.csv", await download("b0f6c2a4-6a3e-4c55-9d1e-2f8a7c1b9e40"));import os
import requests
API = "https://simplecold.com/api/v1"
API_KEY = os.environ["SIMPLECOLD_API_KEY"]
def download(job_id, dest, output="csv", verified_only=False):
res = requests.get(
f"{API}/api-validation/bulk/{job_id}/download-result",
headers={"Authorization": f"Bearer {API_KEY}"},
params={"output": output, "verifiedOnly": "true" if verified_only else "false"},
timeout=300,
)
res.raise_for_status()
with open(dest, "wb") as f:
f.write(res.content)
download("b0f6c2a4-6a3e-4c55-9d1e-2f8a7c1b9e40", "results.csv")<?php
const API = 'https://simplecold.com/api/v1';
function download(string $jobId, string $dest, string $output = 'csv', bool $verifiedOnly = false): void
{
$query = http_build_query(['output' => $output, 'verifiedOnly' => $verifiedOnly ? 'true' : 'false']);
$file = fopen($dest, 'wb');
$ch = curl_init(API . "/api-validation/bulk/$jobId/download-result?$query");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('SIMPLECOLD_API_KEY')],
CURLOPT_FILE => $file,
CURLOPT_FAILONERROR => true,
CURLOPT_TIMEOUT => 300,
]);
$ok = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
fclose($file);
if (!$ok) {
unlink($dest);
throw new RuntimeException("download failed: $status");
}
}
download('b0f6c2a4-6a3e-4c55-9d1e-2f8a7c1b9e40', 'results.csv');curl -o verified.json \
"https://simplecold.com/api/v1/api-validation/bulk/b0f6c2a4-6a3e-4c55-9d1e-2f8a7c1b9e40/download-result?output=json&verifiedOnly=true" \
-H "Authorization: Bearer YOUR_API_KEY"A successful download is the file itself, not the JSON envelope. It is sent as application/octet-stream with Content-Disposition: attachment; filename="<job-id>-validation-result.csv" (or .json).
File format
Every download uses this format, including downloads of jobs that finished before 2026-10-27.
CSV
Your original columns come first, with their exact header text and casing, in the same order. These columns are added after them, in this order:
| Column | Description |
|---|---|
Result | Deliverable, Risky, Undeliverable, or Processing while the job is still running. |
Reason | One plain sentence explaining the result. The same text as statusMessage in the API. |
Reason code | The reason code, for example mailbox_not_found. See Statuses and categories. |
Suggestion | The corrected address for possible_typo, otherwise empty. |
Retry later | Yes when a later re-check may change the result (blocked_by_server, no_response, server_error), otherwise No. |
Domain | The domain part of the address. |
Mail server country | Country code where the domain's mail servers appear to be. |
Website country | Country code where the domain's website appears to be. |
Website language | Language of the domain's website. |
First Name,Email,Company,Result,Reason,Reason code,Suggestion,Retry later,Domain,Mail server country,Website country,Website language
"Jane","[email protected]","Acme","Deliverable","The mailbox exists and accepts mail.","mailbox_found","","No","acme.com","US","US","EN"
"Bob","[email protected]","Initech","Undeliverable","Possible typo. Did you mean [email protected]?","possible_typo","[email protected]","No","gmial.com","","",""If one of your columns already has one of these names (ignoring case), your column keeps its name and ours gets (SimpleCold) added, for example Domain (SimpleCold).
Cells that start with =, +, - or @ get a leading apostrophe ('), so a spreadsheet app shows them as text instead of running them as formulas.
JSON
The same rows as an array of objects. Your original fields keep their original keys (the exact header text). These keys are added after them:
| Key | Type | Description |
|---|---|---|
result | string | deliverable, risky, undeliverable, or pending while the job is still running. |
reasonCode | string | The reason code, for example mailbox_not_found. |
reason | string | One plain sentence explaining the result. |
suggestion | string | null | The corrected address for possible_typo, otherwise null. |
retryLater | boolean | true when a later re-check may change the result. |
domain | string | The domain part of the address. |
mailServerCountry | string | null | Country code where the domain's mail servers appear to be. |
websiteCountry | string | null | Country code where the domain's website appears to be. |
websiteLanguage | string | null | Language of the domain's website. |
If one of your keys clashes with one of these, ours gets (SimpleCold) added, the same as in CSV.
[
{
"First Name": "Jane",
"Email": "[email protected]",
"Company": "Acme",
"result": "deliverable",
"reasonCode": "mailbox_found",
"reason": "The mailbox exists and accepts mail.",
"suggestion": null,
"retryLater": false,
"domain": "acme.com",
"mailServerCountry": "US",
"websiteCountry": "US",
"websiteLanguage": "EN"
}
]verifiedOnly=true is handy when you only want a send-ready list: it keeps only mailbox_found rows. Most teams download every row instead, because the risky and undeliverable rows tell you which contacts to suppress and which domains accept all mail.
Errors
| HTTP | Message | Cause |
|---|---|---|
400 | Please check your input and try again. | output or verifiedOnly is missing. The body is { "success": false, "message": ..., "data": [...] }. |
400 | Job not found. | No job with this id in your organization. |
400 | Results are not available for this job. | The job is QUEUE or FAILED. |
401 | Invalid API key and others | See authentication errors. |
429 | Rate limit exceeded. Maximum 30 downloads per minute per API key. | Over 30 downloads per minute with this API key. Wait Retry-After seconds. |