Skip to content

API reference/phone

Download a bulk phone job's results

GET/api/v1/phone/bulk/{jobId}/results

authentication
Secret API key as a bearer token
billing
Free.

The result file as CSV: one row per input number, in input order, with the number as submitted, every field of the single-number response as a column except four a run never fills (range_seen_by_you, client_velocity and destination_new_for_you, because a run is not counted in your own traffic, and in_call_window, because a run takes no call window; the live-check fields stay empty unless the run asked for the check, and local_time and utc_offsets are the clock when the row was processed), the credits it cost, whether the suppression list answered it (suppressed), and whether an earlier row of the list already held the same number in any spelling (is_duplicate, with duplicate_of_row naming that row's 1-based position in the list, header not counted; a repeat is looked up once and is free). Time zones, UTC offsets and sources are joined by ; (sources as dataset@version), risk signals as signal:weight pairs. Fields containing spaces or commas are quoted. Add ?variant= to download only some rows: valid or invalid; one risk level (low, medium, high); deduped, the valid rows that are the first in the list to hold their number; mobile, the valid rows whose line_type is mobile — most North American numbers answer fixed_line_or_mobile because mobiles and landlines share blocks there, and are not in it; ok, the rows your phone policy answered ok_to_send true for; or blocked, the rows it held back, with blocked_by saying why. The default full returns everything. Available once the job is completed, or cancelled or failed with at least one processed row — those rows were billed, so they stay downloadable. A job still running answers 409 RESULTS_NOT_READY, a cancelled job with nothing processed 409 JOB_CANCELLED, and a failed job with nothing processed 409 JOB_FAILED.

format=json answers the same rows, filtered the same way, as objects keyed by the result file's own header instead of a CSV attachment: { "success": true, "data": { "job": …, "results": [ … ] }, "meta": { "variant", "request_id" } }. It is streamed row by row exactly as the CSV is, so a 100,000-row run costs no more to answer either way. A CSV cell carries no type, so only the two the writer encodes come back typed: an empty cell is null and true/false are booleans. Everything else is the string as written — a number is never silently made numeric, because postcodes, phone numbers and house numbers lose their leading zeros that way — and the columns of an uploaded file come back exactly as they were uploaded.

Parameters

name in type description
jobIdrequiredpathinteger
variantquerystringRestrict the file to the valid or invalid rows, one risk level, the first row of each valid number (`deduped`), the valid mobile rows, or the rows your phone policy lets through (`ok`) or holds back (`blocked`). Defaults to full. One of: full, valid, invalid, low, medium, high, deduped, mobile, ok, blocked.
formatquerystring`csv` streams the file as an attachment; `json` streams the same rows as objects. One of: csv, json. Default: csv.

Example request

curl https://spaw.co/api/v1/phone/bulk/{jobId}/results \
  -H "Authorization: Bearer sk_live_…"
const response = await fetch('https://spaw.co/api/v1/phone/bulk/{jobId}/results', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer sk_live_…',
  },
});
const result = await response.json();
import requests

response = requests.get(
    'https://spaw.co/api/v1/phone/bulk/{jobId}/results',
    headers={'Authorization': 'Bearer sk_live_…'},
)
result = response.json()
$ch = curl_init('https://spaw.co/api/v1/phone/bulk/{jobId}/results');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['Authorization: Bearer sk_live_…'],
]);
$result = json_decode(curl_exec($ch), true);
from spaw import Client

client = Client('sk_live_…')
result = client.download_phone_bulk_results(job_id)
import Spaw from 'spaw';

const spaw = new Spaw({ apiKey: 'sk_live_…' });
const result = await spaw.downloadPhoneBulkResults(jobId);
use Spaw\Client;

$spaw = new Client('sk_live_…');
$result = $spaw->downloadPhoneBulkResults($jobId);

This endpoint has no console on its page. It answers with records from your own account, often by an id only you have, rather than about a value you can type here. The dashboard lists the same rows.

Responses

200The result rows, as a CSV attachment or as streamed JSON.

phone,valid,reason,e164,national,international,rfc3966,country,country_name,calling_code,continent,is_eu,currency,line_type,line_type_source,carrier,carrier_source,block_status,region,is_geographic,timezones,extension,is_fictional,is_virtual,is_disposable,disposable_seen_at,risk_score,risk_level,risk_signals,hlr_checked,reachable,ported,current_carrier,roaming,carrier_code,carrier_brand,block_pooled,block_assigned_at,region_level,local_time,utc_offsets,did_you_mean,complaints_90d,complaints_last_at,ok_to_send,blocked_by,sources,credits_used,suppressed,is_duplicate,duplicate_of_row
"+1 415 555 0142",true,,+14155550142,"(415) 555-0142","+1 415-555-0142",tel:+1-415-555-0142,US,"United States",1,NA,false,USD,fixed_line_or_mobile,numbering_plan,,,reserved,"San Francisco, CA",true,America/Los_Angeles,,true,false,false,,100,high,fictional:80;reserved_block:40,false,,,,,,,,,locality,2026-09-24T14:20:06-07:00,-07:00,,0,,false,fictional,[email protected];nanpa-co-codes@2026-09-24;fictional-ranges@2026-09-25;disposable-phone-numbers@2026-09-24;us-complaint-reports@2026-09-24,1,false,false,

401The key is missing, malformed, or revoked.

{
    "success": false,
    "error": {
        "code": "UNAUTHENTICATED",
        "message": "Provide a valid API key as a bearer token.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

404No such record on this account.

{
    "success": false,
    "error": {
        "code": "NOT_FOUND",
        "message": "No record with that id on this account.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

409The job is still running (`RESULTS_NOT_READY`), or was cancelled or failed before any row was processed (`JOB_CANCELLED`, `JOB_FAILED`).

{
    "success": false,
    "error": {
        "code": "RESULTS_NOT_READY",
        "message": "The results are not ready yet.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

429Over 5 requests per second for the key. Retry after the limit resets.

{
    "success": false,
    "error": {
        "code": "RATE_LIMITED",
        "message": "Too many requests. Retry after the limit resets.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

Error codes

Failures answer { success: false, error: { code, message, request_id } }. Each code has its own page.

markdown version·openapi.json