---
title: "Merge a duplicate principal into the current one"
method: POST
path: "/agent/v1/control-plane/merge"
tags: ["ControlPlane"]
---

# Merge a duplicate principal into the current one

`POST /agent/v1/control-plane/merge`

## Request

- Base URL: `https://api.run402.com`
- URL: `https://api.run402.com/agent/v1/control-plane/merge`
- Auth: none declared

Consolidate a duplicate principal (source) into the one this session belongs to (target). Possession proof of BOTH sides: this session with a fresh passkey step-up (principal.merge; the action-bound elevation binds to source_principal_id), plus a live source-session token authenticated within the last 10 minutes. Phase 1 requires an authority-empty source (no active org memberships, project grants, or siwx authenticators). Atomically re-points active authenticators + verified emails to the target, revokes the source's sessions (revoked_reason=merged), disables the source with merge traceability, and writes a principal.merge audit row in the same transaction.

## Request body

- object
  - `source_session_token` string, required: A live control-plane session token for the principal being merged (sign in to it, then pass its token here).
  - `source_principal_id` string: The source principal id the step-up elevation was bound to; must match the token's principal.

## Response `200`

Merged { status, source_principal_id, moved, revoked_source_sessions, note }

- object

## Other responses

- `401`: Source proof invalid or stale (INVALID_AUTH / MERGE_SOURCE_SESSION_STALE)
- `403`: STEP_UP_REQUIRED
- `409`: MERGE_SOURCE_HAS_AUTHORITY / MERGE_SOURCE_HAS_WALLET / MERGE_PRINCIPAL_DISABLED

---

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