---
title: "Create contacts in bulk"
method: POST
path: "/contacts/batch"
tags: ["Contacts"]
---

# Create contacts in bulk

`POST /contacts/batch`

## Request

- Base URL: `https://api.mepmail.dev`
- URL: `https://api.mepmail.dev/contacts/batch`
- Auth: HTTP bearer

MepMail extension: creates up to 1000 contacts in one request (Resend imports contacts only via CSV). Each item is a CreateContactRequest. `on_conflict` decides what happens to an email that already belongs to a contact — `error` (default), `skip`, or `upsert`. An upsert updates `first_name`/`last_name` only when provided, merges `properties` (provided keys overwrite), adds `segments` and upserts `topics`. `unsubscribed: true` opts the contact out; `unsubscribed: false` on an already unsubscribed contact is ignored — a batch never re-subscribes anyone, that stays an explicit PATCH /contacts/{id}. The `x-batch-validation` header picks strict (default, all-or-nothing) or permissive (valid subset written, failures listed in `errors`).

## Query parameters

- `on_conflict` 'error' | 'skip' | 'upsert': What to do with an item whose email (case-insensitive) already belongs to a contact: `error` fails the item, `skip` leaves the contact untouched and reports its id, `upsert` merges the item into it. Also decides how an email repeated inside the batch is handled: `error` fails the later occurrence, `skip` keeps the first, `upsert` collapses them into one write (later scalar fields win, associations are unioned).

## Headers

- `x-batch-validation` 'strict' | 'permissive': `strict` (default): any invalid item rejects the whole batch with that item's status and a `contacts.{index}: ` message prefix, writing nothing. `permissive`: invalid items are listed in `errors` and the valid subset is written.

## Request body

- unknown[]: 1-1000 CreateContactRequest items
  - unknown

## Response `200`

Batch processed

- BatchContactsResponse
  - `data` object[], required: One entry per successful item, in request order
    - `object` 'contact', required
    - `index` integer, required: Position of the item in the request array
    - `id` string, uuid, required: The contact's id (the existing one for skipped/updated)
    - `status` 'created' | 'updated' | 'skipped', required
  - `counts` object, required: Per-status totals over the request items; they sum to the request length
    - `created` integer, required
    - `updated` integer, required
    - `skipped` integer, required
    - `failed` integer, required
  - `errors` object[]: Permissive mode only: the failed items by request index
    - `index` integer, required
    - `message` string, required

## Other responses

- `401`: Missing, malformed or invalid API key
- `403`: Plan limit reached (strict mode)
- `404`: Unknown segment or topic (strict mode)
- `409`: Contact already exists (strict mode, on_conflict=error)
- `422`: Validation error
- `429`: Rate limit exceeded: 600 requests per minute per API key and 3,000 per team; wait for Retry-After

---

[API](https://skmtc.dev/mepmail/apis/mepmail-api.md) · [All operations](https://skmtc.dev/mepmail/apis/mepmail-api/llms.txt) · [OpenAPI document](https://skmtc.dev/mepmail/apis/mepmail-api/revisions/262a89f73730?raw)
