# Public Customers Bulk Upsert

Bulk-upsert customers (async).
Accepts up to 100,000 customers per call. The body is validated
synchronously; the actual upsert runs out-of-band on a worker.
**Response.**
- `202 Accepted` with `JobAck { job_id, status_url }` on the
first call.
- `200 OK` with the *same* `JobAck` on an idempotent replay
(same `Idempotency-Key` + same body). No second job runs.

**What to do next.** Poll `GET /jobs/{job_id}` (the `status_url`
is the canonical path) until `status` is `COMPLETED` or
`FAILED`. A `COMPLETED` job with `error_count > 0` means some
rows failed — see `errors[]` for the per-row detail. The Jobs
chapter has the full polling guide.
**Idempotency.** Pass an `Idempotency-Key` header (≤ 255 chars).
Same key with a *different* body returns
`422 IDEMPOTENCY_KEY_MISMATCH`.

Endpoint: POST /customers
Version: 1.79.2

## Header parameters:

  - `authorization` (string | null)
    Bearer <mrc_live_…> API key

  - `Idempotency-Key` (string | null)
    Optional client-supplied key for safe retries; <= 255 chars

## Request fields (application/json):

  - `customers` (array, required)

  - `customers.customer_id` (string, required)

  - `customers.name` (string, required)

  - `customers.vat_id` (string | null)

  - `customers.emails` (array | null)

  - `customers.addresses` (array | null)

  - `customers.addresses.kind` (string | null)
    Address type, e.g. billing | shipping | hq

  - `customers.addresses.street` (string | null)

  - `customers.addresses.street_number` (string | null)
    House / building number kept separate from street for ERPs that store them apart

  - `customers.addresses.postal_code` (string | null)

  - `customers.addresses.city` (string | null)

  - `customers.addresses.region` (string | null)

  - `customers.addresses.country` (string | null)
    ISO 3166-1 alpha-2 country code

  - `customers.addresses.is_default` (boolean | null)

  - `customers.custom_fields` (object | null)
    Custom fields for the customer. On the **write** path these are stored as supplied (internal column-id keyed) and are NOT label-remapped — deliberately asymmetric with the label-keyed read responses in v1.9.0 (Wave 1 partners only read).

## Response 200:

  - `200` (unknown)
    Idempotent replay — same job returned

## Response 200 fields (application/json):

  - `job_id` (string, required)

  - `status_url` (string, required)
    Relative URL to poll for this job's status

  - `warnings` (array)
    Non-fatal notices about the accepted payload — e.g. article translations for unsupported languages that were dropped. The job runs regardless; nothing listed here blocks processing. Deterministic for a given body, so idempotent replays return the same warnings.

## Response 202:

  - `202` (unknown)
    Accepted; poll status_url for completion

## Response 400:

  - `400` (unknown)
    Validation failed

## Response 422:

  - `422` (unknown)
    Idempotency-Key reused with a different body

