This guide turns what a card tap gives you, a uid or a URL, into something you can render. Every call is unauthenticated: no API key, no account, no user consent. That makes it usable from a browser, a kiosk, or a link preview.
1. Work out what you have
| What you have | Do this |
|---|---|
| A card uid | Fetch the document directly at /c/{uid}/json |
| A card or profile URL | Fetch the page and follow its discovery link |
| A URL on some other host | The same: discovery works the same way |
The direct fetch is one hop instead of two. Use it whenever you have the uid.
curl -H "Accept: application/vnd.davi.card+json" \
"https://davi.social/c/CARD_UID/json"
2. Discover from a URL
When you have only a URL, fetch it and look for the alternate link:
const ACCEPT = "text/html, application/vnd.davi.card+json, application/json";
const res = await fetch(url, { headers: { Accept: ACCEPT } });
// A card document may come straight back — check before parsing as HTML.
const type = res.headers.get("content-type") ?? "";
if (type.includes("+json") || type.includes("application/json")) {
return await res.json();
}
const html = await res.text();
const tag = html.match(
/<link[^>]+type=["']application\/vnd\.davi\.card\+json["'][^>]*>/i,
);
const href = tag?.[0].match(/href=["']([^"']+)["']/i)?.[1];
if (!href) return null; // nothing to display
return await (await fetch(new URL(href, res.url))).json();
A profile URL advertises the owner's primary card. If they have none, the link is absent and there is no card document to reach. That is a valid state, not an error.
3. Check the version before the fields
const m = /^(\d+)\.(\d+)$/.exec(card?.version ?? "");
if (!m) return null; // not a card document
if (Number(m[1]) !== 1) return null; // a major you don't read
Accept any minor version. Code written against 1.0 can read a 1.7
document.
4. Decide what to show
Branch on status, not on which fields are present:
status | What it means | Reasonable UI |
|---|---|---|
claimed | Somebody holds this card | Show the holder |
unclaimed | Nobody holds it yet | Offer to claim it |
frozen | Held, but withheld as a credential | Show the holder, don't act on the card |
A frozen card still carries its holder's details, so owner_name being
present does not mean the card is live.
Render owner_name as the display name. It is the one name field that applies
to every holder. owner_given_name and owner_family_name refine it for people
and are absent for organizations, so a UI built only on those breaks on an
organization-held card.
5. Handle the empty cases
404 + application/problem+json → no card to show
no discovery link on a page → no card to show
version major you don't read → no card to show
A 404 carries an RFC 9457 problem document. The same body comes back for a card
that does not exist and for one that exists but has nothing to show. Treat both
as "nothing to display".
Next
The document never carries an email address or a phone number, whoever reads it. If your product signs people in, authenticated calls add what it leaves out:
- Fetching a Card: the cards the signed-in user holds.
- Transactions: their wallets and balances.
- Card JSON Format: every field, and the transport contract.