DataSkip 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

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.

Prefer a spec? openapi.json (OpenAPI 3.1) · developers.md (this page in markdown) · llms.txt (site summary with usage guidance)

01 / CLI

The CLI: skip trace from your terminal

The DataSkip CLI ships in the official npm package skiptrace (Node 18+, Apache-2.0). 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.

skiptrace · node 18+
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
  }
}

03 / REST API

The DataSkip 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.

HTTP · Bearer auth

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"
    }'
{
  "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"]
}
StatusMeaning
200Lookup ran. Check found: a miss is still 200 with contact: null and no charge.
400Missing or invalid address in the JSON body.
401Missing or invalid API key.
402Insufficient balance to cover a match. Top up in the dashboard.
429Rate 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" }
    }'
  1. Create a job with POST /api/user/skip-trace and your CSV's column mapping (the address, city, state, and ZIP columns must all be mapped; owner first and last name columns are optional). The response returns a jobId and an uploadUrl.
  2. Upload the raw CSV. Presigned uploads PUT the file to uploadUrl with Content-Type: text/csv and then call the uploaded endpoint; direct uploads POST the file as multipart form data.
  3. Poll the job until its status is preview. The preview reports total rows, matched rows, and the total charge in cents.
  4. 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.
  5. Poll until completed, then fetch the enriched CSV from downloadUrl. Matched rows gain owner names, phones, and emails; unmatched rows come back unchanged.
EndpointWhat it does
POST /api/user/skip-traceCreate a bulk job. Returns jobId and uploadUrl.
POST /api/user/skip-trace/{jobId}/uploadedNotify 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}/confirmConfirm the previewed cost and charge the balance.
POST /api/user/skip-trace/{jobId}/cancelCancel before confirming. No charge.
GET /api/user/skip-traceList 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.

05 / Errors

Errors: always JSON, always an error message

Every error response from the DataSkip API is JSON with an error string that names the problem, and on 400 the invalid field. A 402 adds balanceRequired (the cents needed to run the request); a CSV job confirm can add dailyMissLimit when the account is over its daily unmatched-list cap. A miss is not an error: it is HTTP 200 with found: false.

{ "error": "Insufficient balance", "balanceRequired": 4 }
StatusMeaningWhat to do
400Body is not JSON, or a required field is missing or malformed; error names it.Fix the field and retry.
401Missing, malformed, invalid, or revoked API key, or the account is not active.Send Authorization: Bearer <key>; create keys in the dashboard under Settings, API.
402The balance cannot cover the request; balanceRequired is in cents.Top up at app.dataskip.io, then retry.
404CSV jobs only: no job with that jobId on this account.Check the id returned when the job was created.
409CSV jobs only: the job is not in a status that allows the action; error names the current status.Poll the job and act on its current status.
429Rate limited (Retry-After header, seconds). A CSV job confirm may also return the daily unmatched-list cap (dailyMissLimit in the body).Wait for Retry-After; for the daily cap, clean the list and retry the next day.
500Server error. A failed lookup is never charged.Retry later; email support@dataskip.io if it persists.

Always call the documented method: the lookup endpoints are POST only, and an unsupported method or an unknown path returns the app's own 405 or 404 page rather than an API error body, so check the path and method against openapi.json before calling.

06 / Rate limits

Rate limits: per account, per minute

EndpointLimit
POST /api/v1/skip-trace500 lookups per minute per account
POST /api/v1/skip-trace-bulk250 requests per minute per account (100 addresses each)
Single and bulk, unmatched fair useMisses are free up to 250 net unmatched addresses per UTC day, and four matches forgive one miss, so a list with a normal hit rate never reaches it. Past that, further misses are charged at the per-lookup rate instead of being blocked.
POST /api/contact5 messages per 15 minutes per IP

A 429 carries a Retry-After header in seconds: wait that long, then retry. Never loop the single-lookup endpoint over a list; the bulk endpoint takes 100 addresses per call and the CSV pipeline takes 250,000 per job.

07 / Versioning

Versioning and deprecation policy

The DataSkip API is versioned in the URL path: /api/v1 is the current stable version, and openapi.json describes it. Backward-compatible changes (new endpoints, new optional request fields, new response fields) ship in place without a version change, so clients should ignore fields they do not recognize. Breaking changes ship as a new path version and never change how v1 behaves.

When a version or endpoint is scheduled for retirement, DataSkip announces it at least 6 months before the retirement date. During that notice period every response from the affected version carries a Deprecation header (RFC 9745) and a Sunset header (RFC 8594) with the retirement date, plus a Link with rel="deprecation" pointing at the announcement. The same dates are published on this page, in developers.md, and in llms.txt. Nothing is deprecated today.

Ship it in an
afternoon.

Get Your API Key

4¢ per match · misses free · support@dataskip.io