API Reference
Data Ingestion
Import Jobs

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

OperationEndpoint
Initialize jobPOST /import-jobs
Set field mappingPOST /import-jobs/{job_id}/mapping
Start jobPOST /import-jobs/{job_id}/start
Get job statusGET /import-jobs/{job_id}
Get errorsGET /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-jobs

Request body

FieldTypeRequiredDescription
objectNamestringYesTarget object (e.g. profiles, orders).
fileNamestringYesOriginal filename, including extension (e.g. spring-2026.csv).
fileSizeintegerYesFile size in bytes.
createListbooleanNoWhen true, create a static list from the imported records.
listNamestringCond.Name of the list to create. Required when createList is true.
targetListIdstringNoAdd imported records to an existing list instead of creating a new one.
autoProfileAfterImportbooleanNoTrigger profile unification after the job completes.
createAudiencebooleanNoWhen true, create a dynamic audience from the imported records.
audienceNamestringCond.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.csv

2. Set the Field Mapping

Map source columns to object fields. Columns not listed in fieldMap are ignored.

POST /import-jobs/{job_id}/mapping

Request body

FieldTypeRequiredDescription
sourceFieldsobjectYesDescribes the source columns: { "columnName": "dataType" }.
fieldMapobjectYesMaps source columns to destination fields: { "src": "dest" }.
importModestringNoProcessing mode. Default: "balanced". See modes below.
templateIdstringNoReuse a saved mapping template by ID instead of supplying sourceFields/fieldMap.
saveAsTemplateobjectNoSave this mapping for future reuse: { "name": "My Template" }.

importMode values

ModeBehaviour
balancedDefault. Rejects rows with type errors; tolerates missing non-key fields. The right choice for most production pipelines.
strictRejects 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_friendlyAccepts nearly everything — coerces types where possible, fills missing fields with nulls. Use for exploratory loads or sources with inconsistent formatting.
customReserved 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

StateMeaning
pendingJob created, awaiting mapping.
readyMapping set, waiting for start.
runningActively processing. Safe to poll.
completedSuccess. Check metrics. May still have invalidRows > 0.
failedUnrecoverable failure. Check errorMessage.

Errors File

When rows fail validation, retrieve the error file location:

GET /import-jobs/{job_id}/errors

Response — 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 StatusCodeMeaning
400CDP_ETL.VALIDATION.REQUEST_INVALIDMalformed body or invalid field combination.
401CDP_ETL.AUTH.UNAUTHORIZEDMissing or invalid bearer token.
403CDP_ETL.AUTH.FORBIDDENToken lacks required scope.
404CDP_ETL.NOT_FOUNDjob_id doesn't exist.
409CDP_ETL.*Operation not allowed in the current state.
422CDP_ETL.VALIDATION.REQUEST_SCHEMAMapping 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