Skip to main content

Troubleshooting: contact imports

Contact imports run as background jobs: POST /contacts/imports enqueues the rows, a queue worker batches them into your tenant, and the job moves pending → running → completed | failed | cancelled. (The state model and cancel/rollback semantics are covered on the Import and migration lifecycle concept page — this page is the per-error decision path.) Most import errors below are state-machine conflicts: the request is well-formed, but the job is standing in the wrong status for what you asked. Check the job’s live state first; a 409 is almost always a stale-status mismatch, not a broken import.

Pre-flight: read the job state first

Before cancelling or rolling back, poll the job:
The response carries the current status and row counters (processed_rows, failed_rows). What you can do next depends on that status: 404 IMPORT_JOB_NOT_FOUND — no job with that id exists in your tenant. Confirm the id you stored from the POST /contacts/imports response, and check you are calling with an API key scoped to the tenant the import ran under. If 404 persists, re-enqueue the CSV; do not keep polling.

Cancel path

409 IMPORT_NOT_CANCELLABLE — the job exists but is already in a terminal state (completed, failed, or cancelled). A terminal job never moves again, so the cancel is refused rather than re-applied, and the error response carries the job’s current status so you can say “Already completed” or “Already cancelled” without a second lookup. This code also fires when two concurrent cancel requests raced and one lost.
  • Fix: poll the job first, and treat a duplicate cancel as idempotent-success on the terminal status.
409 IMPORT_STILL_RUNNING — raised by the rollback endpoint, not the cancel endpoint: you asked for rollback while the job was still pending or running. Rollback operates only on terminal jobs.
  • Fix: cancel the job, wait for it to settle into a terminal state, then roll back. Note that cancel is best-effort — the worker flips the job to cancelled as soon as it sees the flag between batches; contacts already written stay in the tenant until you roll back.

Rollback path

409 ALREADY_ROLLED_BACK — the job’s row already carries a rollback stamp, so the contacts the import created were already deleted once.
  • Fix: treat the message as “already done.” If you suspect rows remain, delete them by id through the bulk-delete endpoint rather than re-rolling back. Rollback is also time-boxed: a job that finished more than 24 hours ago is refused, so run rollback promptly after a bad import and prefer a targeted bulk-delete for older, partially-created rows.

Queue and infrastructure

503 IMPORT_QUEUE_UNAVAILABLE — the API could not hand the job to the import queue because Redis, the queue, or a dependent operator-managed queue component was down.
  • Fix: safe to retry with the same payload — queue-unavailability rejects before any row is written, so a retry does not double-enqueue. If the 503 persists past a few minutes, check your queue/Redis infrastructure (or contact support on hosted plans).

GDPR erasure on imported contacts

409 ERASURE_COOLING_OFF_ACTIVE — you filed a new Article-17 right-to-erasure request on a contact while a pending or executing request already exists for that contact (the default cooling-off period is 7 days). The existing request is either still inside its cancellable window or already executing.
  • Fix: list the contact’s erasure requests to find the active one, and either cancel the active request — which frees a fresh filing — or wait for it to execute; the scheduler then hard-deletes the imported contact and writes the per-resource audit chain. Track which contacts came from which import job so an erasure filed “after import” does not deadlock with the import’s own records.

Segment suggestion

422 AI_INVALID_RULES — on POST /contacts/segments/suggest-rules, the natural-language-to-filter-rules suggester rejected the rules the model drafted. The generated filter was not a valid segment expression — no segment is created, and nothing is written.
  • Fix: reword the prompt into explicit attribute filters (for example, “contacts imported in August with the tag vip”), or edit the rejected rule JSON and re-submit. The same request shape on retry is safe — the endpoint validates and returns suggestions only; a valid suggestion must still be applied when you create the segment.

Decision tree

Send support

Open a ticket with the job id (or, for erasure, the contact id), the API response meta.request_id, and the list of error codes hit. Those three values let support replay the exact state your job was in without a reproduction.