---
title: "Import Contacts"
method: POST
path: "/imports/{import_id}/contacts"
tags: ["Imports"]
---

# Import Contacts

`POST /imports/{import_id}/contacts`

## Request

- Base URL: `https://be.graph8.com/api/v1`
- URL: `https://be.graph8.com/api/v1/imports/{import_id}/contacts`
- Auth: HTTP bearer

Import the file's rows as contacts into a list. THIS WRITES REAL RECORDS.

Two separate authorisations, both resolved for the key's principal and both
required - neither implies the other:

  * may this caller CREATE contacts at all (`enforce_object_create`);
  * may they WRITE to this particular list (`verify_list_access`).

`update_policy` defaults to `override`: a row matching an existing contact
REPLACES that contact's fields rather than only filling blanks. Send
`autofill` if the file should not win over what is already there.

## Path parameters

- `import_id` string, required: Id returned by the upload.

## Headers

- `Authorization` string, nullable
- `X-Target-Org-Id` string, nullable

## Request body

- ImportContactsRequest: Import a previously uploaded file's rows as contacts into one list.
  - `list_id` integer, required: Audience list the contacts land in. REQUIRED - an import with no destination has nowhere to put the rows, and the caller needs WRITE access to this list, not just read.
  - `column_mapping` object: Source column -> graph8 field. An EMPTY mapping is legal and means the importer falls back to its own header matching, which is why this is not required - but an unmapped column the importer cannot recognise is dropped silently, so preview first.
  - `update_policy` 'autofill' | 'override': What happens when a row matches an existing contact. `override` replaces the field; `autofill` only fills one that is currently EMPTY. The default is `override`, matching the app - so an import run without thinking about this WILL overwrite existing data.
  - `enrich_from_graph8` boolean: Match imported rows against the graph8 index and fill missing fields - contacts by work email or LinkedIn URL, companies by domain. Off by default because it is extra work the caller may not want on a file they already trust.

## Response `200`

Successful Response

- ApiResponseImportContactsResponse
  - `data` ImportContactsResponse, required: What the import actually did.
    - `imported` integer, nullable
    - `updated` integer, nullable
    - `skipped` integer, nullable
    - `failed` integer, nullable
    - `list_id` integer, nullable
  - `pagination` PaginationMeta
    - `page` integer, required: Current page number (1-indexed)
    - `limit` integer, required: Items per page
    - `total` integer, required: Total number of items
    - `has_next` boolean, required: Whether there are more pages
    - `next_cursor` string, nullable: Opaque cursor for the next page; null when there are no more items. Pass it back as the `cursor` query param to fetch the following page without computing page numbers.
    - `total_available` integer, nullable: Search endpoints only: how many records really match, when known. Can be larger than `total`, which is capped at the 10,000 results that can be paged. For company search this is a distinct-company estimate. Null when unknown; absent outside search.
    - `total_is_lower_bound` boolean, nullable: Search endpoints only: true when more records match than `total` shows (the result set is larger than the 10,000 results that can be paged). Absent outside search.
    - `notes` string[], nullable: Search endpoints only: plain-language notes about the result, e.g. that more records match than can be paged, or that a reversed `between` range was put in order.

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/graph8/apis/graph8-developer-api.md) · [All operations](https://skmtc.dev/graph8/apis/graph8-developer-api/llms.txt) · [OpenAPI document](https://skmtc.dev/graph8/apis/graph8-developer-api/revisions/2e7cfa0ca921?raw)
