---
title: "Link external records to people and companies"
method: POST
path: "/identify"
tags: ["Identify"]
---

# Link external records to people and companies

`POST /identify`

## Request

- Base URL: `https://api.modem.dev/v1`
- URL: `https://api.modem.dev/v1/identify`
- Auth: HTTP bearer

Idempotently link external platform records (e.g. Stripe customers) to Modem people and companies.

Each `person` / `company` block states the facts the platform knows about that side of the record — not an assertion of how to resolve it. Modem owns all matching.

**Person** — matched by platform `identity` first, then by exact `email`. A bare email matches an existing person but never creates one (it may be a shared inbox like billing@); creation requires `displayName`.

**Company** — matched by the first hit, in trust order:

1. an existing platform `identity` link for this block
2. the resolved person's current company
3. stated `domains`, then the domain derived from `person.email` (free-mail domains such as gmail.com carry no signal)
4. an exact, case-insensitive `name` match
5. otherwise created — only when `name` is stated

When both a person and a company resolve, their membership is recorded.

If the platform identity and a natural key (email/domain) resolve to different entities, the identity match wins and nothing is merged; the other entity is returned as `conflictWith` — a probable duplicate to reconcile deliberately.

Removing a stored link is not part of this endpoint; it will ship separately.

## Headers

- `Idempotency-Key` string

## Request body

- object
  - `identifications` object[], required
    - `company` object
      - `description` string
      - `domains` string[]: Natural keys. Matches (or create-seeds) a company by domain. A free-mail domain never matches, creates, or is stamped (no signal), but does not suppress name-keyed creation.
      - `identity` object: Platform pointer. Matched identity-first; upserted onto the matched/created company.
        - `platform` string, required: Source platform slug (e.g. "stripe", "salesforce").
        - `platformId` string, required: The record id on the platform (e.g. a Stripe customer id like "cus_…").
      - `logoUrl` string, uri
      - `name` string: Enables creation when no match is found.
      - `overwrite` string[]: Scalar fields to overwrite even when already populated. Default is set-once.
    - `person` object
      - `avatarUrl` string, uri
      - `description` string
      - `displayName` string: Enables creation when no match is found.
      - `email` string, email: Natural key. Matches a person by exact, lowercased email. Never creates on its own.
      - `identity` object: Platform pointer. Matched identity-first; upserted onto the matched/created person.
        - `platform` string, required: Source platform slug (e.g. "stripe", "salesforce").
        - `platformId` string, required: The record id on the platform (e.g. a Stripe customer id like "cus_…").
      - `overwrite` string[]: Scalar fields to overwrite even when already populated. Default is set-once.

## Response `200`

OK

- object
  - `results` object[], required
    - `company` object, nullable, required
      - `conflictWith` string, nullable, required: A probable duplicate surfaced as a merge candidate: the platform identity resolved to `id`, but the natural key (email/domain) is owned by this different existing entity. identify never merges — it's left for a deliberate merge. Null when there was no conflict.
      - `created` boolean, required
      - `id` string, required
    - `membershipLinked` boolean, required
    - `person` object, nullable, required
      - `conflictWith` string, nullable, required: A probable duplicate surfaced as a merge candidate: the platform identity resolved to `id`, but the natural key (email/domain) is owned by this different existing entity. identify never merges — it's left for a deliberate merge. Null when there was no conflict.
      - `created` boolean, required
      - `id` string, required

## Other responses

- `400`: Invalid request (validation failed).
- `401`: Missing or invalid API key.
- `403`: The API key does not have access to this resource.
- `404`: The requested resource was not found.
- `409`: The request conflicts with an existing resource or idempotency operation.
- `429`: Rate limit exceeded.
- `500`: Internal server error.

---

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