ServicesAlias

Identity Merges

When one person ends up with two accounts, a merge folds the duplicate into the survivor. Approving one does three things at once:

  • every moved alias is reassigned to the target user,
  • a redirect is written from the source user id to the target, and
  • the merge record is stamped completed with the ids it actually moved.

Anyone may request a merge; only a platform admin may execute one.

Requesting

snug alias merge request --source docs4-dup-acct --target docs4-keep-acct \
  --reason "Duplicate account created during signup"
{
  "merge_id": "mrg_pVnTpMgNcsMAJgQnHYRc",
  "source_user_id": "docs4-dup-acct",
  "target_user_id": "docs4-keep-acct",
  "status": "pending_review",
  "alias_ids_to_move": [],
  "moved_alias_ids": [],
  "created_at": "2026-08-28T23:10:22.272350Z",
  "completed_at": null
}

Leaving alias_ids_to_move empty means all of the source's active aliases. Pass --alias-id (repeatable) to move a subset. Source and target must differ, or the request is 400 invalid_request. Nothing checks that either id belongs to a real user, so a typo in --target produces a merge that completes into an id nobody owns.

Approving and rejecting

snug alias merge approve --merge-id mrg_pVnTpMgNcsMAJgQnHYRc
snug alias merge reject  --merge-id mrg_xTgqqZNGQXDHRvSjWjJC --reason "evidence insufficient"

Each acts on a merge still in pending_review, and both need a platform admin token - an ordinary caller gets 403 insufficient_permissions. Approval reports what actually moved:

{
  "merge_id": "mrg_pVnTpMgNcsMAJgQnHYRc",
  "source_user_id": "docs4-dup-acct",
  "target_user_id": "docs4-keep-acct",
  "status": "completed",
  "moved_alias_ids": [
    "als_DaNVqFLgDSAxAFubRccq",
    "als_xTmBbhusDKTdzQnKXeJy"
  ],
  "completed_at": "2026-08-28T23:10:27.167674Z"
}

There is no endpoint that reads a merge back. Re-approving a completed merge (or re-rejecting a rejected one) is a no-op returning the current record, which is the closest thing to a read. Crossing between the two terminal states is refused with 409 invalid_merge_transition.

What the redirect does

The redirect is what makes a merge stick. Afterwards the source user id still works everywhere: resolving any moved alias returns the target user, and snug alias list --user docs4-dup-acct looks the target up instead. Listing still applies the caller's own visibility, so an outsider sees pagination.total: 2 alongside an empty array where an admin sees both aliases.

Redirects are collapsed, not chained. Merging dup -> keep and then keep -> final rewrites the first pointer so dup lands on final directly:

docs4-alias-dup   -> docs4-alias-final
docs4-alias-dup2  -> docs4-alias-final
docs4-alias-keep  -> docs4-alias-final

The redirect is written whether or not you moved everything. A partial merge leaves the unmoved aliases owned by the source on paper while resolution already points at the target, so an alias can report user_id: docs4-alias-dup2 on GET and still resolve to docs4-alias-final. Move everything unless you have a reason not to.

Conflict policy

conflict_policy.revoked_aliases decides what happens to the source's revoked aliases, and only bites when you name them in alias_ids_to_move - an empty list sweeps up active aliases only, so revoked ones stay put either way. With a source holding one revoked and one active alias, both named explicitly:

  • preserve_revoked (default) moved both, leaving the revoked one attached to the survivor with its revoked_at intact.
  • drop_revoked moved only the active one.
curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"source_user_id":"docs4-alias-dup3","target_user_id":"docs4-alias-final",
       "reason":"Drop the dead link",
       "conflict_policy":{"revoked_aliases":"drop_revoked"}}' \
  http://localhost:4000/api/v1/alias/merge

The body also accepts conflict_policy.primary_identity and conflict_policy.metadata. Both are stored on the merge record but are not consulted when it executes, so do not rely on them to pick a winning primary or reconcile metadata - do that yourself afterwards with primary set.

Reference

  • Alias API - the merge endpoints and their full request bodies
  • Alias overview - linking, resolution, and revocation

On this page