Import Jobs API
Ingest large datasets from object storage (S3, GCS, Azure Blob) or pre-signed upload URLs. Import jobs are the recommended path for one-off loads > 100,000 records, backfills, and any workload where you already have data in a file rather than emitting events row-by-row.
Base URL: https://api.experiture.ai/public/v1
Authentication: Bearer token with cdp:imports:write or cdp:imports:read. See Authentication.
Lifecycle
An import job goes through four explicit stages. You (the client) drive the transitions.
┌──────────┐ create ┌──────────┐ set mapping ┌──────────┐ start ┌──────────┐
│ none │ ─────────▶ │ pending │ ────────────▶ │ ready │ ─────────▶ │ running │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
│
┌────────────┴───────────┐
▼ ▼
┌──────────┐ ┌──────────┐
│completed │ │ failed │
└──────────┘ └──────────┘This two-step flow (init → mapping → start) lets you validate the schema before paying to process the full file.
Endpoints
| Operation | Endpoint |
|---|---|
| Initialize job | POST /import-jobs |
| Set field mapping | POST /import-jobs/{job_id}/mapping |
| Start job | POST /import-jobs/{job_id}/start |
| Get job status | GET /import-jobs/{job_id} |
| Get errors | GET /import-jobs/{job_id}/errors |
1. Initialize the Job
Declare the target object and file metadata. The API provisions a secure upload slot and returns a pre-signed URL you use to deliver the file before proceeding to the mapping step.
POST /import-jobsRequest body
| Field | Type | Required | Description |
|---|---|---|---|
objectName | string | Yes | Target object (e.g. profiles, orders). |
fileName | string | Yes | Original filename, including extension (e.g. spring-2026.csv). |
fileSize | integer | Yes | File size in bytes. |
createList | boolean | No | When true, create a static list from the imported records. |
listName | string | Cond. | Name of the list to create. Required when createList is true. |
targetListId | string | No | Add imported records to an existing list instead of creating a new one. |
autoProfileAfterImport | boolean | No | Trigger profile unification after the job completes. |
createAudience | boolean | No | When true, create a dynamic audience from the imported records. |
audienceName | string | Cond. | Name of the audience to create. Required when createAudience is true. |
Example
curl -X POST https://api.experiture.ai/public/v1/import-jobs \
-H "Authorization: Bearer <your_access_token>" \
-H "Content-Type: application/json" \
-d '{
"objectName": "profiles",
"fileName": "spring-2026.csv",
"fileSize": 1048576,
"createList": true,
"listName": "Spring 2026 Backfill",
"autoProfileAfterImport": true
}'Response — 201 Created
{
"success": true,
"data": {
"jobId": "imp_01HXYZ",
"uploadUrl": "https://upload.../file",
"expiresAt": "2026-04-21T16:30:00Z",
"landingPath": "jobs/imp_01HXYZ/file",
"method": "PUT",
"headers": {},
"requestedBy": "usr_01HXYZ"
},
"correlationId": "<uuid>"
}PUT the file to uploadUrl before proceeding. The URL expires at expiresAt.
The headers object contains additional HTTP headers you must include in the upload request — alongside any headers required by the pre-signed URL itself. For Azure Blob Storage this includes x-ms-blob-type: BlockBlob; for S3 the object is typically empty. Always merge headers into your upload request — omitting them will cause the storage provider to reject the upload.
# Upload the file using the returned method, URL, and headers
curl -X PUT "<uploadUrl>" \
-H "x-ms-blob-type: BlockBlob" \ # from headers object — include all keys
-H "Content-Type: application/octet-stream" \
--data-binary @spring-2026.csv2. Set the Field Mapping
Map source columns to object fields. Columns not listed in fieldMap are ignored.
POST /import-jobs/{job_id}/mappingRequest body
| Field | Type | Required | Description |
|---|---|---|---|
sourceFields | object | Yes | Describes the source columns: { "columnName": "dataType" }. |
fieldMap | object | Yes | Maps source columns to destination fields: { "src": "dest" }. |
importMode | string | No | Processing mode. Default: "balanced". See modes below. |
templateId | string | No | Reuse a saved mapping template by ID instead of supplying sourceFields/fieldMap. |
saveAsTemplate | object | No | Save this mapping for future reuse: { "name": "My Template" }. |
importMode values
| Mode | Behaviour |
|---|---|
balanced | Default. Rejects rows with type errors; tolerates missing non-key fields. The right choice for most production pipelines. |
strict | Rejects any row that doesn't fully satisfy the schema — missing optional fields, type coercions, and extra columns all cause rejection. Use when data quality must be enforced at ingestion. |
landing_friendly | Accepts nearly everything — coerces types where possible, fills missing fields with nulls. Use for exploratory loads or sources with inconsistent formatting. |
custom | Reserved for workspace-level policy overrides. Do not use unless instructed by Experiture support. |
Example
curl -X POST https://api.experiture.ai/public/v1/import-jobs/imp_01HXYZ/mapping \
-H "Authorization: Bearer <your_access_token>" \
-H "Content-Type: application/json" \
-d '{
"sourceFields": {
"email_address": "string",
"FirstName": "string",
"signup_ts": "datetime"
},
"fieldMap": {
"email_address": "email",
"FirstName": "first_name",
"signup_ts": "signed_up_at"
}
}'Response — 200 OK
{
"success": true,
"data": { "ok": true },
"correlationId": "<uuid>"
}If the mapping references an unknown destination field, you get 422 and the job remains in pending.
3. Start the Job
The start endpoint accepts an optional body. Defaults are sufficient for most workloads:
curl -X POST https://api.experiture.ai/public/v1/import-jobs/imp_01HXYZ/start \
-H "Authorization: Bearer <your_access_token>" \
-H "Content-Type: application/json" \
-d '{}'Response — 200 OK
{
"success": true,
"data": {
"status": "started",
"jobId": "imp_01HXYZ",
"lane": "standard",
"message": "Job queued for processing"
},
"correlationId": "<uuid>"
}Once started, the job is terminal — you can't modify the mapping or source. To change, create a new job.
4. Poll Status
curl https://api.experiture.ai/public/v1/import-jobs/imp_01HXYZ \
-H "Authorization: Bearer <your_access_token>"Response — 200 OK
{
"success": true,
"data": {
"stage": "merge",
"state": "completed",
"readRows": 124532,
"validRows": 124087,
"invalidRows": 445,
"mergedInserts": 98221,
"mergedUpdates": 25866,
"startedAt": "2026-04-21T15:30:02Z",
"updatedAt": "2026-04-21T15:38:41Z",
"rowsTotal": 124532,
"rowsImported": 124087,
"rowsRejected": 445,
"successRate": 0.9964,
"errorSummary": {
"VALIDATION_TYPE_MISMATCH": 320,
"REQUIRED_FIELD_MISSING": 125
}
},
"correlationId": "<uuid>"
}errorSummary is a { errorCode: count } map grouping validation failures by type. It lets you triage failures at a glance without reading the full error file — if REQUIRED_FIELD_MISSING dominates, fix your source extract; if VALIDATION_TYPE_MISMATCH dominates, check column formats. errorSummary is null when no rows were rejected.
Recommended polling: exponential backoff starting at 5 s, cap at 60 s. Typical jobs finish in 30 s – 10 min depending on size.
Job states
| State | Meaning |
|---|---|
pending | Job created, awaiting mapping. |
ready | Mapping set, waiting for start. |
running | Actively processing. Safe to poll. |
completed | Success. Check metrics. May still have invalidRows > 0. |
failed | Unrecoverable failure. Check errorMessage. |
Errors File
When rows fail validation, retrieve the error file location:
GET /import-jobs/{job_id}/errorsResponse — 200 OK
{
"success": true,
"data": {
"hasErrors": true,
"errorFileUrl": "https://...",
"expiresAt": "2026-04-22T15:38:41Z",
"errorCount": 445
}
}If no errors occurred: { "hasErrors": false }. When hasErrors is true, download the JSONL file at errorFileUrl before expiresAt. Each line is one rejected row:
{"rowNumber": 42, "sourceRow": {"email_address": "not-an-email"}, "error": "invalid email format"}The pre-signed errorFileUrl expires — re-fetch from the endpoint if you need it again after expiry.
Error Codes
| HTTP Status | Code | Meaning |
|---|---|---|
400 | CDP_ETL.VALIDATION.REQUEST_INVALID | Malformed body or invalid field combination. |
401 | CDP_ETL.AUTH.UNAUTHORIZED | Missing or invalid bearer token. |
403 | CDP_ETL.AUTH.FORBIDDEN | Token lacks required scope. |
404 | CDP_ETL.NOT_FOUND | job_id doesn't exist. |
409 | CDP_ETL.* | Operation not allowed in the current state. |
422 | CDP_ETL.VALIDATION.REQUEST_SCHEMA | Mapping references an unknown destination field. |
Constraints
- Max file size: 50 GB
- Max row count: 200,000,000 rows per job
- Max concurrent running jobs: 10 per workspace
- Retention: completed jobs and their error files are retained 30 days
See Also
- Batch Import Guide — full walkthrough with code
- Records API — for inline batch writes < 10k rows
- Metadata API — inspect destination schemas