Skip to main content
Run the full CSV bulk-import workflow from the command line: upload and analyze the file, create the job with a column mapping, poll until the job reaches a terminal state, and pull the rejected rows if any landed. The recipe composes every leaf in path import.
Prerequisites: authenticated CLI, a CSV file with a header row, and a channel ID to import the contacts into. Find a channel ID with path channel list.

Steps

1

Analyze the CSV

talkvalue path import analyze --file ./contacts.csv --json
Uploads the file, returns a fileKey, the headers, a preview, and the server’s suggested column mapping. Note the fileKey and the column indices. You need both for the next step.
2

Capture the fileKey

FILE_KEY=$(talkvalue path import analyze --file ./contacts.csv --json | jq -r '.data.fileKey')
echo "$FILE_KEY"
Saves the key in a shell variable so the rest of the script can reference it without re-uploading.
3

Create the import job

SOURCE_CHANNEL_ID=7

JOB_ID=$(
  talkvalue path import create \
    --file-key "$FILE_KEY" \
    --source-id "$SOURCE_CHANNEL_ID" \
    --mode UPDATE \
    --mapping 0:EMAIL \
    --mapping 1:FIRST_NAME \
    --mapping 2:LAST_NAME \
    --json \
  | jq -r '.data.importJobId'
)
echo "Job: $JOB_ID"
Returns immediately with the importJobId. The job runs in the background. Your terminal is free for the polling loop below.--mode UPDATE overwrites fields on existing contacts matched by email. Switch to --mode SKIP if you want to leave existing records untouched and treat collisions as duplicates.
4

Poll until the job reaches a terminal state

while :; do
  STATUS=$(talkvalue path import get "$JOB_ID" --json | jq -r '.data.status')
  echo "  status: $STATUS"
  case "$STATUS" in
    COMPLETED|PARTIAL_SUCCESS|FAILED) break ;;
  esac
  sleep 5
done
The job moves through PENDINGRUNNING → one of COMPLETED, PARTIAL_SUCCESS, or FAILED. The loop exits on any terminal state. Tighten or relax sleep based on import size.
5

Export rejected rows when there are failures

FAILED=$(talkvalue path import get "$JOB_ID" --json | jq -r '.data.failedCount')
if [ "$FAILED" -gt 0 ]; then
  talkvalue path import failed-export "$JOB_ID" > "failed-$JOB_ID.csv"
  echo "$FAILED rejected row(s) written to failed-$JOB_ID.csv"
else
  echo "No failures."
fi
Pulls the full rejected-row CSV only when the job had failures. The output header is rowNum,errorCode,errorMessage,rawValue. Fix the rows, re-run the import on the corrected file, and the failure count drops.

Variants

Treat duplicates as silent skips

talkvalue path import create \
  --file-key "$FILE_KEY" \
  --source-id "$SOURCE_CHANNEL_ID" \
  --mode SKIP \
  --mapping 0:EMAIL --mapping 1:NAME --mapping 2:COMPANY_NAME \
  --json
SKIP is the right mode for a weekly registrant export when you want every new email but you don’t want to overwrite manual edits made in the dashboard.

Group failures by error code before fixing

talkvalue path import failed-export "$JOB_ID" \
  | tail -n +2 \
  | awk -F',' '{print $2}' \
  | sort | uniq -c | sort -rn
Counts failures by errorCode so you can prioritize the fix that unlocks the most rows.

One-shot script

#!/usr/bin/env bash
set -euo pipefail

FILE="$1"
SOURCE_CHANNEL_ID="$2"

FILE_KEY=$(talkvalue path import analyze --file "$FILE" --json | jq -r '.data.fileKey')
JOB_ID=$(
  talkvalue path import create \
    --file-key "$FILE_KEY" \
    --source-id "$SOURCE_CHANNEL_ID" \
    --mode UPDATE \
    --mapping 0:EMAIL --mapping 1:FIRST_NAME --mapping 2:LAST_NAME \
    --json | jq -r '.data.importJobId'
)

while :; do
  STATUS=$(talkvalue path import get "$JOB_ID" --json | jq -r '.data.status')
  case "$STATUS" in COMPLETED|PARTIAL_SUCCESS|FAILED) break ;; esac
  sleep 5
done

FAILED=$(talkvalue path import get "$JOB_ID" --json | jq -r '.data.failedCount')
[ "$FAILED" -gt 0 ] && talkvalue path import failed-export "$JOB_ID" > "failed-$JOB_ID.csv"
echo "Final status: $STATUS, $FAILED failure(s)"
Drop into import-csv.sh, then run ./import-csv.sh ./contacts.csv 7.

Tips

  • Valid mapping target fields: EMAIL, FIRST_NAME, LAST_NAME, NAME, PHONE, JOB_TITLE, COMPANY_NAME, ADDRESS, LINKEDIN_URL, X_URL, JOINED_AT. The full reference lives at Column mapping.
  • Re-importing failed-$JOB_ID.csv after corrections clears the failure count. It does not double-import the successful rows from the first run.
  • Each import create queues a separate job. If you re-run with the same fileKey you get a second job ID, not an overwrite of the first.
  • Need to inspect a job later? Every import is listed by import list and individual jobs by import get <id>.

See also