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

# Create Contacts

`POST /contacts`

## Request

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

Create a contact, optionally adding it to a list.

The contact is created independent of any list. When ``list_id`` is
provided, the contact is added to that list AFTER it is created; when
omitted, the contact is created standalone. A company is resolved and
attached from ``company_domain`` when supplied.

Duplicate handling: when the submitted ``work_email`` / ``linkedin_url``
(or the deterministic identity hash) already matches an existing contact,
the handler does NOT overwrite it — it writes nothing and skips the
``list_id`` linkage. This endpoint mirrors the in-app create and responds
``409 Conflict`` with the matched ``contact_id`` and a ``field_diff`` under
``detail`` (rather than a misleading ``201`` success). The 409 is NOT
cached under the ``Idempotency-Key``, so a later create of the same
identity is re-evaluated fresh. The 409 ``detail`` also carries ``hint``,
``upsert`` and ``update``, which name the idempotent create-or-update route
``PUT /contacts/assert`` and the ``PATCH /contacts/{id}`` for the matched
contact. To create-or-update in one call, use ``PUT /contacts/assert``.

Supports optional Stripe-style idempotency via the ``Idempotency-Key``
header — a safe retry of the same key replays the first SUCCESSFUL response
(flagged ``Idempotent-Replay: true``) rather than creating duplicate
contacts.

## Headers

- `Idempotency-Key` string, nullable: Optional. Retrying a create with the same key returns the first response instead of creating a duplicate (cached 24h). Omit for the existing non-idempotent behavior.
- `Authorization` string, nullable
- `X-Target-Org-Id` string, nullable

## Request body

- ContactCreateRequest: Request body for creating contacts.
  - `list_id` integer, nullable: Optional target list ID. When provided, the contact is added to this list after it is created; omit to create the contact without a list.
  - `first_name` string, nullable: Contact first name
  - `last_name` string, nullable: Contact last name
  - `work_email` string, nullable: Work email address
  - `personal_emails` string, nullable: Personal email addresses
  - `direct_phone` string, nullable: Direct phone number
  - `mobile_phone` string, nullable: Mobile phone number
  - `job_title` string, nullable: Job title
  - `job_department` string, nullable: Department
  - `seniority_level` string, nullable: Seniority level
  - `linkedin_url` string, nullable: LinkedIn profile URL
  - `city` string, nullable: City
  - `state` string, nullable: State/Province
  - `country` string, nullable: Country
  - `company_domain` string, nullable: Company domain for matching
  - `company_id` integer, nullable: Existing company id (mashup_companies.id) to attach
  - `custom_fields` object, nullable: Custom column values keyed by the column's display title (e.g. 'Followup 1'), its slug (e.g. 'udo_followup_1_ab12cd34'), or a case/space variant of the title. Keys matching no existing column are reported in the response's `custom_fields.ignored` unless `create_missing_fields` is true.
  - `create_missing_fields` boolean: When true, custom_fields keys that match no existing column are created as NEW custom columns (list-scoped when list_id is set) and then written.

## Response `201`

Successful Response

- ApiResponseContactCreateResponse
  - `data` ContactCreateResponse, required
    - `status` string, required
    - `count` integer, required
    - `validation_errors` string[]
    - `contact_id` integer, nullable
    - `merged` boolean
    - `company_attached` boolean
    - `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). 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/89fc11143b70?raw)
