---
title: "Merge people"
method: POST
path: "/api/people/{person_id}/merge"
tags: ["people"]
---

# Merge people

`POST /api/people/{person_id}/merge`

## Request

- Base URL: `https://api.gumnut.ai`
- URL: `https://api.gumnut.ai/api/people/{person_id}/merge`
- Auth: one of:
  - HTTP bearer
  - HTTP bearer

Merges one or more source people into the primary person identified by the URL. All faces and bodies from source people are reassigned to the primary person. Source people are permanently deleted (this cannot be undone).

In the degenerate case where the primary and all sources are unnamed and have zero faces, the primary is deleted too, its bodies are left with no person, and the response is `204 No Content`.

## Path parameters

- `person_id` string, required: Person ID (with `person_` prefix) of the primary person that absorbs the source people. Person IDs are carried by the `id` field of person responses (e.g. `list_people`, `get_person`) and by person references on faces and assets.

## Request body

- PersonMerge
  - `source_person_ids` string[], required: IDs of the people to merge into the primary person. These people will be deleted after their faces are moved.

## Response `200`

Successful Response

- PersonResponse: Represents a person identified through face clustering and recognition.
  - `id` string, required: Unique person identifier with 'person_' prefix
  - `name` string, nullable: Name assigned to this person, or null when unnamed. Names supplied as text on create or update are trimmed and must not be blank.
  - `birth_date` string, date, nullable: Optional birth date of this person
  - `is_hidden` boolean, required: Whether this person should be hidden from the UI
  - `is_favorite` boolean, required: Whether this person is marked as a favorite
  - `asset_count` integer, nullable: Number of unique photos this person appears in, by a face or a body linked to them, or null if not computed
  - `thumbnail_face_id` string, nullable: ID of the face resource used as this person's thumbnail
  - `asset_urls` object, nullable: Asset variants from this person's thumbnail face. May be null when embedded in an AssetResponse; use /api/people endpoints for full person data.
  - `cluster_metrics` ClusterMetricsResponse: Cohesion metrics for a Person's face cluster — surfaced via `include=cluster_metrics` on the people endpoints. These describe how tight the cluster is in embedding space (lower = more cohesive).
    - `pairwise_p90` number, required: 90th-percentile pairwise cosine distance between faces in this person's cluster. Lower = more cohesive cluster.
    - `pairwise_mean` number, required: Mean pairwise cosine distance between faces in this person's cluster.
    - `face_count` integer, required: Number of faces that fed into the centroid and pairwise metrics. This is the cluster-membership count, **not** the same as `asset_count` — `face_count` counts every face row, while `asset_count` counts distinct assets, including those linked by a body (one asset can contribute multiple faces of the same person).
  - `created_at` string, date-time, required: When this person record was created
  - `updated_at` string, date-time, required: When this person record was last updated

## Other responses

- `204`: Primary person was deleted (primary and all sources unnamed with zero faces). The response is empty.
- `401`: Missing, invalid, or expired credentials.
- `403`: The credentials are valid but not authorized for this operation — for example an API key whose action or library scope excludes it, or a credential type this operation does not accept.
- `404`: Not found
- `409`: Clerk sign-in could not be associated with the existing account. Resolve the identity conflict before retrying sign-in; repeated requests with unchanged verification and identity state will not resolve it.
- `422`: Validation Error
- `429`: Rate limit exceeded, or too many of your requests are already in progress. Retry after the interval in the `Retry-After` header.
- `503`: Authentication provider temporarily unavailable. Retry sign-in later.

## Changes

- **2026-10-16** `7c2860d9eb88`: 2 info
  - added the non-success response with the status `409`
  - added the non-success response with the status `503`
- **2026-08-08** `815e0302a988`: 1 breaking, 1 info
  - response property `detail` list-of-types was widened by adding types `string` to media type `application/json` of response `422`
  - the response property `detail` became required for the status `422`
- **2026-08-05** `c3e15c78f2da`: 4 info
  - added the media type `application/json` for the response with the status `404`
  - added the non-success response with the status `401`
  - added the non-success response with the status `403`
  - added the non-success response with the status `429`

[Operation history](https://skmtc.dev/gumnut-ai/apis/gumnut-api/changes/api/people/:person_id/merge/post.md)

---

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