Batch & files

Core·12 min·updated 2026-07-30

Batch is the same resolver with a different front door. Use it for a backfill, a one-off file, or any workload where nothing is waiting on the answer.

When to use batch rather than resolve

  • More than about 500 records, and nothing is blocked on the result.
  • The input is already a file, and turning it into 40M HTTP calls is work you do not need to do.
  • You want one deduplicated answer over the whole file rather than per-row answers that disagree with each other.

Pricing is identical: 1 credit per record either way. Batch is not a discount, it is a different shape of work.

The job lifecycle

  1. POST /v1/resolve/batch with the records inline, or with an uploaded file reference. Up to 10,000 records per job on paid plans, 1,000 on Developer.
  2. You get a job_id immediately. The call itself costs nothing; the records are billed as they are processed.
  3. GET /v1/jobs/{id} returns queued, running with a progress count, completed, or failed. Poll it, or attach a webhook and stop polling.
  4. On completion the job carries a signed results URL, valid for 24 hours, containing one output row per input row in the order you sent them.
Submit and poll
$ curl -s https://api.spotit.ai/v1/resolve/batch \
    -H "Authorization: Bearer $SPOTIT_KEY" \
    -H "Content-Type: application/json" \
    -d @records.json | jq -r .job_id
job_01JT4M9X2K7QN3

$ curl -s https://api.spotit.ai/v1/jobs/job_01JT4M9X2K7QN3 \
    -H "Authorization: Bearer $SPOTIT_KEY"

Partial results are the normal case

A job does not fail because 40 rows out of 10,000 were unparseable. Those rows come back with a status of error and a reason, the rest come back resolved, and the job completes. You are billed for the records that were processed, and never for a no_match.

Three output rows
{"row": 1, "status": "matched",  "entity_id": "ent_01JR8K3F5T2QW9", "confidence": 0.987}
{"row": 2, "status": "no_match", "reason": "no candidate above floor", "billed": false}
{"row": 3, "status": "error",    "reason": "field 'name' empty", "billed": false}

Backfilling 40 million rows

The largest backfill we have run took nine days at a steady 55 records per second, and the shape of it generalises.

  1. Deduplicate the input first. Most CRM exports are 15–25% duplicates before they reach us. Collapsing them locally is free; resolving them twice is not.
  2. Sort by country. Jobs grouped by jurisdiction hit warmer blocking keys and finish measurably faster.
  3. Chunk to 10,000 and run 4–8 jobs concurrently. Beyond that you are queueing against your own rate limit rather than going faster.
  4. Checkpoint the job IDs. A crash mid-backfill should resume from the last completed job, not from row zero.
  5. Write back the entity ID before anything else. Once the ID is stored, every later attribute is a 0.2-credit read you can repeat at will.
Worth knowing

Cached repeats within 24 hours are free. A backfill that runs twice by accident on the same day costs what it cost the first time.

CSV in, CSV out

Upload a CSV and name the columns that carry the name, country, address and any identifier. The output preserves your original columns and appends ours, so the file can go straight back into the system it came from.

Column mapping
$ curl -s https://api.spotit.ai/v1/resolve/batch \
    -H "Authorization: Bearer $SPOTIT_KEY" \
    -F file=@accounts.csv \
    -F 'map={"q":"company_name","country":"iso2","address":"billing_street"}' \
    -F 'output=csv'

Failure modes worth knowing

  • A job that has been queued for more than 15 minutes means you are at your plan's concurrency limit, not that it is stuck.
  • Results URLs expire after 24 hours. Re-request one from the job rather than re-running the job.
  • A job is immutable once submitted. To change the mapping, submit a new one — this is deliberate, so a re-run is reproducible.

Try it against your own data

The Developer plan is free forever and needs no card. 2,500 credits is enough to answer the only question that matters: does it resolve your records.