# Preview contact import

Prepare contacts from an uploaded CSV, TSV or vCard without saving records or making an AI call. Supply zero-based column mappings for a table. Review each row, check existing contacts, then use customers.CreatePerson or customers.UpdatePerson to import them with the usual permissions and retry guarantees. Names, email addresses, phone numbers, job titles and LinkedIn URLs are supported.

POST https://api.brigg.app/v1/operations/customers.PreviewContactImport

## Access & visibility

Allow `customers.PreviewContactImport` on your API key.

**Workspace access by default.** Private member data requires [approved member delegation](https://developer.brigg.app/authentication#member-delegation) through `Brigg-Act-As`; the key supplies permissions while private ownership and project boundaries remain enforced.

[Set up authentication](https://developer.brigg.app/authentication) · [Choose permissions](https://developer.brigg.app/permissions)

## Request fields

Send a JSON object with the following fields. Required fields must be included; omit optional fields you do not need.
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `file_id` | string | Yes | Use a file_id returned by this listing or by CompleteUpload. It is not an upload ID, storage path or filename. [documents.ListDocuments](https://developer.brigg.app/reference/documents/ListDocuments). At least 1 character. |
| `has_header` | boolean | No | Whether the uploaded table's first row contains column headings. Default: `true`. |
| `mapping` | [object](#request-mapping) | No | Mapping from supported Person fields to zero-based table column indexes. |
| `limit` | integer | No | Maximum number of results to return in this page. Continue with next_cursor when returned. Default: `50`. Range: 1 to 100. |
| `cursor` | string | No | Copy next_cursor from the preceding response. Keep the same filters, sort and credential. |

### request.mapping

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `display_name` | integer | No | Full name shown for the contact. Minimum: 0. |
| `email` | integer | No | Email address. Minimum: 0. |
| `phone` | integer | No | Phone number including its country calling code. Minimum: 0. |
| `title` | integer | No | Display title. Minimum: 0. |
| `linkedin_url` | integer | No | Public LinkedIn profile URL for the person or company. Minimum: 0. |

## Response

Returns **200 OK** with a JSON body.

An empty `items` array means this page contains no matching visible results. Follow `next_cursor` when present.
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `file_id` | string | Yes | File identifier. |
| `row_count` | integer | Yes | Number of source rows detected in the uploaded file. |
| `duplicate_count` | integer | Yes | Number of source rows classified as duplicates. |
| `invalid_count` | integer | Yes | Number of source rows that did not pass the contact-field checks. |
| `items` | array of [Contact import row](#contact-import-row-response-items) | Yes | Parsed rows from the uploaded file, with validation and duplicate results. Pass next_cursor as cursor to continue when a next cursor is returned. |
| `next_cursor` | string or null | Yes | Pass this value as cursor to continue with the same filters, sort and credential. Null when there are no more results. |
| `has_more` | boolean | Yes | Whether another page of results is available. |

### Contact import row · response.items[]

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `row` | integer | Yes | One-based row number in the source file. |
| `status` | string | Yes | Preparation result for this row. Review invalid and duplicate rows before importing. Allowed: `"ready"`, `"duplicate"`, `"invalid"`. |
| `data` | [object](#response-items-data) | No | Contact fields parsed using the supplied column mapping. |
| `error` | string | No | Explanation of a row validation failure, when present. |

### response.items[].data

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `display_name` | string | No | Full name shown for the contact. |
| `email` | string | No | Email address. |
| `phone` | string | No | Phone number including its country calling code. |
| `title` | string | No | Display title. |
| `linkedin_url` | string | No | Public LinkedIn profile URL for the person or company. |

## Pagination

Pass `next_cursor` as `cursor` on the next request, keeping the same query and acting identity. Stop when `next_cursor` is null. Treat the cursor as opaque; never construct or edit it.

```typescript
const query = {
  "file_id": "f_contacts",
  "has_header": true,
  "mapping": {
    "display_name": 0,
    "email": 1,
    "title": 2
  },
  "limit": 50
};
let cursor;
do {
  const response = await fetch("https://api.brigg.app/v1/operations/customers.PreviewContactImport", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.BRIGG_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ ...query, ...(cursor ? { cursor } : {}) }),
  });
  const page = await response.json();
  if (!response.ok) throw new Error(`${response.status}: ${page.message}`);
  for (const item of page.items) {
    console.log(item); // Process or upsert each result.
  }
  cursor = page.next_cursor;
} while (cursor);
```


## Errors & retries

This operation reads data. It needs no idempotency key and can be retried after a transient failure.

For rate limits, follow the server’s retry guidance and back off. See [HTTP errors](https://developer.brigg.app/http#errors) and [safe retries and limits](https://developer.brigg.app/reliability).

| HTTP status | Code | What to do |
| --- | --- | --- |
| 400 | `invalid_json` | Send a JSON object with Content-Type: application/json. |
| 400 | `invalid_input` | Check the request fields and types. |
| 401 | `invalid_key` | The workspace API key is missing, invalid or revoked. |
| 403 | `forbidden` | Check the key’s operation and resource grants. If Brigg-Act-As is supplied, the key must be authorized to delegate to that member. |
| 404 | `not_found` | The requested operation or resource is not available to this credential. |
| 429 | `rate_limited` | Wait for Retry-After before retrying. |
| 422 | `text_file_required` | Upload a text file containing CSV, TSV or vCard data. |
| 422 | `invalid_text_encoding` | Upload UTF-8 text. |
| 413 | `source_too_large` | Contact preparation supports the existing 4,000,000-character text-file limit; no rows are truncated. |

## Next steps

- [List documents](https://developer.brigg.app/reference/documents/ListDocuments)

[Download OpenAPI](https://developer.brigg.app/openapi.json) · [Versioning & retirement](https://developer.brigg.app/versioning)

## Examples

### Example request

Fictional values. Replace IDs and business details with values from your workspace.
#### cURL


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

#### TypeScript


```typescript
const response = await fetch("https://api.brigg.app/v1/operations/customers.PreviewContactImport", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.BRIGG_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "file_id": "f_contacts",
    "has_header": true,
    "mapping": {
      "display_name": 0,
      "email": 1,
      "title": 2
    },
    "limit": 50
  }),
});
const result = await response.json();
if (!response.ok) throw new Error(`${response.status}: ${result.error} — ${result.message}`);
console.log(result);
```

#### Python


```python
import json
import os
from urllib.request import Request, urlopen
from urllib.error import HTTPError

payload = {
  "file_id": "f_contacts",
  "has_header": True,
  "mapping": {
    "display_name": 0,
    "email": 1,
    "title": 2
  },
  "limit": 50
}
request = Request(
    "https://api.brigg.app/v1/operations/customers.PreviewContactImport",
    data=json.dumps(payload).encode("utf-8"),
    headers={
        "Authorization": f"Bearer {os.environ['BRIGG_API_KEY']}",
        "Content-Type": "application/json",
    },
    method="POST",
)
try:
    with urlopen(request) as response:
        print(json.load(response))
except HTTPError as error:
    print(error.code, json.load(error))
    raise
```

#### Response example


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

## JSON schemas


```json-schema-request
{
  "type": "object",
  "properties": {
    "file_id": {
      "type": "string",
      "minLength": 1,
      "description": "Use a file_id returned by this listing or by CompleteUpload. It is not an upload ID, storage path or filename. [documents.ListDocuments](https://developer.brigg.app/reference/documents/ListDocuments)."
    },
    "has_header": {
      "type": "boolean",
      "default": true,
      "description": "Whether the uploaded table's first row contains column headings."
    },
    "mapping": {
      "type": "object",
      "properties": {
        "display_name": {
          "type": "integer",
          "minimum": 0,
          "description": "Full name shown for the contact."
        },
        "email": {
          "type": "integer",
          "minimum": 0,
          "description": "Email address."
        },
        "phone": {
          "type": "integer",
          "minimum": 0,
          "description": "Phone number including its country calling code."
        },
        "title": {
          "type": "integer",
          "minimum": 0,
          "description": "Display title."
        },
        "linkedin_url": {
          "type": "integer",
          "minimum": 0,
          "description": "Public LinkedIn profile URL for the person or company."
        }
      },
      "additionalProperties": false,
      "description": "Mapping from supported Person fields to zero-based table column indexes."
    },
    "limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 100,
      "default": 50,
      "description": "Maximum number of results to return in this page. Continue with next_cursor when returned."
    },
    "cursor": {
      "type": "string",
      "description": "Copy next_cursor from the preceding response. Keep the same filters, sort and credential."
    }
  },
  "required": [
    "file_id"
  ],
  "additionalProperties": false,
  "examples": [
    {
      "file_id": "f_contacts",
      "has_header": true,
      "mapping": {
        "display_name": 0,
        "email": 1,
        "title": 2
      },
      "limit": 50
    }
  ]
}
```


```json-schema-response
{
  "type": "object",
  "properties": {
    "file_id": {
      "type": "string",
      "description": "File identifier."
    },
    "row_count": {
      "type": "integer",
      "description": "Number of source rows detected in the uploaded file."
    },
    "duplicate_count": {
      "type": "integer",
      "description": "Number of source rows classified as duplicates."
    },
    "invalid_count": {
      "type": "integer",
      "description": "Number of source rows that did not pass the contact-field checks."
    },
    "items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "row": {
            "type": "integer",
            "description": "One-based row number in the source file."
          },
          "status": {
            "type": "string",
            "enum": [
              "ready",
              "duplicate",
              "invalid"
            ],
            "description": "Preparation result for this row. Review invalid and duplicate rows before importing."
          },
          "data": {
            "type": "object",
            "properties": {
              "display_name": {
                "type": "string",
                "description": "Full name shown for the contact."
              },
              "email": {
                "type": "string",
                "description": "Email address."
              },
              "phone": {
                "type": "string",
                "description": "Phone number including its country calling code."
              },
              "title": {
                "type": "string",
                "description": "Display title."
              },
              "linkedin_url": {
                "type": "string",
                "description": "Public LinkedIn profile URL for the person or company."
              }
            },
            "additionalProperties": false,
            "description": "Contact fields parsed using the supplied column mapping."
          },
          "error": {
            "type": "string",
            "description": "Explanation of a row validation failure, when present."
          }
        },
        "required": [
          "row",
          "status"
        ],
        "additionalProperties": false,
        "title": "Contact import row"
      },
      "description": "Parsed rows from the uploaded file, with validation and duplicate results. Pass next_cursor as cursor to continue when a next cursor is returned."
    },
    "next_cursor": {
      "type": [
        "string",
        "null"
      ],
      "description": "Pass this value as cursor to continue with the same filters, sort and credential. Null when there are no more results."
    },
    "has_more": {
      "type": "boolean",
      "description": "Whether another page of results is available."
    }
  },
  "required": [
    "file_id",
    "row_count",
    "duplicate_count",
    "invalid_count",
    "items",
    "next_cursor",
    "has_more"
  ],
  "additionalProperties": false,
  "examples": [
    {
      "file_id": "f_contacts",
      "row_count": 1,
      "duplicate_count": 0,
      "invalid_count": 0,
      "items": [
        {
          "row": 1,
          "status": "ready",
          "data": {
            "display_name": "Alex Morgan",
            "email": "alex@example.com",
            "title": "Operations manager"
          }
        }
      ],
      "has_more": false,
      "next_cursor": null
    }
  ]
}
```

