---
title: "Assert Contacts Batch"
method: PUT
path: "/contacts/assert/batch"
tags: ["Assert / Upsert"]
---

# Assert Contacts Batch

`PUT /contacts/assert/batch`

## Request

- Base URL: `https://be.graph8.com/api/v1`
- URL: `https://be.graph8.com/api/v1/contacts/assert/batch`
- Auth: HTTP bearer

Batch create or update up to 100 contacts. Matches by work_email, then linkedin_url.

## Headers

- `Authorization` string, nullable
- `X-Target-Org-Id` string, nullable

## Request body

- BatchContactAssertRequest
  - `list_id` integer, required: Target list ID
  - `contacts` object[], required: Up to 100 contacts with work_email or linkedin_url
  - `create_missing_fields` boolean: When true, any key in `custom_fields` that matches no existing column is created as a NEW list-scoped custom column (max 100 per request) and then written. Default false preserves the previous behaviour of not creating columns implicitly.

## Response `200`

Successful Response

- ApiResponseBatchAssertResponse
  - `data` BatchAssertResponse, required
    - `total` integer, required
    - `created` integer
    - `updated` integer
    - `errors` object[]
    - `custom_fields` CustomFieldWriteReport: How a write path handled the request's custom (dynamic) column keys. Shared by every Developer API write surface that accepts custom columns (``POST /contacts``, ``PUT /contacts/assert[/batch]``, ``PUT /companies/assert[/batch]``). Every field defaults to empty, so a client that ignores this object sees no behavioural change. It exists because these keys were previously dropped with NO caller-visible signal — the only trace was a server-side ``logger.warning`` in ``append_to_audience``.
      - `written` string[]: Caller keys that resolved to a custom column and were written.
      - `created` string[]: Caller keys for which a NEW custom column was created by this request.
      - `ignored` string[]: Caller keys that matched no custom column and were NOT written. Create the column first (POST /fields) or pass create_missing_fields=true.
    - `warnings` string[]: Advisory notes about values saved as sent that look wrong (e.g. an unrecognised seniority or country), prefixed with the row index. Empty when the input is clean.
  - `pagination` PaginationMeta
    - `page` integer, required: Current page number (1-indexed)
    - `limit` integer, required: Items per page
    - `total` integer, required: Total number of items
    - `has_next` boolean, required: Whether there are more pages
    - `next_cursor` string, nullable: Opaque cursor for the next page; null when there are no more items. Pass it back as the `cursor` query param to fetch the following page without computing page numbers.
    - `total_available` integer, nullable: Search endpoints only: how many records really match, when known. Can be larger than `total`, which is capped at the 10,000 results that can be paged. For company search this is a distinct-company estimate. Null when unknown; absent outside search.
    - `total_is_lower_bound` boolean, nullable: Search endpoints only: true when more records match than `total` shows (the result set is larger than the 10,000 results that can be paged). Absent outside search.
    - `notes` string[], nullable: Search endpoints only: plain-language notes about the result, e.g. that more records match than can be paged, or that a reversed `between` range was put in order.

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/graph8/apis/graph8-developer-api.md) · [All operations](https://skmtc.dev/graph8/apis/graph8-developer-api/llms.txt) · [OpenAPI document](https://skmtc.dev/graph8/apis/graph8-developer-api/revisions/2e7cfa0ca921?raw)
