Alias
Canonical identity resolution across fragmented external accounts. One internal user carries many linked identities - Discord accounts, wallet addresses, domain handles, game characters, hashed recovery contacts, scoped anonymous personas - and any of them resolves back to that one user in a single lookup.
When to reach for it: turning a Discord id, wallet address, or partner account id into the internal user it belongs to; recording proof that a user owns an external handle; giving an agent a pseudonymous persona scoped to one world; folding a duplicate signup into the surviving account.
When not to: signing a user in, sessions, and OAuth provider accounts belong to Auth; a user list owned by an external directory belongs to SCIM; allocating a unique display name under a charset and reservation policy belongs to Moniker. An external-id-to-user map with no verification, revocation, or merge semantics is one KV key.
Concepts
- One prime user, many aliases. Every alias points at a single internal user id - the caller's JWT subject unless an admin names another.
- Normalization decides identity.
platformandexternal_idfold into anormalized_idunique across the tenant:walletlowercases and hex-checks,domainstrips the scheme and trailing slash,emaillowercases then SHA-256 hashes,discord/game/partnerkeep the id verbatim, anything else is lowercased. The platform name always is. - Verification is evidence, not a flag. A new link is
pending_verification; attestations promote it toverified. An attestation may carry an expiry, and once every one has lapsed the alias falls back topending_verification. - Two independent gates.
visibilitycontrols who may read the alias record;scopescontrols who may resolve through it. Resolve deliberately ignores visibility. - Primary is per scope. One alias is primary per (user, scope) pair; omitting a scope uses a single global slot, and promoting inside one scope demotes only that scope's previous holder.
- Ghost proxies are one-way. An anonymous proxy issues a random handle
bound to an audience and a purpose. The prime user id comes back only to
an admin or a caller holding
identity.proxy_inspect.
Linking an external account
snug alias link --platform discord --id docs4-903115 --handle docs4-nova --scope community.rewards
snug alias link --platform wallet --id 0xA1B2C3D4E5F6DEADBEEF
snug alias link --platform domain --id https://docs4.example.com/ --handle docs4.example.comOver HTTP the same call is POST /api/v1/alias/link:
curl -X POST -H "Authorization: Bearer $SNUG_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"platform":"discord","external_id":"docs4-903115","handle":"docs4-nova"}' \
http://localhost:4000/api/v1/alias/link{
"status": 201,
"msg": "Created",
"data": {
"alias_id": "als_cJxBynnbnwZSXbyWUWVb",
"user_id": "docs-wave",
"normalized_id": "discord:docs4-903115",
"verification_status": "pending_verification"
}
}Relinking the same normalized id as the same owner is idempotent - it
returns the original alias_id, still under a 201 Created. If a
different user holds it, the call fails with 409 alias_conflict naming
that user, the signal to open a merge. Linking on behalf of
someone else needs a platform admin token.
Resolving and listing
snug alias resolve docs4-903115 --platform discord --scope community.rewards
snug alias resolve discord:docs4-903115
snug alias resolve als_cJxBynnbnwZSXbyWUWVb
snug alias list --user docs-wave --status verified --platform discordResolution answers with the prime user_id, the alias, its verification
status, and a deliberately narrow visible_metadata - display_name,
handle, and avatar_url/locale when present. A bare external id is only
ever tried as a normalized id, so snug alias resolve docs4-903115 is
404 alias_not_found while --platform discord or the full
discord:docs4-903115 succeeds.
Scopes gate resolution: asking for a scope the alias does not list is
403 insufficient_permissions, but an alias with an empty scopes list
satisfies every request - list scopes on anything you mean to restrict.
Visibility is not consulted here, so any authenticated caller can turn a
private alias into a user id.
Listing sorts over linked_at, revoked_at, created_at, or updated_at
through the HTTP-only sort_by/sort_order; anything else is
400 invalid_request, while an unrecognised status quietly returns an
empty page. Aliases the caller may not see are filtered out after the page
is counted, so pagination.total can exceed what you received, and revoked
aliases are listed unless filtered out.
Verification and the primary identity
snug alias attest --alias-id als_cJxB... --method social_proof \
--issuer social-proof --challenge chal_docs4nova --confidence 0.95
snug alias primary set --alias-id als_cJxB... # global slot
snug alias primary set --alias-id als_cJxB... --scope display # display slotAttesting flips verification_status to verified and appends the
attestation with its issuer, proof, and confidence. Promoting a revoked
alias is refused with 400 invalid_request. Because each scope holds its
own primary, an alias primary anywhere reports is_primary: true - the flag
does not say which scope.
Anonymous proxies
snug alias proxy create --audience world:docs4-forest-01 \
--purpose agent_world_interaction --scope world.chat --expires-in 3600You get back a proxy_id, a generated handle such as phantom-3u4w, the
audience and purpose, status: active, and an expires_at. The response
omits user_id entirely for ordinary callers; only an admin or
identity.proxy_inspect sees it. Resolving a live handle refuses to
deanonymize and answers 409 alias_conflict. Once the TTL passes the handle
stops resolving at all (404), and within one sweep interval the background
worker marks the proxy expired and frees the handle - both confirmed
against a one-second proxy.
Recovery contacts
snug alias recovery add --email docs4-user@example.com --scope account.recovery
snug alias recovery verify --recovery-id rcm_zMRY... --email docs4-user@example.comOnly a salted hash is persisted; the raw contact never comes back out, and
verification re-hashes what you submit and compares. A mismatch is
400 recovery_challenge_failed. Listing someone else's contacts with
snug alias recovery list --user <id> needs a platform admin token or the
account.recovery scope.
Aggregated metadata
snug alias metadata --user docs-wave collects fields across a user's
aliases with the source alias, platform, and timestamp attached - newest
write wins, and a primary alias overrides display_name. Aggregation skips
private and revoked aliases, and private is the default visibility, so
this stays empty until you link something with "visibility":"public". The
key version inside metadata is reserved by the storage layer and is
replaced with an integer.
Revoking
snug alias revoke --alias-id als_cJxB... --reason compromisedRevocation is a tombstone, not a delete: it releases the lookup entry,
clears the alias out of every primary scope, and stamps revoked_at with
the reason, while the document survives for audit. Resolution stops
immediately, GET still works, and the external id becomes linkable again
as a brand-new alias with a new id. Revoking twice is a no-op.
Limits and configuration
100 aliases and 25 active proxies per user, 250 aliases per merge, 32 KB of
metadata, and a seven-day default proxy TTL - all tunable, along with the
expiry sweep interval, through the ALIAS_* variables in the
Alias CONFIG reference.
Reference
- Alias API - every endpoint, callable
- Identity merges - folding a duplicate user into the survivor
- Related: Auth for accounts, sessions, and OAuth linking, SCIM for directory-driven provisioning, Moniker for allocating unique names