# Import contacts from a file

Upload a CSV, TSV or vCard, review the rows, then create contacts with ordinary record operations.

Use this flow when your integration already has a contact file. Preparation never creates or changes a contact. You decide which validated rows to apply and whether they match existing people.

## Upload the source

Follow [Upload and use files](https://developer.brigg.app/files). Use a UTF-8 CSV, TSV, plain-text table or vCard and save the completed `file_id`. The contact parser supports up to 4,000,000 text characters per file. A larger source is refused; no rows are silently truncated.

Give the key access to `customers.PreviewContactImport`, `customers.CreatePerson`, and read/write access to `core:Person`. Add `customers.ListPeople` and `customers.UpdatePerson` if you need to match and update existing contacts. A contact is a customer record; creating one does not invite a workspace member or assign an account role.

## Map and review

For a table, map column numbers starting at zero. For example, this source has a header and three columns:

```csv
Name,Email,Job title
Alex Morgan,alex@example.com,Operations manager
Taylor Chen,taylor@example.com,Project lead
```

```bash
curl 'https://api.brigg.app/v1/operations/customers.PreviewContactImport' \
  -H "Authorization: Bearer $BRIGG_API_KEY" \
  -H 'Content-Type: application/json' \
  --data '{"file_id":"f_contacts","has_header":true,"mapping":{"display_name":0,"email":1,"title":2},"limit":50}'
```

A vCard describes its own fields, so omit `mapping` for that format. The supported fields are `display_name`, `email`, `phone`, `title` and `linkedin_url`.

```json
{
  "file_id": "f_contacts",
  "row_count": 2,
  "duplicate_count": 0,
  "invalid_count": 0,
  "items": [
    {"row":1,"status":"ready","data":{"display_name":"Alex Morgan","email":"alex@example.com","title":"Operations manager"}},
    {"row":2,"status":"ready","data":{"display_name":"Taylor Chen","email":"taylor@example.com","title":"Project lead"}}
  ],
  "has_more": false,
  "next_cursor": null
}
```

`ready` rows contain `data` that satisfies the published Create person contract. An `invalid` row contains an explanation. `duplicate` means an earlier row in this source has the same normalized email, or the same name when there is no email. It does not mean the person already exists in Brigg. Empty source lines are ignored by the parser; `row` counts parsed contact rows after the header.

When `has_more` is true, pass `next_cursor` as `cursor` with the same file and mapping. Each page checks current file access. A cursor cannot be reused with a different source, mapping or credential.

## Apply the reviewed rows

For a new person, send the row's `data` to [Create person](https://developer.brigg.app/reference/customers/CreatePerson):

```bash
curl 'https://api.brigg.app/v1/operations/customers.CreatePerson' \
  -H "Authorization: Bearer $BRIGG_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: contacts-november-row-1' \
  --data '{"data":{"display_name":"Alex Morgan","email":"alex@example.com","title":"Operations manager"}}'
```

Store the returned `item.id`. Reuse this row's idempotency key when retrying its creation, so a timeout cannot create a duplicate.

If you are reconciling existing contacts, use [List people](https://developer.brigg.app/reference/customers/ListPeople) to find candidates and choose the target explicitly before [Update person](https://developer.brigg.app/reference/customers/UpdatePerson). Preparation does not guess merges or grant permission to write. Skip invalid or duplicate rows until your integration has resolved them. A failed write does not undo other rows: retain each row's result so you can resume safely.

