Enrichment: File Match
If you only want to match and enrich a file — no segments, no audiences, no campaigns — this
is the endpoint family built for that. Every route here is namespaced under /v1/match/, and
that’s deliberate: stay inside these routes and you never need to learn the audience-building
model at all.
The one path to know
Section titled “The one path to know”POST /v1/match/file— creates the job, returns a signed upload URL.PUTyour file to that URL.- Poll
GET /v1/match/file/{match_id}until matching finishes. POST /v1/match/{match_id}/deliveries— export the result.- Poll
GET /v1/match/{match_id}/deliveriesuntil the delivery completes, then download.
That’s the entire happy path. Everything below fills in the details and the five ways people get it wrong.
1. Create the job
Section titled “1. Create the job”curl -X POST https://api.infiniteaudience.ai/v1/match/file \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "Q1 customer list", "file_format": "csv"}'{ "segment_id": "seg_8f2a91", "audience_id": "aud_c103d4", "match_id": "seg_8f2a91", "name": "Q1 customer list", "status": "pending", "upload_url": "https://storage.googleapis.com/cf-uploads/enrichment-uploads/org_xyz/seg_8f2a91/input.csv?X-Goog-Signature=...", "upload_expires_at": "2026-08-10T15:30:00.000Z", "expires_at": "2026-11-08", "file_format": "csv", "compression": "none", "shard_count": 1}2. Upload your file
Section titled “2. Upload your file”PUT the file directly to upload_url — no Authorization header, since the URL itself is
the credential:
curl -X PUT "$UPLOAD_URL" \ -H "Content-Type: text/csv" \ --data-binary @customers.csvThe Content-Type you send must exactly match the format you declared:
file_format |
Required Content-Type |
|---|---|
csv |
text/csv |
avro |
application/avro |
json |
application/json |
jsonl |
application/x-ndjson |
Gzip: send the raw gzip bytes with the underlying format’s Content-Type (e.g. csv +
gzip is still Content-Type: text/csv, just gzip-compressed bytes) and set compression: "gzip" in the create request — do not set an HTTP Content-Encoding header.
avro + gzip is rejected with 400 UNSUPPORTED_INPUT_COMPRESSION — Avro is already
internally compressed; upload it uncompressed, or use csv/json/jsonl if you need gzip.
Sharding large files: set shard_count (1–50) on the create call to get upload_urls[]
instead of a single upload_url — PUT every shard. Every CSV shard must repeat the same
header row, and all shards must share format and compression.
The upload itself is what starts identity resolution — nothing else triggers it.
3. Poll for match completion
Section titled “3. Poll for match completion”curl https://api.infiniteaudience.ai/v1/match/file/seg_8f2a91 \ -H "Authorization: Bearer $ACCESS_TOKEN"{ "match_id": "seg_8f2a91", "name": "Q1 customer list", "status": "active", "matching_status": "completed", "input_record_count": 50000, "match_count": 34210, "match_rate": 0.6842, "amount_charged": null, "file_format": "csv", "compression": "none", "column_mappings": { "email": "email_address", "first_name": "fname" }, "error_message": null, "segment_id": "seg_8f2a91", "audience_id": "aud_c103d4", "audience_status": "active", "source_job_id": "seg_8f2a91", "created_at": "2026-08-10T15:02:11.000Z", "expires_at": "2026-11-08"}Poll until matching_status is completed or failed — pending and processing both mean
“still running.” Check status, too: it flips to active in the same instant as
matching_status: completed, so either is a valid ready-signal.
amount_charged is null here — that’s correct, not a bug. File match bills at first
delivery, not at upload; see the billing note in step 4.
4. Export the result
Section titled “4. Export the result”curl -X POST https://api.infiniteaudience.ai/v1/match/seg_8f2a91/deliveries \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{"include_unmatched": true}'{ "delivery_id": "del_44a1", "audience_id": "aud_c103d4", "audience_version": 1, "destination": "download", "status": "processing", "output_format": "csv", "output_compression": "none", "record_count": null}Optionally pass template_id or field_list to choose which enriched attributes to include —
omit both for the Standard IAG attribute set. output_format defaults to your input format;
override it if you want a different one out than you put in.
Billing: this is where file match actually charges. The first successful delivery for a given match run emits one aggregate usage event covering matching + enrichment + delivery together; re-delivering the exact same (job × version × fields × destination) again is free.
5. Poll for delivery completion and download
Section titled “5. Poll for delivery completion and download”{ "deliveries": [ { "delivery_id": "del_44a1", "audience_id": "aud_c103d4", "status": "completed", "output_format": "csv", "output_compression": "none", "output_bytes": 4821004, "output_record_count": 50000, "completed_at": "2026-08-10T15:06:44.000Z", "download_urls": ["https://storage.googleapis.com/cf-exports/.../output.csv?X-Goog-Signature=..."] } ], "total": 1}Match on delivery_id — it’s a list, most recent first. download_urls are signed for 24
hours; re-GET this same endpoint any time to get freshly re-signed URLs rather than letting
old ones expire on you.
Human-in-the-loop: reviewing column mappings before matching starts
Section titled “Human-in-the-loop: reviewing column mappings before matching starts”By default, uploading immediately triggers automatic column-name inference. If you’d rather
review the mapping first — useful for files with unusual or ambiguous column names — set
hitl: true on the create call. This changes the flow:
-
POST /v1/match/filewith{"hitl": true}— the upload URL now points to a staging location that automatic matching doesn’t watch. -
Upload as normal. Nothing starts yet.
-
POST /v1/match/{match_id}/analyze(empty body) to preview the inferred mapping:{"segment_id": "seg_8f2a91","analysis": {"columns": [{ "source_column": "email_address", "mapped_to": "email", "inferred_type": "STRING", "confidence": 0.98, "warning": null },{ "source_column": "fname", "mapped_to": "first_name", "inferred_type": "STRING", "confidence": 0.72, "warning": "Ambiguous — verify" }],"unmapped_columns": ["internal_notes"],"recommendations": ["Consider mapping 'internal_notes' or confirm it should be excluded."]}}Treat anything under 0.6 confidence as needing a human look before you confirm.
-
POST /v1/segments/{match_id}/mappingswith{"column_mappings": {...}}to confirm and start matching — note this one route is segment-namespaced, not match-namespaced; there is no/v1/match/{id}/mappingstwin. Send the complete mapping, not a delta — omit a field entirely (rather than mapping it) using the"(skip)"sentinel value.Changed your mind?
POST /v1/segments/{match_id}/mappings/cancelclears the staged upload without starting anything — fully idempotent, safe to call even if you already confirmed. -
Continue from step 3 of the main flow above (poll for completion, then deliver).
Reference: signed-URL format details
Section titled “Reference: signed-URL format details”Recapping the mechanics from steps 2 and 5, all in one place:
- Upload URLs: 30-minute TTL,
Content-Typemust exactly match the declared format (see table above), gzip is raw bytes with noContent-Encodingheader,avro+gzipis rejected. - Download URLs: 24-hour TTL, always re-signed fresh by
GET /v1/match/{id}/deliveries— never cached from an earlier response. - Sharding:
shard_count1–50 on create;upload_urls[]replacesupload_urlwhen sharded (never both); every shard must be uploaded, and every CSV shard repeats the header row.