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

# contacts-import

> Bulk-import contacts from a dataset into the active Landbase account

Bulk-import the contacts in a dataset into the active account under a tag. Start
an import, read a snapshot of its progress, or block until it reaches a terminal
phase. Optionally choose what wins when an imported row matches a contact you
already have — see [duplicate handling](#duplicate-handling).

> [!NOTE]
>
> `contacts-import` is a **platform-mode** command. Before running it:
>
> 1. [`landbase-cli auth login`](https://www.landbase.com/docs/reference/auth) — authenticate the session.
> 2. [`landbase-cli account set`](https://www.landbase.com/docs/reference/account) — pick and persist the target account.
> 3. Have a `datasetId` from a prior search or agent run — the CLI does not
>    produce datasets, so run a search/agent flow first.
>
> Without an active account the command exits with `No account id...`; pass
> `--account=<id>` per call as an escape hatch.

## contacts-import start

Start an import. Reads a JSON body — `{ datasetId, tagName, importId?, onDuplicate? }`
— and enqueues processing in one call. Pass `-` to read the body from stdin. Returns
`{ importId, runRef }`. With `--watch`, the command stays attached, polls until
the import is terminal, and writes the final status to stdout instead of the
start response.

**Usage**

```bash theme={null}
landbase-cli contacts-import start --json <path|-> [--watch]
```

**--json** `string` *required*

Path to a JSON file with the import body, or `-` to read it from stdin. Body
shape: `{ "datasetId": "<dataset-id>", "tagName": "Q2 outreach", "importId"?: 7, "onDuplicate"?: "keep" | "update" }`.
`datasetId` and `tagName` (1–100 chars) are required; `importId` resumes an
existing draft; `onDuplicate` picks the
[duplicate-handling policy](#duplicate-handling).

**Flags**

| Flag | Default | Description |
| - | - | - |
| `--watch` | — | Block until the import is terminal, then print the final status to stdout |
| `--account=<id>` | — | Override the active account for a single command |
| `--verbose` | — | Print request/response details to stderr |

***

## contacts-import status

Read one status snapshot. Designed for scripts that drive their own polling loop.

**Usage**

```bash theme={null}
landbase-cli contacts-import status <importId>
```

**importId** `integer` *required*

The import id returned by `contacts-import start`.

Prints a lean, stable shape — `{ importId, phase, percent, counts, error }` —
where keys are present in every phase and `null` until known. See
[output schemas](https://www.landbase.com/docs/reference/output-schemas.md#contacts-import).

***

## contacts-import wait

Block until the import is terminal, then print the final status to stdout. Runs
the same polling loop as `start --watch`, streaming a progress line to stderr per
poll. Backoff schedule: 5s, 10s, then a 20s cap; overall timeout 30 minutes.

**Usage**

```bash theme={null}
landbase-cli contacts-import wait <importId>
```

**importId** `integer` *required*

The import id to wait on.

Prefer `wait` (or `start --watch`) over an external polling loop unless you need
a custom cadence. Avoid sub-second polling — it won't return results faster and
may trip rate limits.

***

## Duplicate handling

`onDuplicate` decides what wins when an imported row matches a contact that
already exists in the account. It's optional — omit it (or send `null`) and the
server's default handling applies, exactly as before. Matched rows land in
`counts.merged` either way.

| Value | Effect on the matched contact |
| - | - |
| `keep` | Existing values win on conflicting **identity** fields — name, email, phone, LinkedIn |
| `update` | Non-blank uploaded values overwrite those identity fields |

> [!NOTE]
>
> Either policy still updates **profile** fields (job title, location, …) from
> the file when the file carries a value — `onDuplicate` only toggles the
> identity fields. Blank values never clear existing data, and contacts locked
> by an active campaign are never overwritten.

***

## Phases

`phase` is the value to branch on. The import is done once it reaches a terminal
phase.

| Phase | Kind | Meaning |
| - | - | - |
| `draft` | in-flight | Row created but not yet enqueued (rare — `start` enqueues atomically) |
| `preparing` | in-flight | Parsing/validating the dataset; `percent` is `null` |
| `in_progress` | in-flight | Actively ingesting; `percent` is `0`–`100` |
| `completed` | terminal | Done; `counts` carries the final totals |
| `partial` | terminal | Some rows imported before a later failure; `counts` reflect what landed, `error` carries the reason |
| `failed` | terminal | Failed; `error` carries the reason |

## Errors

| Code | Meaning |
| - | - |
| `INVALID_INPUT` | Missing/invalid `--json`, malformed `datasetId`/`tagName`, a non-integer / zero `importId`, or an `onDuplicate` that isn't `keep` or `update`. |
| `AUTH_REQUIRED` | Run `landbase-cli auth login` first (browser-interactive). |
| `AUTH_FAILED` | The platform session expired (7-day TTL) — re-login. |
| `NOT_FOUND` | The `importId` is unknown or belongs to a different account. |
| `VALIDATION_ERROR` | The back-end rejected the body (e.g. `datasetId` is unknown or not accessible to this account). |
| `CONFLICT` | `start` was called again on an `importId` already enqueued. Don't retry — poll `status <importId>`; the in-flight job keeps running. |
| `TIMEOUT` | `wait` / `start --watch` exceeded 30 min still in a non-terminal phase. Resume with `status <importId>` (the id rides in the error's `meta.importId`). |

## Examples

```bash theme={null}
# Fire-and-forget, drive your own poll
echo '{"datasetId":"<dataset-id>","tagName":"Q2 outreach"}' \
  | landbase-cli contacts-import start --json -
# → { "importId": 7, "runRef": "..." }
landbase-cli contacts-import status 7

# Block-and-collect in a single invocation (terminal status to stdout)
echo '{"datasetId":"<dataset-id>","tagName":"Q2 outreach"}' \
  | landbase-cli contacts-import start --json - --watch

# Wait on an existing import
landbase-cli contacts-import wait 7

# Pick a duplicate policy ("update" lets the uploaded file win instead)
echo '{"datasetId":"<dataset-id>","tagName":"Q2 outreach","onDuplicate":"keep"}' \
  | landbase-cli contacts-import start --json - --watch
```

## Related

* [How to import contacts](https://www.landbase.com/docs/guides/how-to/import-contacts.md)
* [account command reference](https://www.landbase.com/docs/reference/account.md)
* [dialer-sessions command reference](https://www.landbase.com/docs/reference/dialer-sessions.md) — create a calling session from the imported tag
* [datasets command reference](https://www.landbase.com/docs/reference/datasets.md)
* [Output schemas](https://www.landbase.com/docs/reference/output-schemas.md#contacts-import)
* [Error codes](https://www.landbase.com/docs/reference/error-codes.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.