---
title: "Check Import Duplicates"
method: POST
path: "/imports/{import_id}/check-duplicates"
tags: ["Imports"]
---

# Check Import Duplicates

`POST /imports/{import_id}/check-duplicates`

## Request

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

Count how many rows of an uploaded file already exist, before importing. Writes nothing.

Contacts are matched by work email, companies by domain. The response
carries the counts and, for a sample of matched rows, YOUR OWN file row
(`csv_data`) with `matched: true`. It never returns the existing record:
the app handler matches across the whole organisation with no record
scope, so returning its `existing` details would let a key whose contact
visibility is own/team look up the identity behind any email. Use the
contact and company reads for the record itself, which are scope-gated.

Needs `contacts:read` (entity_type `contact`) or `companies:read`
(`company`) as well as `lists:read`. The file must belong to the key's
organisation (the load is org-scoped).

## Path parameters

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

## Headers

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

## Request body

- CheckImportDuplicatesRequest: Which uploaded rows already exist in graph8.
  - `column_mapping` object: Source column -> graph8 field, the same mapping you intend to import with.
  - `entity_type` 'contact' | 'company': `contact` matches on work email; `company` matches on domain.

## Response `200`

Successful Response

- ApiResponseCrmRecordPayload
  - `data` CrmRecordPayload, required: An open envelope for one record, one dashboard or one job receipt. Open because most of these reads are COMPUTED shapes - an engagement summary, a next-best-step, an intelligence status - that grow with the product, and dropping a field from an advisory read makes it say "nothing to report" rather than "we did not tell you".
  - `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)
