{"revision":"0ed62d2c742f8e20","language":"en","entry":{"id":"person-card","kind":"endpoint","group":"cards","title":"Read a person’s card","method":"GET","path":"/u/{card}.json","paths":["/u/{card}","/v1/identities/{n}"],"summary":"Read the full public card, signed record, proof and recent history.","response":"The card includes n, id, url, type, keys, links and proof. historyBefore points to older history. An erased card returns a public tombstone with HTTP 410.","parameters":[{"name":"card","description":"A permanent card number or its current alias.","in":"path","required":true}],"sample":"/u/1.json","access":"public","base":"directory","notes":["Without .json, send Accept set to application/json. The /v1 variant requires the permanent number.","On a log in format 2 the card adds format, current, authority, authorityNext, state, index and standing, and proof carries the v2 entry instead of the record. index.statements is the first page of the card’s staff statements, and index.statementsNext leads to the next one."],"errors":["400 invalid_parameter","401 invalid_api_key","403 api_key_scope","403 api_key_suspended","403 developer_suspended","404 not_found","410 erased","429 rate_limited","500 internal_error","503 busy"],"related":["statements","external-keys-file"],"sections":[{"kind":"code","language":"shell","code":"curl -fsS 'https://veil.rip/u/1.json'"},{"kind":"code","language":"javascript","code":"const response = await fetch(\"https://veil.rip/u/1.json\");\nconst result = await response.json();"}],"verify":"Verify proof, the version chain and the expected identity. The summary endpoint alone does not carry this proof.","live":true,"responses":[{"status":200,"contentType":"application/json","description":"The card includes n, id, url, type, keys, links and proof. historyBefore points to older history. An erased card returns a public tombstone with HTTP 410.","fields":[{"name":"n","type":"integer","description":"Permanent card number."},{"name":"url","type":"string","description":"Permanent URL using the card number."},{"name":"name","type":"string","description":"Current display name; a hidden name becomes Card followed by its number."},{"name":"initials","type":"string","description":"Initials for an image fallback."},{"name":"since","type":"string","description":"Version-one date in YYYY-MM-DD UTC."},{"name":"id","type":"string","description":"Version-one commitment in hex; the source of the fingerprint."},{"name":"idSalt","type":"object | null","description":"server and client salt halves in hex. client can be null."},{"name":"token","type":"string","description":"Stable public-log pseudonym in base64url."},{"name":"version","type":"integer","description":"Current published version."},{"name":"keys","type":"array<CardKey>","description":"All device and recovery keys, including revoked keys."},{"name":"links","type":"array<CardLink>","description":"Linked accounts and pending takeovers."},{"name":"orgs","type":"array<CardOrg>","description":"Accepted organisation affiliations of a person."},{"name":"events","type":"array<CardEvent>","description":"Newest page of card history, ordered oldest first within the page."},{"name":"witnesses","type":"object | null","description":"signed, of and needed counts for the current version. These are server checks."},{"name":"nodes","type":"integer | null","description":"Number of hashes in the current inclusion path."},{"name":"fingerprint","type":"Fingerprint","description":"Twelve words in each language and twelve five-digit groups."},{"name":"proof","type":"CardProof | null","description":"Current opening and inclusion proof. Null means the log could not be verified now; never treat it as verified."},{"name":"erasure","type":"object | null","description":"requestedAt and executesAt for a pending erasure."},{"name":"type","type":"string","description":"person or organization."},{"name":"members","type":"array<CardMember>, optional","description":"Present for an organisation, including pending invitations."},{"name":"moderation","type":"object | null","description":"Public moderation state and actions."},{"name":"eventsTotal","type":"integer","description":"Total history events across all pages."},{"name":"historyBefore","type":"integer | null","description":"Pass as before to the history endpoint to read the older page."},{"name":"badges","type":"array<object>","description":"Active signed or derived badges."},{"name":"nameHidden","type":"object | null","description":"Public reason, since and logIndex for a hidden name."},{"name":"logo","type":"object, optional","description":"Image type and URL; omitted without a visible image."},{"name":"logoHidden","type":"object, optional","description":"Public reason for a hidden logo."},{"name":"alias","type":"string | null","description":"Current alias; it can change."},{"name":"aliasUrl","type":"string | null","description":"Current alias address. Use n as the permanent identifier."},{"name":"format","type":"integer | optional","description":"2 on a log in format 2; absent in format 1."},{"name":"current","type":"V2Version | optional","description":"Format 2: the current version with label, body, signature, signedBy, logIndex, publishedAt and entry."},{"name":"authority","type":"array<V2Version> | optional","description":"Format 2: authority versions from version 1, at most 50, each with its log entry, i and inclusion proof on the index checkpoint."},{"name":"authorityNext","type":"integer | null | optional","description":"Format 2: where GET /v1/identities/{n}/authority?after= continues, or null."},{"name":"state","type":"object | optional","description":"Format 2: head, keys, links, orgs, the first page of members, external (public keys from elsewhere, [] when none) and the set roots; roots.external is there only when the card has public keys from elsewhere."},{"name":"external","type":"array<CardExternal> | optional","description":"Format 2: the public keys from elsewhere as the page shows them, with the server’s check in proofOk. Your client checks each proof again from state.external and does not rely on proofOk."},{"name":"index","type":"V2IndexBundle | null | optional","description":"Format 2: the card’s index bundle on the proof’s checkpoint, as the log serves it at /index/<token>, with statements and statementsNext added. Null when the log gave none."},{"name":"standing","type":"integer | null | optional","description":"Format 2: the log position of the card’s last staff statement as the server knows it, or null. A staff browser puts it in prev of a new statement. The verified value is index.value.standing."},{"name":"alerts","type":"array<object> | optional","description":"Format 2, a person: the alerts in force on its linked accounts, one per account (platform, subject, display, state, since, to, logIndex, publishedAt). The server’s word: check them with GET /v1/identities/{n}/alerts."},{"name":"statements","type":"object | optional","description":"Format 2: the card’s signed statements in short: total, latest (the three newest: id, title, publishedAt, retracts, retracted, url), url and feed."}]},{"status":304,"contentType":"none","description":"Not modified. The rebuilt card has the same bytes as the `ETag` you sent. Reuse the card stored with it and verify it as usual. There is no response body, and an erased card still answers 410.","fields":[]}],"responseObjects":[{"id":"card-key","title":"Device key","fields":[{"name":"id","type":"string","description":"Device label shown on the card."},{"name":"alg","type":"string","description":"Readable algorithm name."},{"name":"fp","type":"string","description":"Full public key as stored in the signed record."},{"name":"ed25519","type":"string","description":"Ed25519 key or composite-key half, in hex."},{"name":"since","type":"string","description":"Date the key was added."},{"name":"state","type":"string","description":"active, offline or revoked. Offline denotes a recovery key."},{"name":"revoked","type":"string | null","description":"Revocation date, when revoked."},{"name":"role","type":"string","description":"Signing role assigned to the key."},{"name":"addedIn","type":"integer","description":"Version that added the key."},{"name":"revokedIn","type":"integer | null","description":"Version that revoked the key."}],"typeName":"CardKey"},{"id":"card-link","title":"Linked account","fields":[{"name":"service","type":"string","description":"Platform identifier."},{"name":"handle","type":"string","description":"Display handle or normalised account."},{"name":"id","type":"string | null","description":"Stable provider ID, when separate from the handle."},{"name":"how","type":"string","description":"Proof method, such as oauth, dns_txt, well_known, emailCode or backlink."},{"name":"at","type":"string","description":"Time the link was established."},{"name":"state","type":"string","description":"active or pending."},{"name":"until","type":"string | null","description":"Pending takeover deadline."},{"name":"takeover","type":"integer | null","description":"Pending takeover ID."},{"name":"unicode","type":"string, optional","description":"Unicode spelling of an international domain. Keep the ASCII handle as the identifier."},{"name":"addedIn","type":"integer | null","description":"Version that added the link."},{"name":"logIndex","type":"integer | null","description":"Zero-based log index of that version."},{"name":"check","type":"CardLinkCheck, optional","description":"The server’s periodic re-check of the proof (since 30 October 2026), for an active domain, Fediverse or GitHub link it has checked at least once. Its word, not part of the signed card."}],"typeName":"CardLink"},{"id":"card-link-check","title":"Re-check of a proof","fields":[{"name":"state","type":"string","description":"ok, failing (the place answered and the proof is not there) or unreachable (no answer)."},{"name":"at","type":"string","description":"Time of the last check that got an answer."},{"name":"since","type":"string, optional","description":"Since when the proof has not confirmed. Absent while ok."},{"name":"reported","type":"boolean","description":"True once 30 days passed without a confirmation and the owner was told. The link stays."}],"typeName":"CardLinkCheck"},{"id":"fingerprint","title":"Card fingerprint","fields":[{"name":"words","type":"object","description":"en and it each contain the same twelve word-list positions in their language."},{"name":"digits","type":"array<string>","description":"Twelve groups of five decimal digits."}],"typeName":"Fingerprint"},{"id":"card-org","title":"Organisation affiliation","fields":[{"name":"n","type":"integer","description":"Permanent card number."},{"name":"url","type":"string","description":"Permanent card URL."},{"name":"name","type":"string","description":"Display name, subject to moderation."},{"name":"role","type":"string","description":"Accepted membership role."},{"name":"since","type":"string","description":"Membership start time."},{"name":"nameHidden","type":"boolean","description":"Whether staff hide the name."},{"name":"logo","type":"object | null","description":"Image type and URL, when available."}],"typeName":"CardOrg"},{"id":"card-member","title":"Organisation member","fields":[{"name":"n","type":"integer","description":"Member’s permanent card number."},{"name":"url","type":"string","description":"Member card URL."},{"name":"name","type":"string","description":"Member’s display name."},{"name":"role","type":"string","description":"Membership role."},{"name":"state","type":"string","description":"Membership state, including pending invitations."},{"name":"since","type":"string | null","description":"Acceptance time, when accepted."},{"name":"icon","type":"string | null","description":"Role icon, when provided."},{"name":"nameHidden","type":"boolean","description":"Whether staff hide the member’s name."},{"name":"logo","type":"object | null","description":"Image type and URL."}],"typeName":"CardMember"},{"id":"card-event","title":"Card history event","fields":[{"name":"t","type":"string","description":"Publication time in ISO 8601 UTC."},{"name":"kind","type":"string","description":"Change kind, such as created, added, revoked, renamed or takeover."},{"name":"by","type":"object","description":"device, key and identity of the signer, where available."},{"name":"n","type":"integer | null","description":"Card version, or null for an external event."},{"name":"logIndex","type":"integer | null","description":"Zero-based log index."},{"name":"device","type":"string | null","description":"Device concerned by the change."},{"name":"service","type":"string | null","description":"Platform concerned by the change."},{"name":"handle","type":"string | null","description":"Account concerned by the change."},{"name":"changes","type":"array<string>","description":"All changes in this version, in a fixed order."}],"typeName":"CardEvent"},{"id":"proof","title":"Card proof","fields":[{"name":"version","type":"integer","description":"Version covered by this proof."},{"name":"record","type":"string","description":"Exact canonical JSON text that was signed. Do not reformat before verifying."},{"name":"salt","type":"string","description":"Commitment salt in hex."},{"name":"serverSalt","type":"string","description":"Server’s salt contribution in hex."},{"name":"clientSalt","type":"string | null","description":"Device’s salt contribution in hex, when used."},{"name":"commitment","type":"string","description":"SHA-256 of salt bytes followed by record bytes, in hex."},{"name":"signedBy","type":"string","description":"Public signing key."},{"name":"signature","type":"string","description":"Signature over the identity-version prefix and exact record."},{"name":"entry","type":"string","description":"Exact canonical log entry text."},{"name":"logIndex","type":"integer","description":"Zero-based leaf index."},{"name":"leafHash","type":"string","description":"SHA-256 of 0x00 followed by entry bytes, in hex."},{"name":"checkpoint","type":"Checkpoint","description":"Signed checkpoint containing size, root and note."},{"name":"inclusion","type":"array<string>","description":"Audit-path hashes in hex, deepest sibling first."},{"name":"witnesses","type":"array<string>","description":"Witness cosignature lines also present in checkpoint.note."}],"typeName":"CardProof"},{"id":"checkpoint","title":"Checkpoint","fields":[{"name":"size","type":"integer","description":"Number of leaves in the signed tree."},{"name":"root","type":"string","description":"Root hash in base64."},{"name":"note","type":"string","description":"Full signed note, including log and witness signatures."}],"typeName":"Checkpoint"},{"id":"v2-version","title":"Version in format 2","typeName":"V2Version","fields":[{"name":"label","type":"string","description":"The canonical label exactly as signed, after the prefix veil-rubrica:identity-version:v2 and a line break. For an organisation it carries signer, the member card that signs and its version. Its hash is the c that the index keeps for the version."},{"name":"body","type":"string","description":"The canonical body: the operations outside label.auth and the salt. It never carries signer. label.body is its hash."},{"name":"signature","type":"string","description":"The signature over the prefix and the label."},{"name":"signedBy","type":"string","description":"The key that signed."},{"name":"logIndex","type":"integer | null","description":"Zero-based position of the version’s entry in the log."},{"name":"publishedAt","type":"string | null","description":"Earliest cosignature time on the first checkpoint that held it."},{"name":"entry","type":"string","description":"The version’s log entry as text: key, label, signature and v. The index checks the signature from this entry alone."},{"name":"i","type":"integer, optional","description":"In the authority list of a card: the entry’s log position, the same as logIndex."},{"name":"inclusion","type":"array<string>, optional","description":"In the authority list of a card: the entry’s inclusion proof in hex, deepest sibling first, on the checkpoint of index. Absent without a verified log."}]},{"id":"v2-index","title":"Index bundle","typeName":"V2IndexBundle","fields":[{"name":"checkpoint","type":"object","description":"note, the checkpoint signed by the log with the witness cosignatures, and size."},{"name":"entry","type":"object","description":"The checkpoint’s last index entry: i, entry and its inclusion proof."},{"name":"value","type":"object | null","description":"The card’s value in the index: auth, closed, latest and standing. Null when the card is not in the index."},{"name":"proof","type":"object","description":"bitmap and siblings: the proof of value under the root of the index entry."},{"name":"tail","type":"array<object>","description":"The entries after the index entry, each with i, entry and inclusion. Apply those of this card to value yourself."},{"name":"statements","type":"array<StatementEntry> | null","description":"The first page, at most 200, of the card’s counted staff statements, newest first. The first is value.standing and each prev is the i of the next. An empty array means no statements, null means the server could not read or prove the chain."},{"name":"statementsNext","type":"string | null","description":"The address of the next page of statements, or null when the chain ends here."}]},{"id":"statement-entry","title":"Statement entry","typeName":"StatementEntry","fields":[{"name":"i","type":"integer","description":"The entry’s position in the log."},{"name":"entry","type":"string","description":"The entry text as the log holds it, signature included."},{"name":"inclusion","type":"array<string>","description":"Inclusion proof in hex, deepest sibling first, on the checkpoint of the card’s index bundle."},{"name":"by","type":"integer, optional","description":"Card number of the staff member who signed. A hint for display: the signer in the entry is what counts."}]},{"id":"card-external","title":"Public key from elsewhere","fields":[{"name":"type","type":"string","description":"ssh-ed25519, ssh-ecdsa-p256, ssh-ecdsa-p384, ssh-rsa, pgp, minisign, ed25519, ml-dsa-44, ml-dsa-65 or ml-dsa-87."},{"name":"fp","type":"string","description":"The fingerprint as its own tools print it: SHA256:… for SSH and ML-DSA, the 40 hex digits of the version 4 fingerprint in capitals for OpenPGP, the 16-digit key ID for minisign, the key in hex for Ed25519."},{"name":"label","type":"string","description":"The name the person gave the key, up to 40 characters."},{"name":"pub","type":"string","description":"The public key in its canonical form, without comment. For OpenPGP, the base64 of its packets: the primary key with its self-signatures, the user IDs the person kept, the signing subkeys."},{"name":"ids","type":"array<string> | optional","description":"OpenPGP only, when the key carries user IDs: the ones the person chose to keep, as the key writes them. Your client reads them again from pub and shows only those with a valid self-certification."},{"name":"addedIn","type":"integer","description":"The card version that added the key."},{"name":"since","type":"string","description":"When that version was published."},{"name":"logIndex","type":"integer | null","description":"The log entry of that version."},{"name":"proofOk","type":"boolean","description":"The server’s own check of the proof. Informative only."},{"name":"anchor","type":"object | null","description":"The quoted checkpoint: origin, size, root and signedAt, the quorum’s cosigning time."},{"name":"consistency","type":"object | null","description":"An RFC 6962 consistency proof {size, proof} from the quoted size to the size of the card’s checkpoint, or null."}]}],"errorFormat":"json","errorDetails":[{"status":400,"code":"invalid_parameter","description":"A path, query or body field is missing, malformed or outside its accepted range.","action":"Correct the field named in `error.message` before sending again.","scope":"endpoint"},{"status":401,"code":"invalid_api_key","description":"The bearer credential is malformed, unknown, expired or revoked.","action":"Use an active key. To use anonymous public access, remove `Authorization` entirely. Invalid credentials never fall back automatically.","scope":"optional-api-key"},{"status":403,"code":"api_key_scope","description":"The API key lacks read or verify scope, or the endpoint does not accept bearer keys.","action":"Use the correct scope. Identity writes and key management still require a device signature.","scope":"optional-api-key"},{"status":403,"code":"api_key_suspended","description":"This API key is suspended.","action":"Ask staff to restore it; another key cannot lift an account suspension.","scope":"optional-api-key"},{"status":403,"code":"developer_suspended","description":"The owner’s API account is suspended or the owner card is unavailable.","action":"Check the owner account and any staff restriction. Creating another key does not bypass suspension.","scope":"optional-api-key"},{"status":404,"code":"not_found","description":"The requested card, key, alias, code or resource was not found.","action":"Check the identifier. Temporary codes and previews may already have expired or been used.","scope":"endpoint"},{"status":410,"code":"erased","description":"The card existed and was erased or removed. The response also carries its public erasure receipt.","action":"Stop using this card as current authority. Retain only the public receipt if needed.","scope":"endpoint"},{"status":429,"code":"rate_limited","description":"An anonymous, signed-request or API-key allowance was exceeded.","action":"Wait at least `Retry-After` seconds. Share a queue and retry budget across your workers.","scope":"common"},{"status":500,"code":"internal_error","description":"An unexpected server error occurred. Internal details are not returned.","action":"Retry reads with bounded backoff. Check write results before sending a new signed operation.","scope":"common"},{"status":503,"code":"busy","description":"The application has reached a temporary concurrency or database limit.","action":"Wait `Retry-After` seconds and retry with bounded backoff.","scope":"common"}],"responseHeaders":[{"name":"Content-Type","value":"application/json","description":"Use the declared media type before choosing a parser."},{"name":"Date","value":"HTTP date","description":"Server time. Compare it with your clock when signing requests."},{"name":"ETag","value":"W/\"<SHA-256 of the JSON bytes>\"","description":"Send it back in `If-None-Match`. The server rebuilds the card with current visibility, moderation and proofs, and answers 304 only when the JSON bytes are the same."},{"name":"Cache-Control","value":"private, no-cache, must-revalidate","description":"A shared cache does not keep the card, and a client revalidates it before every reuse."},{"name":"Retry-After","value":"integer seconds, on rate_limited or busy","description":"Wait at least this long before retrying. Other 429/503 codes do not necessarily include it."}],"documentationUrl":"https://veil.rip/docs/person-card","supportsBrowserFetch":true,"browserMode":"fetch"}}