Card stats
Choose 2–4 stats to show under your name, in the order you want them.
The stat row under your name is your card. You pick which stats it shows and in what order; the API computes the values. The schema is card-1.json (Schemas).
The catalog
| id | Label | Value |
|---|---|---|
projects | Projects | Projects on the profile |
stars | Stars | Total GitHub stars across the profile's projects |
followers | Followers | Builders following this profile |
posts | Posts | Posts published (deleted posts excluded) |
streak | Streak | Current Daily Taste streak in days (0 once broken) |
badges | Badges | Badges earned |
shipped | Shipped | Ship posts published (kind ship, deleted excluded) |
joined | Joined | The month the builder joined, as "YYYY-MM" |
Every value is an integer ≥ 0 except joined, which is a "YYYY-MM" string. Stats show even when the matching section is hidden: they're the builder's choice.
Ids are stable forever. New stats can join the catalog at any time without a version bump, so build pickers from the live catalog rather than a hardcoded list:
curl https://api.taste.dev/profile/stats/catalog{
"stats": [
{ "id": "projects", "label": "Projects", "description": "Projects on the profile." },
{ "id": "stars", "label": "Stars", "description": "Total GitHub stars across the profile's projects." },
{ "id": "followers", "label": "Followers", "description": "Builders following this profile." },
{ "id": "posts", "label": "Posts", "description": "Posts published (deleted posts excluded)." },
{ "id": "streak", "label": "Streak", "description": "Current Daily Taste streak in days (0 once broken)." },
{ "id": "badges", "label": "Badges", "description": "Badges earned." },
{ "id": "shipped", "label": "Shipped", "description": "Ship posts published (kind \"ship\")." },
{ "id": "joined", "label": "Joined", "description": "Month the builder joined, as YYYY-MM." }
]
}The catalog is public and cached for a day.
Rules
- 2 to 4 stats, no repeats, each a catalog id.
- Order is yours. The page shows them left to right in the order you send.
- Default. A profile that never picked shows
["stars", "projects", "followers"]. - Retired stats disappear quietly. If an id is ever retired, cards that chose it drop it when read, and are topped up from the defaults so a card always shows at least 2.
Pick your stats
curl -X PUT https://api.taste.dev/profile/card \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"stats": ["streak", "stars", "projects"]}'await fetch('https://api.taste.dev/profile/card', {
method: 'PUT',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ stats: ['streak', 'stars', 'projects'] }),
});The body's stats is exactly the card's stats:
{ "stats": ["streak", "stars", "projects"] }The response is the saved card:
{ "card": { "schema": 1, "stats": ["streak", "stars", "projects"] } }Anything else is 400, with a message that names the problem and lists the known ids:
{
"error": "invalid",
"message": "unknown stat id \"karma\"; known ids: projects, stars, followers, posts, streak, badges, shipped, joined"
}The other messages are stats must list 2 to 4 stat ids (got 5); known ids: … and duplicate stat id "stars".
Reading the card
Every page from GET /public/u/{username} carries both the choice and the values:
{
"card": { "schema": 1, "stats": ["streak", "stars", "joined"] },
"stats": [
{ "id": "streak", "label": "Streak", "value": 12 },
{ "id": "stars", "label": "Stars", "value": 1692 },
{ "id": "joined", "label": "Joined", "value": "2026-09" }
]
}stats is in card order and holds only the chosen stats. label is the English display label.
Showing stats
taste.dev renders the row as pills: the value in the theme's display font, a small glyph in the highlight color, and the label in muted caps. Counts are compact (999, 1.2k, 12k, 1.3M), joined reads as a month (Sep '26), and zeros are muted. You're free to render them your own way.