APIPublic profile API

Public profile API

GET /public/u/{username}: everything needed to render taste.dev/@name, in one unauthenticated request.

This is the document taste.dev renders. It is public, needs no key, and validates against profile-1.json (Schemas).

http
GET https://api.taste.dev/public/u/{username}
usernamestringpathrequired

The handle, without the @. Case-insensitive: Mika and mika are the same page, and the response carries the canonical lowercase username.

Example

cURLbash
curl https://api.taste.dev/public/u/mika
JavaScriptjs
const res = await fetch(`https://api.taste.dev/public/u/${encodeURIComponent(name)}`);
const page = res.ok ? await res.json() : null; // 404: no builder here
Pythonpython
import requests

res = requests.get(f"https://api.taste.dev/public/u/{name}")
page = res.json() if res.ok else None  # 404: no builder here
200 OKjson
{
  "username": "mika",
  "display_name": "Mika Tanaka",
  "headline": "Shipping tiny AI apps weekly",
  "photo_url": "https://api.taste.dev/avatars/glasses/mika.svg",
  "building": "An AI receptionist for indie pharmacies",
  "level": "shipping",
  "tools": ["claude_code", "cursor", "v0"],
  "topics": ["AI agents", "Health"],
  "needs": ["Marketing / growth", "Feedback"],
  "gives": ["Engineering", "AI / ML"],
  "here_for": ["Find people to build my idea"],
  "links": [
    { "title": "X / Twitter", "url": "https://x.com/mikaships" },
    { "title": "mika.build", "url": "https://mika.build" }
  ],
  "projects": [
    {
      "id": "7b1c9e52-2f4a-4c1e-9a55-0d6f3c1e8a10",
      "slug": "counter",
      "name": "Counter",
      "tagline": "The AI receptionist for indie pharmacies",
      "followers": 214,
      "url": "https://counter.mika.build",
      "description": null,
      "image_url": "https://storage.googleapis.com/taste-uploads/counter.png",
      "icon": "💊",
      "stars": 412
    },
    {
      "id": "c0a4f1d8-6e3b-4b8a-8f0e-5b2d7a9c4e21",
      "slug": "tidepool",
      "name": "Tidepool",
      "tagline": "Voice notes in, weekly reflections out",
      "followers": 96,
      "url": "https://github.com/mikatanaka/tidepool",
      "description": null,
      "image_url": null,
      "icon": "🌊",
      "stars": 1280
    }
  ],
  "badges": [
    { "key": "first_ship", "label": "First Ship", "category": "shipping", "tier": 1 }
  ],
  "github_handle": "mikatanaka",
  "readme_widgets": [
    {
      "kind": "skill_icons",
      "service": "skillicons.dev",
      "src": "https://skillicons.dev/icons?i=ts,react,swift",
      "href": null,
      "alt": "TypeScript, React, Swift",
      "live": true
    }
  ],
  "joined": "2026-09",
  "support_links": [
    { "platform": "buymeacoffee", "url": "https://buymeacoffee.com/mikaships" },
    { "platform": "github_sponsors", "url": "https://github.com/sponsors/mikatanaka" }
  ],
  "theme": {
    "schema": 1,
    "id": "terminal",
    "name": "Terminal",
    "author": "taste",
    "description": "Green phosphor on a dark tube. The original.",
    "mode": "dark",
    "colors": {
      "background": "#020803",
      "background2": "#06120B",
      "surface": "#0B1510",
      "line": "#1D2E24",
      "text": "#EAF3EE",
      "muted": "#8AA597",
      "accent": "#AEF8D0",
      "onAccent": "#04170E",
      "highlight": "#39FF6A"
    },
    "background": { "type": "gradient", "colors": ["#06120B", "#020803"], "angle": 180 },
    "effects": { "scanlines": true, "grain": 0, "glow": true },
    "type": { "display": "sans", "body": "sans", "label": "pixel", "headingStyle": "prompt" },
    "shape": { "radius": 18, "avatar": "circle", "border": 1 },
    "layout": {
      "order": ["building", "tools", "links", "projects", "pay_it_forward", "topics", "here_for", "badges"],
      "links": "buttons",
      "projects": "cards",
      "tools": "tiles"
    }
  },
  "card": { "schema": 1, "stats": ["streak", "stars", "projects", "joined"] },
  "stats": [
    { "id": "streak", "label": "Streak", "value": 12 },
    { "id": "stars", "label": "Stars", "value": 1692 },
    { "id": "projects", "label": "Projects", "value": 2 },
    { "id": "joined", "label": "Joined", "value": "2026-09" }
  ]
}

Response fields

usernamestringrequired

The canonical handle, lowercase. The page lives at taste.dev/@{username}.

display_namestringrequired

The builder's name.

headlinestring | nullrequired

One line about them.

photo_urlstring | nullrequired

Their photo (or pixel avatar). null when there is none: draw an initial, or a pixel avatar from /avatars/auto/{username}.svg.

buildingstring | nullrequired

What they're building.

levelstring | nullrequired

How often they ship, e.g. "shipping".

toolsstring[]required

Tool slugs, e.g. claude_code, cursor.

topicsstring[]required

What they're into.

givesstring[]required

Can give (pay it forward).

needsstring[]required

Looking for.

here_forstring[]required

Why they're on Taste.

linksobject[]required

Up to 12 {title, url} in the builder's order. URLs are http(s).

projectsobject[]required

{id, slug, name, tagline, followers, url, description, image_url, icon, stars}. image_url is a screenshot or an http(s) icon; icon is an emoji or image URL; stars is GitHub stars (≥ 0). description is one line about it (the repo description for projects imported from GitHub), or null.

Each project is a product: slug names its page at taste.dev/@{username}/{slug} (Products), tagline is its one-line pitch (or null) and followers counts who follows it in the app. id, slug, tagline and followers are newer: older responses omit them, and a project without a slug has no product page yet, so link to its url instead.

badgesobject[]required

{key, label, category, tier} in the order earned. tier starts at 1.

github_handlestring | nullrequired

Their GitHub username.

readme_widgetsobject[]

Images the builder brought over from their GitHub profile README with Import from GitHub — stats cards, streaks, banners, badge walls, typing SVGs — in order, at most 20. Each is {kind, service, src, href, alt, live}: kind names what it is (stats_card, top_languages, streak, trophies, activity_graph, snake, banner, typing, skill_icons, badge, counter_badge, wakatime, tokscale, trendshift, gif, image, … — new kinds may appear), src is always https, href is where it links (or null), live means the service draws it from live data. Optional: older responses omit it.

Render each as a plain image — never as HTML — lazily, with no referrer, linked to href, and hide any image that fails to load: these are third-party services and some go down.

support_linksobject[]

Where to back the builder, in their order: {platform, url}. platform is one of buymeacoffee, kofi, github_sponsors, polar, patreon, stripe or other (new platforms may appear: treat one you don't know as other); url is https. taste.dev shows them as a Support section of brand-colored buttons, and a product page's Support button falls back to the first one. Optional: older responses omit it.

joinedstringrequired

Join month, "YYYY-MM".

themeobjectrequired

The complete, resolved theme (Themes). If the stored theme no longer exists, this is terminal.

cardobjectrequired

{"schema": 1, "stats": [ids]}: which stats show, in order (Card stats).

statsobject[]required

The card's 2–4 stats as {id, label, value}, in card order. value is an integer, or "YYYY-MM" for joined.

Hidden sections

Builders can hide parts of their profile. A hidden section still appears in the response, blank (null or []), so the shape never changes:

Hidden in the appBlank in the response
Storybuilding, level, gives
Portfolioprojects
Stacktools
Intereststopics, here_for
Linkslinks, github_handle, readme_widgets

stats always show: they're the builder's pick.

Not found

Anything that isn't a live, public page returns the same thing:

404 Not Foundjson
{ "error": "not_found" }

That covers an unknown or malformed handle, an account that is deleted, paused or suspended, a profile held for moderation, and a member who hasn't finished a profile. Treat every 404 as "no builder here". taste.dev shows a "claim this name" page.

Caching

The response is sent with:

http
Cache-Control: public, max-age=60, stale-while-revalidate=600

Edits show up within about a minute. taste.dev re-renders pages on the same one-minute cycle. The catalog and schema endpoints are cached for a day (public, max-age=86400).

Privacy

The page is public by design, so it carries only what the builder put on their page. It never includes:

  • their account id, email address or phone number
  • their location, timezone or calendar
  • verification details
  • who they follow, connect or talk with, and their posts
  • anything in a section they hid

Stats are counts, not lists: followers is a number, never who.