> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshooting: contact imports

> Resolve cancel, rollback, queue, and lifecycle errors on async contact imports — IMPORT_JOB_NOT_FOUND on the status poll, IMPORT_NOT_CANCELLABLE / IMPORT_STILL_RUNNING / ALREADY_ROLLED_BACK 409s, IMPORT_QUEUE_UNAVAILABLE 503s, ERASURE_COOLING_OFF_ACTIVE on GDPR requests, and AI_INVALID_RULES from the segment-rule suggester.

# 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](/concepts/imports-migration-model) 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:

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/contacts/imports/{id}" \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

The response carries the current `status` and row counters (`processed_rows`, `failed_rows`). What you can do next depends on that status:

| Status                             | Cancel allowed        | Rollback allowed                   |
| ---------------------------------- | --------------------- | ---------------------------------- |
| `pending`, `running`               | Yes                   | No — cancel first                  |
| `completed`, `failed`, `cancelled` | No (already terminal) | Yes, within 24 hours of completion |

**`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

| Code                         | HTTP | Cause                                                         | Fix                                                                            |
| ---------------------------- | ---- | ------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `IMPORT_JOB_NOT_FOUND`       | 404  | The job id does not exist in your tenant                      | Verify the id from the enqueue response and the tenant scope; re-run if needed |
| `IMPORT_NOT_CANCELLABLE`     | 409  | Job is already terminal (or lost a concurrent cancel race)    | Poll first; consume the terminal status                                        |
| `IMPORT_STILL_RUNNING`       | 409  | Rollback called on a `pending`/`running` job                  | Cancel first, then roll back once terminal                                     |
| `ALREADY_ROLLED_BACK`        | 409  | The job was already rolled back once                          | Treat as done; use bulk-delete for leftover rows                               |
| `IMPORT_QUEUE_UNAVAILABLE`   | 503  | Redis / import queue / queue component offline                | Retry-safe; escalate to infra if persistent                                    |
| `ERASURE_COOLING_OFF_ACTIVE` | 409  | An active GDPR erasure request already exists for the contact | Cancel the active request or wait for it to execute                            |
| `AI_INVALID_RULES`           | 422  | The LLM-proposed segment rules failed validation              | Rephrase the prompt or edit and re-submit the rule JSON                        |

## 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.
