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.
Request
Section titled “Request”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, andmatch_confidenceare populated, andenrichmentmay contain real values. This id is round-trippable: submit it back asiag_person_idon a future request and it resolves to the same identity. - Tiers
02–05— no graph match. This is a derived id, computed deterministically from whatever PII you submitted (ornullif none qualified) purely so unmatched rows still get a stable identifier.match_level/match_type/match_confidencearenullandenrichmentis{}. 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.
Billing: only matched rows are charged
Section titled “Billing: only matched rows are charged”amount_charged reflects matched rows only — tier 02–05 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.
Idempotency
Section titled “Idempotency”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.