Skip to content
SeekOutDevelopers

Guides

Contact jobs

POST /v1/contact-retrieval-jobs retrieves contacts for up to 500 people in one bounded, durable job. You get a free estimate first if you want one, then a job id to read or a webhook to receive.

Estimate before you spend

Send the same body with quote_only: true and nothing is persisted, nothing is held, and nothing is charged. The answer prices the work per component after deduplication, so it is the number to show a user before they commit.

{
  "items": 2,
  "components": 3,
  "reserved_microunits_estimate": "3000",
  "lines": [
    {
      "meter": "contact_email.v1",
      "units": 2,
      "unit_price_microunits": "1000",
      "total_microunits": "2000"
    },
    {
      "meter": "contact_phone.v1",
      "units": 1,
      "unit_price_microunits": "1000",
      "total_microunits": "1000"
    }
  ],
  "omitted": { "duplicates": 0, "not_servable": 0 }
}

Amounts in these samples are placeholders, not rates. Pricing is consumption-based, so you only pay for data you get back, and it is shown to signed-in accounts under Pricing in the console.

An estimate is an estimate: a person whose contacts your organization already holds costs nothing when the job actually runs, and a channel the provider cannot answer is never charged.

Create the job

Without quote_only the same request creates the job. It answers 202 with a Job once acceptance is durable — the reservation is taken inline, so a 402 arrives synchronously and no job exists.

Create a batch
curl --request POST "https://developer-api.seekout.io/v1/contact-retrieval-jobs" \
  --header "Authorization: Bearer $SEEKOUT_API_KEY" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: $(uuidgen)" \
  --data '{
    "items": [
      { "person_id": "person_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "channels": ["email"] },
      { "person_id": "person_ccccccccccccccccccccccccccccccccccccccccccc", "channels": ["email", "phone"] }
    ]
  }'

The answer is the job. reserved_microunits is what is held against your balance while it runs; charged_microunits grows only as components are delivered, and whatever is not delivered is released.

HTTP/1.1 202 Accepted

{
  "job_id": "01JEXAMPLEJOB0000000000000",
  "type": "contact_retrieval",
  "status": "queued",
  "counts": { "requested": 3, "delivered": 0, "pending": 3 },
  "reserved_microunits": "3000",
  "charged_microunits": "0",
  "result_available": false,
  "result_url": "/v1/job-results/01JEXAMPLEJOB0000000000000"
}

What a job bounds

The limits a job is created under
BoundValueOver it
Items per job500422 — send fewer rows
Running jobs per organization2429 contact_job_concurrency_cap with Retry-After
Job lifetime30 minutesThe job ends expired; delivered components are charged and the rest are released
Result retention7 days409 result_set_expired — create the job again

Rows are deduplicated per (person, channel) before anything is priced or reserved, and the answer reports what it dropped in omitted.duplicates. Asking for the same pair twice never pays twice.

Read the job

Every pending single-person retrieval is readable through the same two reads, using the job_id its 202 carried — one Jobs API for both shapes (see contacts).

Job statuses

What each status means
StatusMeaningResult readable
queuedAccepted and funded; not started yetNo
runningComponents are with the providerNo
completedEvery component resolvedYes
partialSome components resolved and some did notYes
failedNo component could be deliveredNo
cancellingA cancel was requested and is convergingNo
cancelledCancelled; delivered components were chargedYes
expiredThe 30-minute deadline passedYes
A result read before the job ends is a 409GET /v1/job-results/{job_id} answers 409 job_not_finished until the job is terminal — it never returns a half-built page. And a job id belonging to another workspace is 404 job_not_found, exactly as an absent one is, so a job id is never a way to learn what exists.

Estimates are free, so are all four reads and the cancel. Only delivered components charge — see credits.