Customize your Taste page
Your page at taste.dev/@you is data: a theme, a stats card, your links and your handle. Here is what you can change and the API behind it.
Every builder on Taste gets a page at taste.dev/@name. The app and the website draw it from the same JSON, so what you pick in the app is exactly what the web shows. This documentation covers what's customizable, the formats, and the public API any app can read.
What you can customize
| What | How | Where it shows |
|---|---|---|
| Theme | Pick one of the built-ins: terminal (default), studio, pearl | Colors, background, fonts, card shape and section layout of your whole page |
| Card stats | Choose 2–4 stats, in order | The stat row under your name |
| Links | Up to 12 ordered links | Your link-in-bio buttons |
| Handle | Claim a username | Your URL: taste.dev/@name |
| Avatar | Upload a photo, or wear a generated pixel avatar | The top of your page and your share card |
Every field of a theme, the fonts, layouts and contrast rules, and the three built-ins.
Card statsThe stat catalog and how to pick 2–4 of them.
Public profile APIGET /public/u/{username}: one request, everything needed to render a page.
JSON Schemas for every document, and how to validate against them.
The model
A page is three documents that travel together:
- Profile: the data. Name, headline, what you're building, tools, links, projects, badges.
- Theme: how it looks. Nine colors, a background, font roles, shape and section layout. Data only: a theme carries no CSS and no code. See Themes.
- Card: which stats show, in order. See Card stats.
GET /public/u/{username} returns all three in one response: the profile fields, a fully resolved theme, the card, and the chosen stats with their values. Each document has a JSON Schema with an integer version (profile-1, theme-1, card-1). See Schemas and Versioning.
Quick start
Read any public page. No key, no auth:
curl https://api.taste.dev/public/u/mikaconst res = await fetch('https://api.taste.dev/public/u/mika');
if (res.status === 404) throw new Error('No builder here');
const page = await res.json();
console.log(page.display_name, page.theme.id, page.stats);import requests
page = requests.get("https://api.taste.dev/public/u/mika").json()
print(page["display_name"], page["theme"]["id"], page["stats"])Then render it:
- Paint
theme.colors.background(or thetheme.backgroundgradient) and set fonts fromtheme.type. - Show
statsas the stat row, in order:labelandvalue. Skip ids you don't recognize. - Lay out the sections in
theme.layout.order, each in its variant (layout.links,layout.projects,layout.tools).
Endpoints
The API host is https://api.taste.dev.
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /public/u/{username} | none | A public page: profile, theme, card and stats |
| GET | /profile/stats/catalog | none | Every stat a card can show |
| GET | /schemas | none | Index of the JSON Schemas |
| GET | /schemas/{file} | none | One schema, e.g. /schemas/theme-1.json |
| GET | /avatars/styles | none | The pixel-avatar styles |
| GET | /avatars/{style}/{seed}.svg | none | A generated pixel avatar |
| GET | /themes | yes | The built-in themes, full JSON, in picker order |
| PUT | /profile/theme | yes | Pick your theme |
| PUT | /profile/card | yes | Pick your card's stats |
| PUT | /profile/links | yes | Replace your links |
| PUT | /profile/username | yes | Claim or change your handle |
| GET | /username/available?u={name} | yes | Is a handle free for you? |
Authenticated endpoints take Authorization: Bearer <access token>: the signed-in member's session from the Taste app. There is no third-party app registration yet, so today these are what the app calls on your behalf.
Links
PUT /profile/links replaces your whole list, in order, and returns what was saved.
PUT /profile/links
Content-Type: application/json
{"links": [{"title": "Site", "url": "mika.build"}, {"url": "https://github.com/mikatanaka"}]}{
"links": [
{ "title": "Site", "url": "https://mika.build" },
{ "title": "github.com", "url": "https://github.com/mikatanaka" }
]
}- At most 12 links.
- URLs must be
httporhttps. A bare domain getshttps://. Other schemes (mailto:,ftp://) are rejected. titleis optional: it defaults to the URL's host, is trimmed, and is at most 60 characters.- Anything invalid returns
400 {"error": "invalid"}.
Handle
Your handle is your URL: taste.dev/@name. Handles are 1–30 characters of lowercase letters, digits and _, with single . or - allowed between other characters. They start with a letter or digit and are case-insensitive (/@Mika redirects to /@mika).
GET /username/available?u=mika.builds{ "available": false, "reason": "taken" }reason is taken, invalid, reserved, premium (with a price_usd), or null when it's free. Your own current handle reads as available to you.
PUT /profile/username
Content-Type: application/json
{"username": "mika.builds"}→ 200 {"username": "mika.builds"}
| Status | Body | When |
|---|---|---|
| 400 | {"error": "invalid", "reason": "invalid"} | Malformed |
| 400 | {"error": "invalid", "reason": "reserved"} | Reserved (brands, taste.dev routes, tool names) |
| 402 | {"error": "premium", "reason": "premium", "price_usd": 50} | 1–5 character names are premium, not purchasable yet |
| 409 | {"error": "conflict", "reason": "taken"} | Someone has it |
| 429 | {"error": "rate_limited"} | You renamed in the last 30 days (one rename a month) |
Pixel avatars
Members without a photo wear a generated pixel avatar. They are deterministic: the same style and seed always draw the same SVG, and responses are cached for a year.
curl https://api.taste.dev/avatars/styles
curl https://api.taste.dev/avatars/glasses/mika.svgThe styles are hoodie, visor, headset, glasses, robot, cat, astro, wizard, rain, ghost, alien and pixel. auto picks one from the seed. An unknown style draws pixel. The seed is 1–64 characters of A-Z a-z 0-9 . _ -.