---
title: "Bulk-import contacts from CSV"
method: POST
path: "/v1/contacts/import-csv"
tags: ["Contacts"]
---

# Bulk-import contacts from CSV

`POST /v1/contacts/import-csv`

Parse a raw CSV string and upsert the rows as contacts (≤ 1000). By default each column header maps to a field of the same name (`email` is required); pass `mapping` to remap columns explicitly. Rows with a missing/invalid email are SKIPPED (counted in `summary.skipped`), not errored; disposable-domain rows are imported with `validationStatus: "risky"` (deliverable but flagged). Returns the same `{ summary, fieldsCreated, errors, warnings }` as batch-create — `207` when some rows fail validation.

## Request body

- ContactsImportCsvRequest
  - `csv` string, required
  - `mapping` object
  - `validate` boolean — Optional deliverability check on ingestion. When true, each address is validated with the provider (2 credits per address, charged on success) and the verdict is saved to the contact’s `validationStatus`. Submissions above the inline cap (100 addresses) upsert first and validate as a background job, returning a `validationJobId`.

## Response `200`

Import summary (all rows valid).

- ContactsImportCsvResponse
  - `summary` object, required
    - `inserted` integer, required
    - `updated` integer, required
    - `failed` integer, required
    - `skipped` integer, required
  - `fieldsCreated` string[], required
  - `errors` object[], required
    - `email` string, required
    - `code` string, required
    - `message` string, required
  - `warnings` object[], required
    - `code` string, required
    - `field` string
    - `message` string, required
    - `from` string
    - `to` string
    - `original` string
    - `normalized` string
  - `validation` object
    - `valid` integer, required
    - `risky` integer, required
    - `invalid` integer, required
    - `unscored` integer, required
  - `validationJobId` string

## Other responses

- `207` — Partial success — some rows failed validation.
- `400` — The request body or query string was invalid (unknown key, wrong type, or missing required field). Strict schemas reject unknown keys — including `brandId`, which is always resolved from the API key.
- `401` — The API key was missing, invalid, or revoked.
- `403` — The caller does not have the required `contacts` permission.
- `429` — The request hit the rolling rate limit window.
- `500` — Unexpected internal error.

---

[API](https://skmtc.net/brew/apis/brew-public-api-v1.md) · [All operations](https://skmtc.net/brew/apis/brew-public-api-v1/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/brew/brew-public-api-v1/versions/db96cfedc6c5/schema)
