Developers / CLI · SDK · REST
Skip trace in
one command.
Turn a US property address into the owner's name, phone numbers with DNC flags, emails, and mailing address. One npm package gives you a CLI and a typed JS/TS SDK; the REST API works from any language. It is the same flat 4¢ per match as the dashboard rate sheet, and misses are always free. For pricing context and how to compare providers, start with the skip tracing API overview.
npm install -g skiptrace
Get an API Key
- 4¢per match
- $0per miss
- 1package, 3 ways in
For AI Agents
Vibe coder?
Copy a ready-made brief with the endpoint, auth, SDK, and error handling, then paste it into your AI coding agent and let it wire up DataSkip for you.
- Claude
- Codex
- Cursor
Everything your agent needs, in one paste.
01 / CLI
The CLI: skip trace from your terminal
Create an API key in the dashboard (Settings → API), save it once, then trace addresses straight from your shell. Exit codes are script-friendly: 0 match, 1 miss, 2 usage error, 3 API error.
$ skiptrace config set-key pc_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
$ skiptrace "123 Example St, Phoenix AZ 85001"
$ skiptrace lookup "44 Pine St" --city Bridgewater --state MA --zip 02324
$ skiptrace "123 Main St, Austin TX 78701" --json | jq '.phones'
02 / npm package
The SDK: skip trace from your code
The skiptrace package imports as a typed library. The API key falls back to the SKIPTRACE_API_KEY env var, then the saved CLI config, so new SkipTrace() with no arguments just works. It also ships skipBulk for up to 100 addresses in one call.
import { SkipTrace, ApiError } from 'skiptrace';
const st = new SkipTrace({ apiKey: process.env.SKIPTRACE_API_KEY });
try {
const result = await st.skip('123 Example St, Phoenix AZ 85001');
if (result.found) {
console.log(result.contact.fullName, result.phones, result.emails);
}
} catch (err) {
if (err instanceof ApiError && err.status === 402) {
// insufficient balance
}
}
import { SkipTrace, ApiError } from 'skiptrace';
const st = new SkipTrace({ apiKey: process.env.SKIPTRACE_API_KEY });
try {
const result = await st.skip('123 Example St, Phoenix AZ 85001');
if (result.found) {
console.log(result.contact.fullName, result.phones, result.emails);
}
} catch (err) {
if (err instanceof ApiError && err.status === 402) {
// insufficient balance
}
}
import { SkipTrace, ApiError } from 'skiptrace';
import type { LookupInput, LookupResponse } from 'skiptrace';
const st = new SkipTrace({ apiKey: process.env.SKIPTRACE_API_KEY });
const input: LookupInput = { address: '44 Pine St', city: 'Bridgewater', state: 'MA', zip: '02324' };
const result: LookupResponse = await st.skip(input);
if (result.found && result.contact) {
console.log(result.contact.fullName, result.phones, result.emails);
}
03 / REST API
The REST API: skip trace from anywhere
One endpoint, any language, with your API key as a Bearer token. A hit charges 4¢ and returns the full contact; a miss returns found: false and charges nothing.
POST https://app.dataskip.io/api/v1/skip-trace
$ curl -X POST https://app.dataskip.io/api/v1/skip-trace \
-H "Authorization: Bearer pc_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"address": "44 Pine St",
"city": "Bridgewater",
"state": "MA",
"zip": "02324"
}'
$ curl -X POST https://app.dataskip.io/api/v1/skip-trace \
-H "Authorization: Bearer pc_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"address": "44 Pine St",
"city": "Bridgewater",
"state": "MA",
"zip": "02324"
}'
import os
import requests
resp = requests.post(
"https://app.dataskip.io/api/v1/skip-trace",
headers={"Authorization": f"Bearer {os.environ['SKIPTRACE_API_KEY']}"},
json={"address": "44 Pine St", "city": "Bridgewater", "state": "MA", "zip": "02324"},
)
data = resp.json()
if data["found"]:
print(data["contact"]["fullName"], data["phones"], data["emails"])
<?php
$ch = curl_init('https://app.dataskip.io/api/v1/skip-trace');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('SKIPTRACE_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['address' => '44 Pine St', 'city' => 'Bridgewater', 'state' => 'MA', 'zip' => '02324']),
]);
$data = json_decode(curl_exec($ch), true);
if ($data['found']) {
echo $data['contact']['fullName'];
}
{
"success": true,
"found": true,
"charged": 4,
"contact": {
"firstName": "J•••",
"lastName": "C•••••",
"fullName": "J••• C•••••",
"propertyAddress": "44 Pine St",
"propertyCity": "Bridgewater",
"propertyState": "MA",
"propertyZip": "02324",
"mailingAddress": "44 Pine St",
"mailingCity": "Bridgewater",
"mailingState": "MA",
"mailingZip": "02324"
},
"phones": [
{ "number": "(•••) •••-0182", "type": "mobile", "dnc": false },
{ "number": "(•••) •••-4415", "type": "landline", "dnc": true }
],
"emails": ["j•••••@•••••.com"]
}
| Status | Meaning |
|---|---|
| 200 | Lookup ran. Check found: a miss is still 200 with contact: null and no charge. |
| 400 | Missing or invalid address in the JSON body. |
| 401 | Missing or invalid API key. |
| 402 | Insufficient balance to cover a match. Top up in the dashboard. |
| 429 | Rate limited. Retry after the indicated delay. |
04 / Bulk API
The bulk API: 100 per request, or a whole CSV
Skip trace up to 100 addresses in one API call, or a whole CSV in one job (never loop the single-lookup endpoint, which is limited to 500 lookups per minute per account). Either way you only pay for hits: misses are refunded automatically, even in bulk. The CSV pipeline handles bigger lists, up to 250,000 records, and previews the matched count and exact cost before anything is charged.
POST https://app.dataskip.io/api/v1/skip-trace-bulk
{
"addresses": [
{ "address": "123 Sample Ave", "city": "Anytown", "state": "CA", "zip": "90210" },
{ "address": "456 Demo Blvd", "zip": "90211" }
]
}
{
"success": true,
"total": 2,
"matched": 1,
"totalCharged": 4,
"results": [
{ "found": true, "charged": 4, "contact": { "fullName": "J••• C•••••" }, "phones": ["..."], "emails": ["..."] },
{ "found": false, "charged": 0, "contact": null, "phones": [], "emails": [] }
]
}
Up to 100 entries per request; only the address field is required per entry, and results come back in request order with the same shape as a single lookup. Billing matches single lookups: 4¢ per match, misses free. The worst case (every entry matching) is charged up front, a 402 with balanceRequired means the balance cannot cover the batch, and non-matches are refunded automatically when the request completes, so the net charge is always matches only. Rate limit: 250 bulk requests per minute per account, and 429 responses carry a Retry-After header.
await st.skipBulk(['123 Example St, Phoenix AZ 85001', { address: '44 Pine St', city: 'Bridgewater', state: 'MA', zip: '02324' }]);
In the SDK, skipBulk is on the client and as a one-shot export, fully typed (BulkLookupResponse). Free-form strings and structured objects mix in the same array.
For bigger lists, the CSV job pipeline takes one file of up to 250,000 addresses. Jobs created through the API also appear in the dashboard, and skiptrace bulk in the CLI runs this same pipeline.
$ curl -X POST https://app.dataskip.io/api/user/skip-trace \
-H "Authorization: Bearer pc_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"filename": "leads.csv",
"columnMapping": { "address": "Property Address", "city": "City", "state": "State", "zip": "Zip" }
}'
- Create a job with
POST /api/user/skip-traceand your CSV's column mapping (only the address column is required). The response returns ajobIdand anuploadUrl. - Upload the raw CSV. Presigned uploads PUT the file to
uploadUrlwithContent-Type: text/csvand then call theuploadedendpoint; direct uploads POST the file as multipart form data. - Poll the job until its status is
preview. The preview reports total rows, matched rows, and the total charge in cents. - Confirm to run the charge, or cancel free of charge. Only matched rows are billed, at 4¢ each; a 402 response means the balance cannot cover the job.
- Poll until
completed, then fetch the enriched CSV fromdownloadUrl. Matched rows gain owner names, phones, and emails; unmatched rows come back unchanged.
| Endpoint | What it does |
|---|---|
| POST /api/user/skip-trace | Create a bulk job. Returns jobId and uploadUrl. |
| POST /api/user/skip-trace/{jobId}/uploaded | Notify after a presigned upload finishes. |
| GET /api/user/skip-trace/{jobId} | Job status. Also refreshes an expired downloadUrl on completed jobs. |
| POST /api/user/skip-trace/{jobId}/confirm | Confirm the previewed cost and charge the balance. |
| POST /api/user/skip-trace/{jobId}/cancel | Cancel before confirming. No charge. |
| GET /api/user/skip-trace | List the account's 50 most recent jobs. |
Job statuses: pending · uploaded · processing · preview · confirmed · generating · completed · failed · cancelled. A job never charges before the preview is confirmed.
Ship it in an
afternoon.
Get Your API Key
4¢ per match · misses free · support@dataskip.io