API: Bulk Skip-Trace Jobs
Submit up to 10,000 rows as JSON or CSV, poll status, and fetch paginated results.
POST /jobs runs asynchronous skip tracing on up to 10,000 rows per job — the API counterpart of bulk skip tracing in the dashboard. You submit rows, get a job_id back immediately, then either poll for completion or receive a signed webhook. Larger lists remain a dashboard flow.
Three ways to submit rows
All three input modes hit the same endpoint and return the same response.
1. JSON rows
Send a rows array (1–10,000 entries). Each row uses the same fields as POST /trace: address, city, and state required; first_name, last_name, zip, mailing_address/mailing_city/mailing_state/mailing_zip, county, apn, and owner_type optional. Put LLC names in last_name — entity rows bill at your llc_trust_trace rate automatically.
curl -X POST https://data.acquiredcrm.com/api/v1/jobs \
-H "Authorization: Bearer ad_live_XXXX" \
-H "Content-Type: application/json" \
-d '{
"filename": "my-list",
"rows": [
{"first_name":"John","last_name":"Doe","address":"123 Main St","city":"Phoenix","state":"AZ","zip":"85001"},
{"last_name":"Sunrise Holdings LLC","address":"456 Oak Ave","city":"Portland","state":"OR"}
],
"add_ons": ["dnc"],
"callback_url": "https://example.com/hooks/acquired-data"
}'2. Raw CSV body
Send your export file as-is with Content-Type: text/csv. Column headers are matched case- and punctuation-insensitively ("Property Address" or "Address", "First Name", "Mailing Address", "APN", "Owner Type", and so on), and unrecognized columns are ignored — so Propwire and PropStream exports work unmodified. Columns mapping to address, city, and state are required. Because the body is the file, job options move to query parameters: filename, add_ons (comma-separated), and callback_url (URL-encoded).
curl -X POST "https://data.acquiredcrm.com/api/v1/jobs?filename=my-list&add_ons=dnc,phone_verification&callback_url=https%3A%2F%2Fexample.com%2Fhooks%2Facquired-data" \
-H "Authorization: Bearer ad_live_XXXX" \
-H "Content-Type: text/csv" \
--data-binary @propwire_export.csv3. Multipart file upload
Put the CSV in a file form field. Options can ride along as form fields (which take precedence) or query parameters, and filenamedefaults to the uploaded file's name.
curl -X POST https://data.acquiredcrm.com/api/v1/jobs \
-H "Authorization: Bearer ad_live_XXXX" \
-F "file=@propstream_export.csv" \
-F "add_ons=dnc" \
-F "callback_url=https://example.com/hooks/acquired-data"Options and add-ons
filename— label for the job and result file.add_ons— any ofdnc,phone_verification,litigator,deceased,tested_emails,llc_trust,data_verification,family_contacts. Each is priced per record on top of the trace rate — seeGET /pricingand the add-ons guide.callback_url— a public https URL POSTed on completion (no IP literals or local hostnames). See webhooks.
The 202 response
HTTP 202
{
"job_id": "bc3e1413-9a7d-4a52-b1f0-3c8e5d6f7a89",
"status": "processing",
"total_rows": 2,
"credits_charged": 8,
"add_ons": ["dnc"],
"environment": "production"
}Only one bulk job can run per account at a time — submitting another while one is processing returns 429 job_in_progress with a Retry-After header. Other errors: 402 insufficient_credits when your balance is below the job cost, and 422 validation_error for bad rows, unknown fields, more than 10,000 rows, an unsafe callback_url, or a CSV missing address/city/state columns.
Listing and polling jobs
GET /jobs lists every trace job on the account (dashboard and API), newest first. It takes page (default 1), per_page (default 20, max 100), and an optional source filter (api or app). GET /jobs/{id} returns one job with progress:
curl https://data.acquiredcrm.com/api/v1/jobs/bc3e1413-9a7d-4a52-b1f0-3c8e5d6f7a89 \
-H "Authorization: Bearer ad_live_XXXX"{
"job_id": "bc3e1413-9a7d-4a52-b1f0-3c8e5d6f7a89",
"status": "complete",
"progress": { "completed_batches": 1, "total_batches": 1, "percent": 100 },
"total_rows": 2,
"credits_charged": 8,
"filename": "my-list",
"add_ons": ["dnc"],
"source": "api",
"created_at": "2026-07-17T18:00:01Z",
"completed_at": "2026-07-17T18:02:44Z",
"error_message": null,
"download_url": "https://data.acquiredcrm.com/storage/...signed-csv...",
"results_url": "https://data.acquiredcrm.com/api/v1/jobs/bc3e1413-9a7d-4a52-b1f0-3c8e5d6f7a89/results"
}When status is complete, download_url is a signed CSV link (valid 1 hour) and results_url serves the rows as JSON.
Fetching results as JSON
GET /jobs/{id}/results paginates with page and per_page (default 100, max 1,000 per page). Rows are flat objects keyed like primary_phone and email_1. Requesting results before the job finishes returns 409 job_not_complete.
curl "https://data.acquiredcrm.com/api/v1/jobs/bc3e1413-9a7d-4a52-b1f0-3c8e5d6f7a89/results?page=1&per_page=100" \
-H "Authorization: Bearer ad_live_XXXX"{
"job_id": "bc3e1413-9a7d-4a52-b1f0-3c8e5d6f7a89",
"total_rows": 2,
"page": 1,
"per_page": 100,
"rows": [
{
"property_address": "123 Main St",
"property_city": "Phoenix",
"matched_first_name": "John",
"matched_last_name": "Doe",
"primary_phone": "6025551234",
"email_1": "john@example.com"
}
]
}Webhook redelivery
If your endpoint missed the completion callback, POST /jobs/{id}/retry-webhook re-delivers it. Pass an optional body of {"callback_url": "https://..."}to set or override the job's destination — this also lets dashboard-created jobs deliver to a webhook after the fact. Details in the webhooks guide.
How billing works
- Credits are charged upfront for every row when the job is accepted: trace rate per row (
base_trace, orllc_trust_tracefor entity rows) plus each selected add-on. - Rows that don't match are not refunded — you pay per row submitted, not per match.
- If the job fails, the full charge is refunded automatically.
Need every phone scrubbed against DNC lists? Use DNC jobs, which force the compliance add-ons on.