Resolve a Batch of Records

The pipeline for resolving a list of partial records — a conference registration list, a trade-show scan, an acquired CRM — to identities you can act on. Deep Match is asynchronous: you submit a batch, wait for it, then reconcile. What the answers mean is in Deep Match.

The pipeline#

Step paths below are relative — every request also needs the version and environment prefix, https://api.aggknowledgeai.com/v3/{environment}. See Environments.
1
Shape your rows
One row per record you want resolved, with every signal you hold on it. Set metadata on each row to carry your own identifier through.
2
Submit the batchPOST /person/asynchronous/deep-match
Returns a batchId immediately with status pending, plus one rows entry per submitted row carrying its rowId. Tier 3.
3
Wait for completion
Poll the batch endpoint on an interval, backing off on a 429. Resolution time scales with the batch, so poll on a cadence you would be happy running all day.
4
Collect resultsGET /person/asynchronous/deep-match/{id}
Once status is ready, items holds one entry per submitted row. Tier 4, and not billed — the matched rows were charged when they resolved.
5
Reconcile and triage
Join each item back to your record by rowId or your own metadata, then branch on outcome.

Submitting the batch#

Send only the fields you actually hold. An absent field costs nothing; a guessed one can push a row to multiple_candidates.

cURL — Submit a Deep Match batch
curl --request POST \
  --url https://api.aggknowledgeai.com/v3/live/person/asynchronous/deep-match \
  --header 'Content-Type: application/json' \
  --header 'x-api-key: YOUR_API_KEY' \
  --data '{
  "rows": [
    {
      "fullName": "dan entrup",
      "stateOrProvince": "new york",
      "country": "united states",
      "emails": ["dan.entrup@example.com"],
      "employerNames": ["aggknowledge"],
      "metadata": { "crmId": "A-17" }
    },
    { "npis": ["1033479100"], "metadata": { "crmId": "B-99" } },
    { "fullName": "jay patel", "country": "united states", "metadata": { "crmId": "C-42" } }
  ]
}'

The response gives you the handle and an upper bound on cost. rowCount is rows accepted, and only rows that resolve are charged, so the batch can cost anything from nothing to rowCount credits. Each row below is abbreviated; the API echoes every field you sent.

Response
{
  "batchId": "2db5ea61-1752-42cf-8ea1-ab1da060cd0a",
  "rowCount": 3,
  "status": "pending",
  "rows": [
    { "rowId": "b6f0e2a1-9c3d-4f5e-8a7b-1d2c3e4f5a6b", "rowIndex": 0, "row": { "fullName": "dan entrup", "metadata": { "crmId": "A-17" } } },
    { "rowId": "c7a1f3b2-0d4e-4a6f-9b8c-2e3d4f5a6b7c", "rowIndex": 1, "row": { "npis": ["1033479100"], "metadata": { "crmId": "B-99" } } },
    { "rowId": "d8b2a4c3-1e5f-4b7a-8c9d-3f4e5a6b7c8d", "rowIndex": 2, "row": { "fullName": "jay patel", "metadata": { "crmId": "C-42" } } }
  ]
}
Join on rowId, not on position. The create response pairs every rowId with the row it came from, so build your map from that rather than from the order you sent. rowIndex still marks where a row sat in the batch, and items still comes back in that order — but it identifies a position, not a record, so it stops being true the moment you filter or reorder anything.

Collecting the results#

Poll the batch by its batchId until status is ready. Reading a batch back is free and rate-limited at tier 4, so a steady interval costs you nothing but the request.

cURL — Poll the batch
curl --request GET \
  --url https://api.aggknowledgeai.com/v3/live/person/asynchronous/deep-match/BATCH_ID \
  --header 'x-api-key: YOUR_API_KEY'
Push delivery for finished batches is on the roadmap — a subscription that hands you the completed batch instead of you asking for it. Until then, polling is the supported path.
Check status before you read items. Every submitted row is present in items from the moment the batch is accepted — while the batch is pending, their outcome and match fields are null. Iterating early yields a full-length array of unresolved rows, not an empty one, so a loop that does not gate on status === "ready" will silently record every row as a miss.

Triage the outcomes#

A completed batch is not an all-matched batch. Branch on outcome — never on whether personId is populated, which misreads every professional resolved outside the dataset and nearly every healthcare match as a failure.

outcomeResubmitting helps?Action
matched—Store whichever of personId, socialProfile, and aggkHcpId came back.
multiple_candidatesOnly with more evidenceReconcile candidates against your own records first — often one of them is obviously the record you hold. Otherwise queue for a second pass once you can add an email, phone, or employer. Resubmitting the identical row returns the identical answer.
no_matchOnly with better inputFlag the source record. The signals you hold do not describe anyone we know.
suppressedNoRecord the decline permanently and stop re-sending the record on later runs.
failedYesTransient. Collect these rows and submit them as a new batch.
Tip: Persist scoringVersion alongside the outcome. It is the only way to tell a change in your own input quality from a re-tune on our side when you compare this month’s match rate to last month’s.

Cost control#

You are charged one credit per row that resolves, whatever evidence it returned — a trail carrying a dozen criteria is still one answer. multiple_candidates, no_match, suppressed, and failed rows are free — a shortlist of three evidenced candidates costs nothing — and reading a batch back is free.

So the lever is input quality, not batch size. Dropping rows that carry nothing but a common first name costs you nothing, but sending them costs nothing either — they will not resolve. Adding one real signal to a row that would otherwise come back multiple_candidates is what converts spend into answers. Run a sample against the test environment first to exercise your integration without spending credits.