Skip to content

Enrichment: Microbatch

POST /v1/match resolves and enriches a small batch of records synchronously — you get matched, enriched results back in the same response. This is the right tool for real-time lookups (checking a record as a user submits a form, enriching a webhook payload inline). For anything larger, or for an async workflow with a file upload, see Enrichment: File Match instead — that guide is also the right one if you don’t care about the distinction and just want records back.

Terminal window
curl -X POST https://api.infiniteaudience.ai/v1/match \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"records": [
{ "row_id": "1", "email": "jane@example.com", "first_name": "Jane", "last_name": "Doe" },
{ "row_id": "2", "phone": "+15551234567" },
{ "row_id": "3", "full_name": "John Smith", "address_1": "123 Main St", "city": "Austin", "state": "TX", "zip": "78701" }
],
"field_list": ["estimated_household_income", "birth_generation"]
}'
Field Required Notes
records yes 1–100 objects. Each may carry email, phone, email_sha256, phone_sha256, full_name (or separately first_name/middle_name/last_name/name_suffix), address_1, address_2, city, state, zip, dob, iag_person_id, or the array forms emails/phones/addresses/emails_sha256/phones_sha256.
field_list no Enrichment attributes to return — see GET /v1/catalog/fields for the full list and per-record pricing. Mutually exclusive with template_id.
template_id no A named field bundle instead of an explicit list. The only value today is standard_iag.
match_level no Restrict which match tiers count as a match: I (individual), H (household), D (device), S (structural), A (address). Defaults to all five.
row_id no (per record) Your own correlation id. If omitted, records are assigned "1", "2", … in submission order. Must be unique within the request.

Give every record a row_id explicitly — you’ll need it for the next section regardless.

Response — and the trap that catches almost everyone

Section titled “Response — and the trap that catches almost everyone”
{
"run_id": "run_abc123",
"record_count": 3,
"match_count": 2,
"match_rate": 0.6667,
"amount_charged": 0.24,
"records": [
{ "row_id": "1", "iag_person_id": "ACME_01_9f2a...", "match_level": "I", "match_type": "email", "match_confidence": 0.97, "full_name": "Jane Doe", "enrichment": { "estimated_household_income": "100000-149999", "birth_generation": "millennial" } },
{ "row_id": "3", "iag_person_id": "ACME_01_7b1c...", "match_level": "A", "match_type": "address", "match_confidence": 0.81, "full_name": "John Smith", "enrichment": { "estimated_household_income": null, "birth_generation": "gen_x" } },
{ "row_id": "2", "iag_person_id": "ACME_04_e3d0...", "match_level": null, "match_type": null, "match_confidence": null, "full_name": null, "enrichment": {} }
]
}

iag_person_id — tier tells you if it’s a real match

Section titled “iag_person_id — tier tells you if it’s a real match”

Every record gets an iag_person_id, matched or not — the format is CLIENTCODE_TIER_HASH. The tier is what actually tells you whether it matched:

  • Tier 01 — a real graph match. match_level, match_type, and match_confidence are populated, and enrichment may contain real values. This id is round-trippable: submit it back as iag_person_id on a future request and it resolves to the same identity.
  • Tiers 0205 — no graph match. This is a derived id, computed deterministically from whatever PII you submitted (or null if none qualified) purely so unmatched rows still get a stable identifier. match_level/match_type/match_confidence are null and enrichment is {}. This id is not round-trippable — it doesn’t correspond to anything in the graph, so submitting it back won’t resolve to the same record via graph lookup.

Check the tier (the second segment) before trusting a match, don’t just check that iag_person_id is non-null.

amount_charged reflects matched rows only — tier 0205 derived rows are free. The reservation made when you call this endpoint assumes worst-case (every row matches); the actual charge, computed after resolution, is matched_count × (match rate + enrichment field rates). Check current per-record pricing with GET /v1/catalog/fields, or get an exact pre-flight estimate with POST /v1/quote.

Pass an Idempotency-Key header (8–160 chars) to make a retry safe — a repeated call with the same key and the same body returns the original response (with an Idempotency-Replayed: true header) instead of matching and billing again. A retry with the same key but a different body gets 409 IDEMPOTENCY_KEY_CONFLICT; a retry that arrives while the first call is still in flight gets 409 IDEMPOTENCY_REQUEST_IN_PROGRESS with a Retry-After: 5 header.