Skip to main content

Concepts

The identity key

The cross-app key is the email, hashed:

hash = sha256( lower( trim( email ) ) ) // 64 hex chars — byte-identical to Gravatar's spec
  • Email is the identifier every app already has, and it's stable no matter how a user signed in — so it can be the shared key without any coordination.
  • This is what makes adoption free. An app already knows its user's email, so it computes the hash itself and builds the URL. It never calls a Portrait API, holds a Portrait secret, or stores a Portrait id.
  • Portrait derives the same hash from your email at sign-in. Both sides independently arrive at the same key — that's the join.
  • The hash is not a secret. It's a public identifier, safe in client-side HTML. An unknown hash returns a generated avatar, so it's no email-existence oracle.

Three states for any hash

StateServes
GeneratedNothing saved — a deterministic avatar derived from the hash, the same everywhere, with nothing stored.
DesignedAn avatar you built in the option editor (incl. ✨ Ask AI output).
UploadedA real photo, normalized to a square.

Because a generated avatar is derived from the hash, the same person gets the same generated face in every app, every time — no account needed.

✨ Ask AI, briefly

Ask AI turns a photo into a designed avatar: drop a photo and Portrait suggests a set of editor options that open in the Design editor, where you tweak and save through the normal designed path. It's assisted, not an exact likeness, and the photo is discarded, never stored. See POST /me/avatar/from-photo.

Accounts & claims

An account is not an email. A Portrait account anchors on your sign-in; emails are claims attached to it, each proven by a clicked magic link. One account may claim several addresses (work + personal), and every verified claim resolves to the same avatar — so one face follows a person across every address they use.

This dissolves "change my email" as a feature: you add a claim, verify it, set it primary, and drop the old one. There's always at least one primary — it can't be removed without promoting another first.

A verified claim is globally exclusive: one verified hash, one account, first claim wins. A second account attempting the same address is refused. Moving an address between accounts means releasing it from the first.

Verification and login are the same act

A clicked magic link proves control of an inbox. That single proof serves both purposes:

Magic link sent toResult
Unclaimed addressCreate account; that address becomes primary
Address verified on account ALog into A
Address claimed from a session on AVerify it onto A
Address verified on another account BRefuse — already connected

Because verification is login, a magic link to a claimed address always logs into its owner — it never forks a new account off a secondary address. (This is a known Gravatar wart that Portrait fixes; see How Portrait differs from Gravatar.)

Reads and writes

  • Reads (media.portrait.intrasys.ai) are public and unauthenticated, like Gravatar. Every well-formed hash resolves to something — a saved avatar or a generated one.
  • Writes (api.portrait.intrasys.ai) require a Portrait sign-in and only ever act on hashes the session user has verified.

When you save, reset, or claim an address, your one avatar stays consistent across every verified address on your account — you manage a single face, not one per email.

How Portrait differs from Gravatar

Gravatar is the reference design, matched wherever matching is free — identical hash spec, aligned params, the same ~5-minute cache default, one-<img> integration. The deliberate differences:

GravatarPortrait
Sign in with secondary emailsteals the address into a new accountlogs into the owning account
Customise generated artpick from a galleryfull option editor + ✨ Ask AI from a photo
Avatar upload APInone documented — web UI onlyPOST /me/avatar
Different image per emailyesno — one avatar per account
Content rating (r=)user self-ratesnone — r= accepted, ignored
Profile data (bio, links)yes, expandingnon-goal — the image only
HostingAutomattic SaaSself-hosted by Intrasys