ServicesAlias

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. platform and external_id fold into a normalized_id unique across the tenant: wallet lowercases and hex-checks, domain strips the scheme and trailing slash, email lowercases then SHA-256 hashes, discord/game/partner keep 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 to verified. An attestation may carry an expiry, and once every one has lapsed the alias falls back to pending_verification.
  • Two independent gates. visibility controls who may read the alias record; scopes controls 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.com

Over 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 discord

Resolution 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 slot

Attesting 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 3600

You 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.com

Only 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 compromised

Revocation 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

On this page