[{"data":1,"prerenderedAt":5938},["ShallowReactive",2],{"api-blog-list":3},[4,739,1267,1664,2034,2458,2810,3417,4185,4642,5292],{"id":5,"title":6,"author":7,"body":8,"category":708,"date":709,"dateModified":710,"description":711,"extension":712,"faqs":713,"image":710,"meta":725,"navigation":409,"path":726,"readingTime":251,"relatedSlugs":727,"seo":731,"stem":732,"tags":733,"__hash__":738},"apiBlog/api-blog/connect-miromiro-mcp-claude-cursor-chatgpt.md","How to Connect the MiroMiro MCP to Claude, Cursor, ChatGPT and Codex","Soraia",{"type":9,"value":10,"toc":691},"minimark",[11,20,29,34,37,101,108,114,122,126,137,267,270,274,277,324,328,334,351,355,358,424,432,436,439,532,539,543,546,560,564,599,603,606,640,647,651,656,659,663,666,670,673,677,687],[12,13,14,15,19],"p",{},"Every client that speaks MCP can connect to MiroMiro with one URL. Paste ",[16,17,18],"code",{},"https://miromiro.app/mcp"," into the client's connector settings and seven design-extraction tools appear in your chats: section-to-code, brand, design tokens, images, SVGs, fonts, and Lottie.",[12,21,22,23,28],{},"You do not need an account to try it. The bare URL connects in demo mode; a ",[24,25,27],"a",{"href":26},"/api/onboarding","free key"," (100 credits a month, no card) unlocks full results. Client-by-client steps below.",[30,31,33],"h2",{"id":32},"claude-web-and-desktop","Claude - web and desktop",[12,35,36],{},"Claude calls these custom connectors. Two ways in:",[38,39,40,60,87,90],"ol",{},[41,42,43,44,48,49,52,53,52,56,59],"li",{},"In any chat, click the ",[45,46,47],"strong",{},"+"," button in the input, then ",[45,50,51],{},"Connectors"," → ",[45,54,55],{},"Add connector",[45,57,58],{},"Add custom connector",". (Settings → Connectors gets you to the same dialog.)",[41,61,62,63],{},"In the dialog, fill in:\n",[64,65,66,75],"ul",{},[41,67,68,71,72],{},[45,69,70],{},"Name:"," ",[16,73,74],{},"MiroMiro",[41,76,77,71,80,83,84,86],{},[45,78,79],{},"URL:",[16,81,82],{},"https://miromiro.app/mcp?access_key=mm_live_your_key"," - or just ",[16,85,18],{}," to try it keyless first",[41,88,89],{},"Leave the OAuth Client ID and Secret fields under Advanced settings empty. The key travels in the URL.",[41,91,92,93,96,97,100],{},"Click ",[45,94,95],{},"Add",". The tools are live in every chat immediately - ask \"get the brand colors and fonts of stripe.com\" and watch it call ",[16,98,99],{},"extract_brand",".",[12,102,103],{},[104,105],"img",{"alt":106,"src":107},"Claude's + menu: Connectors, then Add connector, then Add custom connector","/api-blog/connector1.png",[12,109,110],{},[104,111],{"alt":112,"src":113},"Claude's Add custom connector dialog with the MiroMiro name and URL filled in","/api-blog/connector2.png",[12,115,116,117,121],{},"One click faster: the ",[24,118,120],{"href":119},"/api/mcp","Add to Claude button on our MCP page"," opens this dialog with the name and URL pre-filled.",[30,123,125],{"id":124},"cursor","Cursor",[12,127,128,129,132,133,136],{},"Cursor supports install deeplinks, so the ",[24,130,131],{"href":119},"Add to Cursor button"," does the whole thing in one click. Manual version, in ",[16,134,135],{},"~/.cursor/mcp.json",":",[138,139,144],"pre",{"className":140,"code":141,"language":142,"meta":143,"style":143},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"mcpServers\": {\n    \"miromiro\": {\n      \"url\": \"https://miromiro.app/mcp\",\n      \"headers\": { \"Authorization\": \"Bearer mm_live_your_key\" }\n    }\n  }\n}\n","json","",[16,145,146,155,173,189,214,249,255,261],{"__ignoreMap":143},[147,148,151],"span",{"class":149,"line":150},"line",1,[147,152,154],{"class":153},"sMK4o","{\n",[147,156,158,161,165,168,170],{"class":149,"line":157},2,[147,159,160],{"class":153},"  \"",[147,162,164],{"class":163},"spNyl","mcpServers",[147,166,167],{"class":153},"\"",[147,169,136],{"class":153},[147,171,172],{"class":153}," {\n",[147,174,176,179,183,185,187],{"class":149,"line":175},3,[147,177,178],{"class":153},"    \"",[147,180,182],{"class":181},"sBMFI","miromiro",[147,184,167],{"class":153},[147,186,136],{"class":153},[147,188,172],{"class":153},[147,190,192,195,199,201,203,206,209,211],{"class":149,"line":191},4,[147,193,194],{"class":153},"      \"",[147,196,198],{"class":197},"sbssI","url",[147,200,167],{"class":153},[147,202,136],{"class":153},[147,204,205],{"class":153}," \"",[147,207,18],{"class":208},"sfazB",[147,210,167],{"class":153},[147,212,213],{"class":153},",\n",[147,215,217,219,222,224,226,229,231,235,237,239,241,244,246],{"class":149,"line":216},5,[147,218,194],{"class":153},[147,220,221],{"class":197},"headers",[147,223,167],{"class":153},[147,225,136],{"class":153},[147,227,228],{"class":153}," {",[147,230,205],{"class":153},[147,232,234],{"class":233},"swJcz","Authorization",[147,236,167],{"class":153},[147,238,136],{"class":153},[147,240,205],{"class":153},[147,242,243],{"class":208},"Bearer mm_live_your_key",[147,245,167],{"class":153},[147,247,248],{"class":153}," }\n",[147,250,252],{"class":149,"line":251},6,[147,253,254],{"class":153},"    }\n",[147,256,258],{"class":149,"line":257},7,[147,259,260],{"class":153},"  }\n",[147,262,264],{"class":149,"line":263},8,[147,265,266],{"class":153},"}\n",[12,268,269],{},"Cursor can send headers, so put the key there instead of in the URL - headers stay out of logs. Restart Cursor and check the MCP panel for the seven tools.",[30,271,273],{"id":272},"chatgpt","ChatGPT",[12,275,276],{},"ChatGPT only takes remote servers, which is exactly what this is:",[38,278,279,294,321],{},[41,280,281,52,284,52,287,290,291],{},[45,282,283],{},"Settings",[45,285,286],{},"Apps & Connectors",[45,288,289],{},"Advanced"," → enable ",[45,292,293],{},"Developer mode",[41,295,296,299,300],{},[45,297,298],{},"Create"," a connector:\n",[64,301,302,308,315],{},[41,303,304,71,306],{},[45,305,70],{},[16,307,74],{},[41,309,310,71,313],{},[45,311,312],{},"MCP server URL:",[16,314,82],{},[41,316,317,320],{},[45,318,319],{},"Authentication:"," No authentication (the key is in the URL)",[41,322,323],{},"Enable the connector in a chat via the tools menu.",[30,325,327],{"id":326},"codex-cli","Codex CLI",[12,329,330,331,136],{},"Add to ",[16,332,333],{},"~/.codex/config.toml",[138,335,339],{"className":336,"code":337,"language":338,"meta":143,"style":143},"language-toml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","[mcp_servers.miromiro]\nurl = \"https://miromiro.app/mcp?access_key=mm_live_your_key\"\n","toml",[16,340,341,346],{"__ignoreMap":143},[147,342,343],{"class":149,"line":150},[147,344,345],{},"[mcp_servers.miromiro]\n",[147,347,348],{"class":149,"line":157},[147,349,350],{},"url = \"https://miromiro.app/mcp?access_key=mm_live_your_key\"\n",[30,352,354],{"id":353},"claude-code","Claude Code",[12,356,357],{},"One command in your terminal, not inside a session:",[138,359,363],{"className":360,"code":361,"language":362,"meta":143,"style":143},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","claude mcp add --transport http miromiro https://miromiro.app/mcp \\\n  --header \"Authorization: Bearer mm_live_your_key\"\n\nclaude mcp list   # look for \"✓ Connected\"\n","bash",[16,364,365,392,405,411],{"__ignoreMap":143},[147,366,367,370,373,376,379,382,385,388],{"class":149,"line":150},[147,368,369],{"class":181},"claude",[147,371,372],{"class":208}," mcp",[147,374,375],{"class":208}," add",[147,377,378],{"class":208}," --transport",[147,380,381],{"class":208}," http",[147,383,384],{"class":208}," miromiro",[147,386,387],{"class":208}," https://miromiro.app/mcp",[147,389,391],{"class":390},"sTEyZ"," \\\n",[147,393,394,397,399,402],{"class":149,"line":157},[147,395,396],{"class":208},"  --header",[147,398,205],{"class":153},[147,400,401],{"class":208},"Authorization: Bearer mm_live_your_key",[147,403,404],{"class":153},"\"\n",[147,406,407],{"class":149,"line":175},[147,408,410],{"emptyLinePlaceholder":409},true,"\n",[147,412,413,415,417,420],{"class":149,"line":191},[147,414,369],{"class":181},[147,416,372],{"class":208},[147,418,419],{"class":208}," list",[147,421,423],{"class":422},"sHwdD","   # look for \"✓ Connected\"\n",[12,425,426,427,431],{},"Claude Code also runs the ",[24,428,430],{"href":429},"/api/blog/add-design-extraction-to-claude-code-mcp","local npm package"," if you prefer the key in an env var instead of a header.",[30,433,435],{"id":434},"demo-mode-vs-a-key","Demo mode vs a key",[12,437,438],{},"The same tools respond either way. What changes:",[440,441,442,460],"table",{},[443,444,445],"thead",{},[446,447,448,451,454,457],"tr",{},[449,450],"th",{},[449,452,453],{},"No key (demo)",[449,455,456],{},"Free key",[449,458,459],{},"Paid plans",[461,462,463,477,491,505,518],"tbody",{},[446,464,465,469,472,475],{},[466,467,468],"td",{},"Results",[466,470,471],{},"Capped previews",[466,473,474],{},"Full",[466,476,474],{},[446,478,479,482,485,488],{},[466,480,481],{},"Rate limit",[466,483,484],{},"5/min, shared per IP",[466,486,487],{},"30/min, yours",[466,489,490],{},"60 to 300/min",[446,492,493,496,499,502],{},[466,494,495],{},"Credits",[466,497,498],{},"None used",[466,500,501],{},"100/month",[466,503,504],{},"5,000 to 200,000/month",[446,506,507,510,513,516],{},[466,508,509],{},"Usage and logs",[466,511,512],{},"No",[466,514,515],{},"Dashboard",[466,517,515],{},[446,519,520,523,526,529],{},[466,521,522],{},"Price",[466,524,525],{},"Free",[466,527,528],{},"Free, no card",[466,530,531],{},"From €19/month",[12,533,534,535,538],{},"Demo mode is for seeing real output before you sign up. Treat the free key as the actual starting point: it is also free, and ",[16,536,537],{},"extract_code"," alone is worth the 30 seconds of signup.",[30,540,542],{"id":541},"what-to-ask-once-connected","What to ask once connected",[12,544,545],{},"You never name tools in prompts - ask design-shaped questions and the agent picks the right one:",[64,547,548,551,554,557],{},[41,549,550],{},"\"Rebuild stripe.com's pricing section in React\"",[41,552,553],{},"\"Extract linear.app's design tokens and restyle our Button component to match\"",[41,555,556],{},"\"What fonts does vercel.com actually load?\"",[41,558,559],{},"\"Pull the brand palette from ourcustomer.com and generate a theme file\"",[30,561,563],{"id":562},"common-problems","Common problems",[64,565,566,577,583,589],{},[41,567,568,571,572,576],{},[45,569,570],{},"\"Invalid or revoked API key\" in the tool result."," The key in your URL or header is wrong or was rotated. Grab the current one from ",[24,573,575],{"href":574},"/api/dashboard/keys","API Keys"," - keys are shown once at creation, so rotate if you lost it.",[41,578,579,582],{},[45,580,581],{},"Rate limited in demo mode."," The keyless tier is 5 requests a minute per IP, shared with everyone behind your NAT. A free key gives you your own 30/minute.",[41,584,585,588],{},[45,586,587],{},"Thin results on an app-like site."," The engine reads static HTML and CSS, no JavaScript execution. A fully client-rendered app returns little, and the tools say so instead of inventing values.",[41,590,591,594,595,598],{},[45,592,593],{},"Claude web has no header field."," Correct - claude.ai custom connectors cannot send custom headers. That is what ",[16,596,597],{},"?access_key="," is for.",[30,600,602],{"id":601},"or-skip-mcp-entirely","Or skip MCP entirely",[12,604,605],{},"The same engine is a plain REST API:",[138,607,609],{"className":360,"code":608,"language":362,"meta":143,"style":143},"curl \"https://miromiro.app/api/v1/brand?url=stripe.com\" \\\n  -H \"Authorization: Bearer $MIROMIRO_API_KEY\"\n",[16,610,611,625],{"__ignoreMap":143},[147,612,613,616,618,621,623],{"class":149,"line":150},[147,614,615],{"class":181},"curl",[147,617,205],{"class":153},[147,619,620],{"class":208},"https://miromiro.app/api/v1/brand?url=stripe.com",[147,622,167],{"class":153},[147,624,391],{"class":390},[147,626,627,630,632,635,638],{"class":149,"line":157},[147,628,629],{"class":208},"  -H",[147,631,205],{"class":153},[147,633,634],{"class":208},"Authorization: Bearer ",[147,636,637],{"class":390},"$MIROMIRO_API_KEY",[147,639,404],{"class":153},[12,641,642,643,646],{},"Full reference at ",[24,644,645],{"href":645},"/api/docs",". You can start for free.",[30,648,650],{"id":649},"frequently-asked-questions","Frequently asked questions",[652,653,655],"h3",{"id":654},"how-do-i-add-a-custom-mcp-connector-to-claude-web","How do I add a custom MCP connector to Claude web?",[12,657,658],{},"The + button in the chat input → Connectors → Add connector → Add custom connector. Name it, paste the URL, click Add.",[652,660,662],{"id":661},"do-i-need-a-key-to-try-it","Do I need a key to try it?",[12,664,665],{},"No. The bare URL runs in demo mode. Full results need a key - free, 100 credits a month, no card.",[652,667,669],{"id":668},"which-clients-does-the-remote-server-work-with","Which clients does the remote server work with?",[12,671,672],{},"Claude web and desktop, Cursor, ChatGPT developer mode, Codex, Claude Code, Windsurf, and any MCP SDK. If it takes a server URL, it works.",[652,674,676],{"id":675},"where-does-my-key-go-safest","Where does my key go safest?",[12,678,679,680,683,684,686],{},"Header (",[16,681,682],{},"Authorization: Bearer",") where the client supports it - Cursor, Claude Code, SDKs. URL (",[16,685,597],{},") only where headers are not an option - claude.ai, ChatGPT. Rotate keys anytime in the dashboard.",[688,689,690],"style",{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}",{"title":143,"searchDepth":157,"depth":157,"links":692},[693,694,695,696,697,698,699,700,701,702],{"id":32,"depth":157,"text":33},{"id":124,"depth":157,"text":125},{"id":272,"depth":157,"text":273},{"id":326,"depth":157,"text":327},{"id":353,"depth":157,"text":354},{"id":434,"depth":157,"text":435},{"id":541,"depth":157,"text":542},{"id":562,"depth":157,"text":563},{"id":601,"depth":157,"text":602},{"id":649,"depth":157,"text":650,"children":703},[704,705,706,707],{"id":654,"depth":175,"text":655},{"id":661,"depth":175,"text":662},{"id":668,"depth":175,"text":669},{"id":675,"depth":175,"text":676},"AI Engineering","2026-07-20",null,"Step-by-step connector setup for every client: paste one URL into Claude web, one-click install in Cursor, developer mode in ChatGPT, one command in Claude Code. Works without a key in demo mode.","md",[714,716,719,722],{"question":655,"answer":715},"Click the + button in the chat input, choose Connectors, then Add connector and Add custom connector. Name it, paste the server URL (for MiroMiro: https://miromiro.app/mcp), and click Add. No OAuth fields needed for servers that use URL keys.",{"question":717,"answer":718},"Do I need an API key to try the MiroMiro MCP?","No. The bare URL https://miromiro.app/mcp connects in demo mode - capped results, shared rate limit, zero signup. For full results, append ?access_key=mm_live_your_key to the URL, or send the key as an Authorization: Bearer header in clients that support headers.",{"question":720,"answer":721},"How do I connect an MCP server to ChatGPT?","Settings, then Apps & Connectors, then Advanced, then enable Developer mode. Create a connector with the MCP server URL and pick No authentication. ChatGPT requires remote (URL-based) servers - it cannot run local stdio packages.",{"question":723,"answer":724},"What is the difference between the remote MCP server and the npm package?","Same seven tools. The remote server (https://miromiro.app/mcp) is a hosted URL - nothing to install, works in browser clients like claude.ai and ChatGPT. The npm package (npx miromiro-mcp) runs locally and reads the key from an env var, which keeps it out of any URL.",{},"/api-blog/connect-miromiro-mcp-claude-cursor-chatgpt",[728,729,730],"add-design-extraction-to-claude-code-mcp","how-to-extract-design-tokens-from-any-website-in-python","best-design-to-code-apis-2026",{"title":6,"description":711},"api-blog/connect-miromiro-mcp-claude-cursor-chatgpt",[734,369,124,272,735,736,737],"mcp","codex","connectors","ai agents","a2tzmmGGjH3MTqWedb8mJxtpJkSYfhXTuPPKLIqpiGE",{"id":740,"title":741,"author":7,"body":742,"category":708,"date":1242,"dateModified":710,"description":1243,"extension":712,"faqs":1244,"image":710,"meta":1256,"navigation":409,"path":1257,"readingTime":216,"relatedSlugs":1258,"seo":1261,"stem":1262,"tags":1263,"__hash__":1266},"apiBlog/api-blog/add-design-extraction-to-claude-code-mcp.md","Add Design Extraction to Claude Code with MCP",{"type":9,"value":743,"toc":1227},[744,747,808,811,821,825,828,835,839,849,853,864,903,913,917,953,956,965,969,975,1104,1110,1114,1144,1146,1149,1179,1185,1187,1191,1200,1204,1207,1211,1214,1218,1224],[12,745,746],{},"Two commands and Claude Code can read any website's design system instead of guessing it from a screenshot. Here is the whole setup:",[138,748,750],{"className":360,"code":749,"language":362,"meta":143,"style":143},"claude mcp add miromiro -s user \\\n  --env MIROMIRO_API_KEY=mm_live_your_key \\\n  -- npx -y miromiro-mcp\n\nclaude mcp list   # look for \"✓ Connected\"\n",[16,751,752,770,780,794,798],{"__ignoreMap":143},[147,753,754,756,758,760,762,765,768],{"class":149,"line":150},[147,755,369],{"class":181},[147,757,372],{"class":208},[147,759,375],{"class":208},[147,761,384],{"class":208},[147,763,764],{"class":208}," -s",[147,766,767],{"class":208}," user",[147,769,391],{"class":390},[147,771,772,775,778],{"class":149,"line":157},[147,773,774],{"class":208},"  --env",[147,776,777],{"class":208}," MIROMIRO_API_KEY=mm_live_your_key",[147,779,391],{"class":390},[147,781,782,785,788,791],{"class":149,"line":175},[147,783,784],{"class":208},"  --",[147,786,787],{"class":208}," npx",[147,789,790],{"class":208}," -y",[147,792,793],{"class":208}," miromiro-mcp\n",[147,795,796],{"class":149,"line":191},[147,797,410],{"emptyLinePlaceholder":409},[147,799,800,802,804,806],{"class":149,"line":216},[147,801,369],{"class":181},[147,803,372],{"class":208},[147,805,419],{"class":208},[147,807,423],{"class":422},[12,809,810],{},"That is genuinely it. The rest of this post is what those commands do, the Cursor/Windsurf versions, and how to actually use it in a session.",[12,812,813,814,816,817,100],{},"No terminal, or you use Claude in the browser? There is also a hosted server - paste ",[16,815,18],{}," into any client that takes a URL. Full walkthrough for Claude web, Cursor, ChatGPT and Codex in ",[24,818,820],{"href":819},"/api/blog/connect-miromiro-mcp-claude-cursor-chatgpt","how to connect the MiroMiro MCP",[30,822,824],{"id":823},"why-bother","Why bother",[12,826,827],{},"Ask a coding agent to \"make it look like stripe.com\" and it works from a screenshot at best, memory at worst. Either way it guesses: colors eyeballed from pixels, spacing approximated, fonts invented. The output is a plausible imitation with none of the real values.",[12,829,830,831,100],{},"With the MCP server connected, the agent stops guessing. It calls the extraction tools itself, gets the site's actual ranked palette, type scale, spacing, and shadows as data, and builds with those. Same prompt, real values. We wrote more about this gap in ",[24,832,834],{"href":833},"/api/use-cases/ai-agent-ground-truth","ground truth for AI agents",[30,836,838],{"id":837},"step-1-get-a-key","Step 1: get a key",[12,840,841,842,845,846,100],{},"Sign in and mint one at ",[24,843,844],{"href":574},"miromiro.app/api/dashboard/keys",". 100 free credits a month, no card. Keys look like ",[16,847,848],{},"mm_live_...",[30,850,852],{"id":851},"step-2-add-the-server","Step 2: add the server",[12,854,855,856,859,860,863],{},"The server is a local stdio process (",[16,857,858],{},"npx miromiro-mcp",") that reads your key from ",[16,861,862],{},"MIROMIRO_API_KEY",". Run this in your terminal - not inside a Claude session; you are configuring the server before the conversation starts:",[138,865,867],{"className":360,"code":866,"language":362,"meta":143,"style":143},"claude mcp add miromiro -s user \\\n  --env MIROMIRO_API_KEY=mm_live_your_key \\\n  -- npx -y miromiro-mcp\n",[16,868,869,885,893],{"__ignoreMap":143},[147,870,871,873,875,877,879,881,883],{"class":149,"line":150},[147,872,369],{"class":181},[147,874,372],{"class":208},[147,876,375],{"class":208},[147,878,384],{"class":208},[147,880,764],{"class":208},[147,882,767],{"class":208},[147,884,391],{"class":390},[147,886,887,889,891],{"class":149,"line":157},[147,888,774],{"class":208},[147,890,777],{"class":208},[147,892,391],{"class":390},[147,894,895,897,899,901],{"class":149,"line":175},[147,896,784],{"class":208},[147,898,787],{"class":208},[147,900,790],{"class":208},[147,902,793],{"class":208},[12,904,905,908,909,912],{},[16,906,907],{},"-s user"," registers it for every project; drop it to scope to the current one. The ",[16,910,911],{},"-y"," matters: it lets npx start the package unattended, so the editor can spawn the server without an install prompt.",[30,914,916],{"id":915},"step-3-verify-and-use-it","Step 3: verify and use it",[138,918,920],{"className":360,"code":919,"language":362,"meta":143,"style":143},"claude mcp list\n# miromiro ... ✓ Connected\n\nclaude\n> Use the miromiro server to get the brand colors and fonts of stripe.com\n",[16,921,922,931,936,940,945],{"__ignoreMap":143},[147,923,924,926,928],{"class":149,"line":150},[147,925,369],{"class":181},[147,927,372],{"class":208},[147,929,930],{"class":208}," list\n",[147,932,933],{"class":149,"line":157},[147,934,935],{"class":422},"# miromiro ... ✓ Connected\n",[147,937,938],{"class":149,"line":175},[147,939,410],{"emptyLinePlaceholder":409},[147,941,942],{"class":149,"line":191},[147,943,944],{"class":181},"claude\n",[147,946,947,950],{"class":149,"line":216},[147,948,949],{"class":153},">",[147,951,952],{"class":390}," Use the miromiro server to get the brand colors and fonts of stripe.com\n",[12,954,955],{},"All seven extraction tools appear in Claude's toolbox automatically: design tokens, brand, images, SVGs, fonts, Lottie, and section-to-code. You do not name tools in prompts - ask design-shaped questions and the agent picks the right one. Prompts that work well:",[64,957,958,960,963],{},[41,959,553],{},[41,961,962],{},"\"Get vercel.com's hero section as Tailwind and adapt it for our landing page\"",[41,964,559],{},[30,966,968],{"id":967},"cursor-and-windsurf","Cursor and Windsurf",[12,970,971,972,974],{},"Both take the standard ",[16,973,164],{}," JSON block:",[138,976,978],{"className":140,"code":977,"language":142,"meta":143,"style":143},"{\n  \"mcpServers\": {\n    \"miromiro\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"miromiro-mcp\"],\n      \"env\": { \"MIROMIRO_API_KEY\": \"mm_live_your_key\" }\n    }\n  }\n}\n",[16,979,980,984,996,1008,1028,1061,1091,1095,1099],{"__ignoreMap":143},[147,981,982],{"class":149,"line":150},[147,983,154],{"class":153},[147,985,986,988,990,992,994],{"class":149,"line":157},[147,987,160],{"class":153},[147,989,164],{"class":163},[147,991,167],{"class":153},[147,993,136],{"class":153},[147,995,172],{"class":153},[147,997,998,1000,1002,1004,1006],{"class":149,"line":175},[147,999,178],{"class":153},[147,1001,182],{"class":181},[147,1003,167],{"class":153},[147,1005,136],{"class":153},[147,1007,172],{"class":153},[147,1009,1010,1012,1015,1017,1019,1021,1024,1026],{"class":149,"line":191},[147,1011,194],{"class":153},[147,1013,1014],{"class":197},"command",[147,1016,167],{"class":153},[147,1018,136],{"class":153},[147,1020,205],{"class":153},[147,1022,1023],{"class":208},"npx",[147,1025,167],{"class":153},[147,1027,213],{"class":153},[147,1029,1030,1032,1035,1037,1039,1042,1044,1046,1048,1051,1053,1056,1058],{"class":149,"line":216},[147,1031,194],{"class":153},[147,1033,1034],{"class":197},"args",[147,1036,167],{"class":153},[147,1038,136],{"class":153},[147,1040,1041],{"class":153}," [",[147,1043,167],{"class":153},[147,1045,911],{"class":208},[147,1047,167],{"class":153},[147,1049,1050],{"class":153},",",[147,1052,205],{"class":153},[147,1054,1055],{"class":208},"miromiro-mcp",[147,1057,167],{"class":153},[147,1059,1060],{"class":153},"],\n",[147,1062,1063,1065,1068,1070,1072,1074,1076,1078,1080,1082,1084,1087,1089],{"class":149,"line":251},[147,1064,194],{"class":153},[147,1066,1067],{"class":197},"env",[147,1069,167],{"class":153},[147,1071,136],{"class":153},[147,1073,228],{"class":153},[147,1075,205],{"class":153},[147,1077,862],{"class":233},[147,1079,167],{"class":153},[147,1081,136],{"class":153},[147,1083,205],{"class":153},[147,1085,1086],{"class":208},"mm_live_your_key",[147,1088,167],{"class":153},[147,1090,248],{"class":153},[147,1092,1093],{"class":149,"line":257},[147,1094,254],{"class":153},[147,1096,1097],{"class":149,"line":263},[147,1098,260],{"class":153},[147,1100,1102],{"class":149,"line":1101},9,[147,1103,266],{"class":153},[12,1105,1106,1107,1109],{},"Cursor: ",[16,1108,135],{},". Windsurf: Settings → MCP. Restart the editor after saving; both show the server's tools in their MCP panel when connected.",[30,1111,1113],{"id":1112},"things-worth-knowing","Things worth knowing",[64,1115,1116,1122,1128],{},[41,1117,1118,1121],{},[45,1119,1120],{},"It reads static HTML + CSS."," No JavaScript execution, so a fully client-rendered app can return little. The tools say so rather than inventing a result, which is the behavior you want in an agent pipeline.",[41,1123,1124,1127],{},[45,1125,1126],{},"Credits:"," a token extraction is 10 credits, brand 15, section-to-code 25. The free 100/month covers real experimentation; the agent sees remaining quota in each response's usage object.",[41,1129,1130,1133,1134,1137,1138,1141,1142,100],{},[45,1131,1132],{},"Server not connecting?"," It is almost always the key: check ",[16,1135,1136],{},"claude mcp list",", and confirm the env var made it in (",[16,1139,1140],{},"claude mcp get miromiro","). Node 18+ is required for ",[16,1143,1023],{},[30,1145,602],{"id":601},[12,1147,1148],{},"The same engine is a plain REST API if your agent framework speaks HTTP instead:",[138,1150,1152],{"className":360,"code":1151,"language":362,"meta":143,"style":143},"curl \"https://miromiro.app/api/v1/extract?url=stripe.com\" \\\n  -H \"Authorization: Bearer $MIROMIRO_API_KEY\"\n",[16,1153,1154,1167],{"__ignoreMap":143},[147,1155,1156,1158,1160,1163,1165],{"class":149,"line":150},[147,1157,615],{"class":181},[147,1159,205],{"class":153},[147,1161,1162],{"class":208},"https://miromiro.app/api/v1/extract?url=stripe.com",[147,1164,167],{"class":153},[147,1166,391],{"class":390},[147,1168,1169,1171,1173,1175,1177],{"class":149,"line":157},[147,1170,629],{"class":208},[147,1172,205],{"class":153},[147,1174,634],{"class":208},[147,1176,637],{"class":390},[147,1178,404],{"class":153},[12,1180,1181,1182,100],{},"Full setup docs, including the agent quickstart, live at ",[24,1183,1184],{"href":1184},"/api/docs/install-mcp",[30,1186,650],{"id":649},[652,1188,1190],{"id":1189},"how-do-i-add-an-mcp-server-to-claude-code","How do I add an MCP server to Claude Code?",[12,1192,1193,1196,1197,1199],{},[16,1194,1195],{},"claude mcp add \u003Cname> ... -- \u003Ccommand>"," in your terminal, then ",[16,1198,1136],{}," to confirm it connected. Exact MiroMiro command at the top of this post.",[652,1201,1203],{"id":1202},"what-tools-does-the-server-expose","What tools does the server expose?",[12,1205,1206],{},"Seven: design tokens, brand, images, SVGs, fonts, Lottie, and section-to-code. They register automatically; the agent picks per task.",[652,1208,1210],{"id":1209},"why-tokens-instead-of-a-screenshot","Why tokens instead of a screenshot?",[12,1212,1213],{},"Screenshots make the model guess every value. Tokens are the site's real CSS - the difference between imitation and ground truth.",[652,1215,1217],{"id":1216},"does-it-work-with-cursor-and-windsurf","Does it work with Cursor and Windsurf?",[12,1219,1220,1221,1223],{},"Yes - standard ",[16,1222,164],{}," JSON (above), restart, done.",[688,1225,1226],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}",{"title":143,"searchDepth":157,"depth":157,"links":1228},[1229,1230,1231,1232,1233,1234,1235,1236],{"id":823,"depth":157,"text":824},{"id":837,"depth":157,"text":838},{"id":851,"depth":157,"text":852},{"id":915,"depth":157,"text":916},{"id":967,"depth":157,"text":968},{"id":1112,"depth":157,"text":1113},{"id":601,"depth":157,"text":602},{"id":649,"depth":157,"text":650,"children":1237},[1238,1239,1240,1241],{"id":1189,"depth":175,"text":1190},{"id":1202,"depth":175,"text":1203},{"id":1209,"depth":175,"text":1210},{"id":1216,"depth":175,"text":1217},"2026-07-18","Install the MiroMiro MCP server in two commands and Claude Code can pull any website's tokens, brand, fonts, and section code instead of guessing.",[1245,1247,1250,1253],{"question":1190,"answer":1246},"Run claude mcp add \u003Cname> with the command that starts the server, in your terminal before starting a session. For MiroMiro: claude mcp add miromiro -s user --env MIROMIRO_API_KEY=your_key -- npx -y miromiro-mcp. Verify with claude mcp list - you want to see Connected.",{"question":1248,"answer":1249},"What does the MiroMiro MCP server let Claude do?","Seven extraction tools appear in Claude's toolbox automatically: design tokens, brand data (logo, palette, fonts), images, SVGs, fonts, Lottie animations, and section-to-code. Claude calls them by itself when you ask design questions about a URL - no manual API calls in your prompts.",{"question":1251,"answer":1252},"Why give a coding agent design tokens instead of a screenshot?","Screenshots make the model guess values - it eyeballs colors, approximates spacing, and invents font names. Tokens are the site's real CSS values, so 'build it like stripe.com' produces the actual palette and type scale instead of a plausible imitation.",{"question":1254,"answer":1255},"Does the MiroMiro MCP server work with Cursor and Windsurf?","Yes. Both take the standard mcpServers JSON block: command npx, args ['-y', 'miromiro-mcp'], and your key in the env. In Cursor it goes in ~/.cursor/mcp.json; in Windsurf under Settings, MCP.",{},"/api-blog/add-design-extraction-to-claude-code-mcp",[1259,729,1260],"connect-miromiro-mcp-claude-cursor-chatgpt","design-token-types-reference",{"title":741,"description":1243},"api-blog/add-design-extraction-to-claude-code-mcp",[734,1264,737,1265,124],"claude code","design tokens","5ZSlTLfRpZyWbN6QBl48G2MrATv3P0HPlFNY504YFvY",{"id":1268,"title":1269,"author":7,"body":1270,"category":1637,"date":1242,"dateModified":710,"description":1638,"extension":712,"faqs":1639,"image":710,"meta":1650,"navigation":409,"path":1651,"readingTime":257,"relatedSlugs":1652,"seo":1656,"stem":1657,"tags":1658,"__hash__":1663},"apiBlog/api-blog/best-brand-data-apis-2026.md","Best Brand Data APIs in 2026 (Logos, Colors, Fonts)",{"type":9,"value":1271,"toc":1623},[1272,1279,1282,1286,1295,1302,1322,1326,1334,1351,1355,1362,1379,1383,1401,1431,1434,1456,1460,1569,1573,1585,1587,1591,1594,1598,1601,1605,1608,1612,1620],[12,1273,1274,1275,1278],{},"If you need to turn ",[16,1276,1277],{},"stripe.com"," into a logo, brand colors, and fonts inside your product, four options are worth your time in 2026. The short version: logos alone are a solved, free problem; the money question is how much design depth you need behind the logo.",[12,1280,1281],{},"The reason this category exists is not design tooling, it is conversion. Brandfetch's customer pages cite Typeform at +5% free-to-paid and Senja at +233% activation after wiring brand personalization into onboarding. \"Enter your URL, watch the product become yours\" is one of the few onboarding tricks with published numbers behind it.",[30,1283,1285],{"id":1284},"_1-brandfetch-the-category-owner","1. Brandfetch - the category owner",[12,1287,1288,1294],{},[24,1289,1293],{"href":1290,"rel":1291},"https://brandfetch.com",[1292],"nofollow","Brandfetch"," is the default answer, with Meta, Typeform, and Lucid on its logo wall. One call returns logos in several formats, brand colors, fonts, and company info.",[12,1296,1297,1298,1301],{},"Their pricing (verified July 2026): free tier of 100 Brand API requests, then the Brand API at ",[45,1299,1300],{},"$99/mo for 2,500 calls"," with $0.10 overage. The Logo API alone is free up to 500k requests a month, which is quietly the best deal in the category.",[64,1303,1304,1310,1316],{},[41,1305,1306,1309],{},[45,1307,1308],{},"Good:"," the deepest logo coverage in the market, hand-curated for big brands, reliable.",[41,1311,1312,1315],{},[45,1313,1314],{},"Bad:"," $99/mo is real money for 2,500 calls, and the design data stops at \"a few colors and font names\". You cannot theme a UI from it - there is no spacing, no type scale, no shadows.",[41,1317,1318,1321],{},[45,1319,1320],{},"Use it when:"," logo quality and coverage matter more than design depth, and the budget is there.",[30,1323,1325],{"id":1324},"_2-logodev-the-pragmatic-logo-endpoint","2. Logo.dev - the pragmatic logo endpoint",[12,1327,1328,1333],{},[24,1329,1332],{"href":1330,"rel":1331},"https://logo.dev",[1292],"Logo.dev"," does one thing: domain in, logo image out, with a generous free tier and simple img-tag integration.",[64,1335,1336,1341,1346],{},[41,1337,1338,1340],{},[45,1339,1308],{}," dead simple, fast to integrate, free for most usage.",[41,1342,1343,1345],{},[45,1344,1314],{}," logos only. No colors, no fonts, no JSON brand object.",[41,1347,1348,1350],{},[45,1349,1320],{}," you literally just need company logos in a table or CRM.",[30,1352,1354],{"id":1353},"_3-clearbit-logo-api-the-old-free-standby","3. Clearbit Logo API - the old free standby",[12,1356,1357,1358,1361],{},"Clearbit's ",[16,1359,1360],{},"logo.clearbit.com/{domain}"," endpoint has been free and everywhere for a decade. Clearbit is now part of HubSpot, and the endpoint has outlived several announcements about its future.",[64,1363,1364,1369,1374],{},[41,1365,1366,1368],{},[45,1367,1308],{}," free, zero setup, still works.",[41,1370,1371,1373],{},[45,1372,1314],{}," logos only, no SLA, and it lives at an acquirer's pleasure. I would not build a paid product on it in 2026.",[41,1375,1376,1378],{},[45,1377,1320],{}," internal tools and prototypes.",[30,1380,1382],{"id":1381},"_4-miromiro-brand-extract-api-identity-plus-the-design-system","4. MiroMiro brand + extract API - identity plus the design system",[12,1384,1385,1386,1390,1391,1395,1396,1400],{},"Our ",[24,1387,1389],{"href":1388},"/api/docs/brand","/v1/brand"," endpoint returns the logo, palette, and fonts like a brand API. The difference is what sits next to it: ",[24,1392,1394],{"href":1393},"/api/docs/design-tokens","/v1/extract"," returns the site's working design system - ranked colors, type scale, spacing, radii, shadows, gradients, motion - and ",[24,1397,1399],{"href":1398},"/api/docs/code","/v1/code"," turns a section into Tailwind/JSX/Vue. Same key, same call shape:",[138,1402,1404],{"className":360,"code":1403,"language":362,"meta":143,"style":143},"curl \"https://miromiro.app/api/v1/brand?url=stripe.com&fields=logo,palette\" \\\n  -H \"Authorization: Bearer $MIROMIRO_API_KEY\"\n",[16,1405,1406,1419],{"__ignoreMap":143},[147,1407,1408,1410,1412,1415,1417],{"class":149,"line":150},[147,1409,615],{"class":181},[147,1411,205],{"class":153},[147,1413,1414],{"class":208},"https://miromiro.app/api/v1/brand?url=stripe.com&fields=logo,palette",[147,1416,167],{"class":153},[147,1418,391],{"class":390},[147,1420,1421,1423,1425,1427,1429],{"class":149,"line":157},[147,1422,629],{"class":208},[147,1424,205],{"class":153},[147,1426,634],{"class":208},[147,1428,637],{"class":390},[147,1430,404],{"class":153},[12,1432,1433],{},"Pricing: 100 free credits a month with no card, then from €19/mo for 5,000 credits. A brand call costs 15 credits, a token extraction 10.",[64,1435,1436,1446,1451],{},[41,1437,1438,1440,1441,1445],{},[45,1439,1308],{}," the only option here that returns enough to actually ",[1442,1443,1444],"em",{},"theme"," a product, not just badge it. Built for the \"enter your URL\" onboarding flow.",[41,1447,1448,1450],{},[45,1449,1314],{}," our logo curation is not Brandfetch's - we extract from the live site rather than maintain a hand-checked logo library, so obscure-brand edge cases favor them. Static HTML + CSS parsing; no JS execution.",[41,1452,1453,1455],{},[45,1454,1320],{}," the goal is brand-personalized UI (onboarding, generated apps, email builders), not a logo lookup.",[30,1457,1459],{"id":1458},"side-by-side","Side by side",[440,1461,1462,1487],{},[443,1463,1464],{},[446,1465,1466,1469,1472,1475,1478,1481,1484],{},[449,1467,1468],{},"API",[449,1470,1471],{},"Logo",[449,1473,1474],{},"Colors",[449,1476,1477],{},"Fonts",[449,1479,1480],{},"Full design system",[449,1482,1483],{},"Free tier",[449,1485,1486],{},"Paid entry",[461,1488,1489,1510,1529,1548],{},[446,1490,1491,1493,1496,1499,1502,1504,1507],{},[466,1492,1293],{},[466,1494,1495],{},"Excellent",[466,1497,1498],{},"Basic",[466,1500,1501],{},"Names",[466,1503,512],{},[466,1505,1506],{},"100 req",[466,1508,1509],{},"$99/mo (2,500 calls)",[446,1511,1512,1514,1517,1519,1521,1523,1526],{},[466,1513,1332],{},[466,1515,1516],{},"Good",[466,1518,512],{},[466,1520,512],{},[466,1522,512],{},[466,1524,1525],{},"Generous",[466,1527,1528],{},"Cheap",[446,1530,1531,1534,1536,1538,1540,1542,1545],{},[466,1532,1533],{},"Clearbit Logo",[466,1535,1516],{},[466,1537,512],{},[466,1539,512],{},[466,1541,512],{},[466,1543,1544],{},"Unlimited-ish",[466,1546,1547],{},"n/a",[446,1549,1550,1552,1554,1557,1560,1563,1566],{},[466,1551,74],{},[466,1553,1516],{},[466,1555,1556],{},"Ranked, full palette",[466,1558,1559],{},"Families + weights + sizes",[466,1561,1562],{},"Yes (tokens + section code)",[466,1564,1565],{},"100 credits/mo",[466,1567,1568],{},"€19/mo (5,000 credits)",[30,1570,1572],{"id":1571},"the-verdict","The verdict",[12,1574,1575,1576,1579,1580,1584],{},"Logos only: Logo.dev, or Clearbit if it is internal. Maximum logo coverage with budget: Brandfetch. Theming a product to the user's brand: that is the job we built for - a logo plus three colors will not carry an onboarding flow that promises \"your brand, applied\". You can ",[24,1577,1578],{"href":574},"get a free key"," and test your own domain in the ",[24,1581,1583],{"href":1582},"/api/demo","playground"," in under a minute.",[30,1586,650],{"id":649},[652,1588,1590],{"id":1589},"what-is-a-brand-data-api","What is a brand data API?",[12,1592,1593],{},"Domain in, structured brand assets out: logo, colors, fonts. Products use them for onboarding personalization, CRM enrichment, and auto-theming.",[652,1595,1597],{"id":1596},"is-there-a-free-logo-api","Is there a free logo API?",[12,1599,1600],{},"Yes, two: Brandfetch's Logo API tier (free to 500k req/mo) and Clearbit's endpoint. For logos alone, pay nothing.",[652,1602,1604],{"id":1603},"why-fetch-brand-data-during-onboarding","Why fetch brand data during onboarding?",[12,1606,1607],{},"Published numbers: +5% free-to-paid at Typeform, +233% activation at Senja (per Brandfetch's customer pages). Users activate faster inside a product that already looks like theirs.",[652,1609,1611],{"id":1610},"brand-api-vs-design-token-api","Brand API vs design token API?",[12,1613,1614,1615,1619],{},"Identity vs system. A logo and five hex codes badge a UI; a token set (palette, type scale, spacing, shadows, motion) is what you need to build one. The ",[24,1616,1618],{"href":1617},"/api/blog/design-token-types-reference","design token types reference"," breaks down the difference.",[688,1621,1622],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":143,"searchDepth":157,"depth":157,"links":1624},[1625,1626,1627,1628,1629,1630,1631],{"id":1284,"depth":157,"text":1285},{"id":1324,"depth":157,"text":1325},{"id":1353,"depth":157,"text":1354},{"id":1381,"depth":157,"text":1382},{"id":1458,"depth":157,"text":1459},{"id":1571,"depth":157,"text":1572},{"id":649,"depth":157,"text":650,"children":1632},[1633,1634,1635,1636],{"id":1589,"depth":175,"text":1590},{"id":1596,"depth":175,"text":1597},{"id":1603,"depth":175,"text":1604},{"id":1610,"depth":175,"text":1611},"Comparisons","Brandfetch, Logo.dev, Clearbit, and MiroMiro compared honestly on depth and pricing - when a logo API is enough and when you need the full design system.",[1640,1642,1644,1647],{"question":1590,"answer":1641},"An API that takes a domain name and returns the company's brand assets - typically the logo, brand colors, and fonts - as structured data. Products use them to personalize onboarding, enrich CRM records, and theme UIs to a customer's brand automatically.",{"question":1597,"answer":1643},"Yes. Brandfetch's Logo API tier is free up to 500,000 requests a month, and Clearbit's long-standing logo endpoint (now part of HubSpot) has been free for years. For logos alone, you should not be paying.",{"question":1645,"answer":1646},"Why do products fetch brand data during onboarding?","Because personalization measurably converts. Brandfetch's own customer pages cite Typeform seeing +5% free-to-paid and Senja +233% activation after adding brand personalization to onboarding. A user who sees their own brand in your product is closer to activated.",{"question":1648,"answer":1649},"What is the difference between a brand API and a design token API?","A brand API returns identity: logo, a handful of colors, maybe fonts. A design token API returns the working design system: the full palette with usage ranking, type scale, spacing, radii, shadows, and motion. If you are theming a UI, identity alone is not enough to build with.",{},"/api-blog/best-brand-data-apis-2026",[1653,1654,1655],"best-design-token-extraction-tools-2026","best-firecrawl-alternatives-2026","how-to-extract-brand-assets-in-bulk",{"title":1269,"description":1638},"api-blog/best-brand-data-apis-2026",[1659,1660,1661,1662],"brand api","brandfetch","logo api","comparison","6oZS0krURBt4VY9_ej0hmKJd6CMEe4ynuxFS-SMRqIw",{"id":1665,"title":1666,"author":7,"body":1667,"category":1637,"date":1242,"dateModified":710,"description":2012,"extension":712,"faqs":2013,"image":710,"meta":2024,"navigation":409,"path":2025,"readingTime":257,"relatedSlugs":2026,"seo":2027,"stem":2028,"tags":2029,"__hash__":2033},"apiBlog/api-blog/best-design-to-code-apis-2026.md","Best Design-to-Code APIs and Tools in 2026",{"type":9,"value":1668,"toc":1998},[1669,1672,1675,1679,1705,1723,1727,1737,1754,1758,1768,1785,1789,1795,1825,1832,1849,1853,1956,1958,1966,1968,1972,1975,1979,1982,1986,1989,1993,1996],[12,1670,1671],{},"\"Design to code\" means four different products depending on what goes in: a Figma file, a screenshot, a prompt, or a live URL. Most listicles mix them together and compare a Figma plugin against an AI app builder, which helps nobody. Here is the landscape sorted by input, with an honest read on each.",[12,1673,1674],{},"Disclosure up front: we build the URL-to-code entry. The others are genuinely good at their own input type, and I will say so.",[30,1676,1678],{"id":1677},"input-a-figma-file","Input: a Figma file",[12,1680,1681,1688,1689,1696,1697,1704],{},[45,1682,1683],{},[24,1684,1687],{"href":1685,"rel":1686},"https://www.animaapp.com",[1292],"Anima"," and ",[45,1690,1691],{},[24,1692,1695],{"href":1693,"rel":1694},"https://www.locofy.ai",[1292],"Locofy"," are the established Figma-to-code plugins - React/HTML output, component mapping, years of production use. ",[45,1698,1699],{},[24,1700,1703],{"href":1701,"rel":1702},"https://www.builder.io",[1292],"Builder.io's Visual Copilot"," is the newer AI take on the same job.",[64,1706,1707,1712,1717],{},[41,1708,1709,1711],{},[45,1710,1308],{}," if your designs live in Figma with proper auto-layout, these produce real component code and preserve design intent.",[41,1713,1714,1716],{},[45,1715,1314],{}," they need a Figma file. Messy layers produce messy code, and there is no path from \"site I saw\" to code - someone has to design it first.",[41,1718,1719,1722],{},[45,1720,1721],{},"Use when:"," your team designs in Figma and wants the handoff automated.",[30,1724,1726],{"id":1725},"input-a-screenshot","Input: a screenshot",[12,1728,1729,1736],{},[45,1730,1731],{},[24,1732,1735],{"href":1733,"rel":1734},"https://github.com/abi/screenshot-to-code",[1292],"screenshot-to-code"," (open source, very popular) and the screenshot modes of various AI tools take a picture and ask a vision model to write matching HTML/Tailwind.",[64,1738,1739,1744,1749],{},[41,1740,1741,1743],{},[45,1742,1308],{}," works on anything you can see. Zero setup for the OSS version. Genuinely impressive first drafts.",[41,1745,1746,1748],{},[45,1747,1314],{}," every value is a guess. The model approximates colors from pixels, eyeballs spacing, and invents font stacks. Two runs give two different answers. Fine for a prototype, dangerous as a source of truth.",[41,1750,1751,1753],{},[45,1752,1721],{}," speed matters more than fidelity.",[30,1755,1757],{"id":1756},"input-a-prompt","Input: a prompt",[12,1759,1760,1767],{},[45,1761,1762],{},[24,1763,1766],{"href":1764,"rel":1765},"https://v0.dev",[1292],"v0"," (Vercel) and its peers generate UI from a text description. Strictly speaking that is prompt-to-code, not design-to-code, but it is where much of the category's attention went.",[64,1769,1770,1775,1780],{},[41,1771,1772,1774],{},[45,1773,1308],{}," the fastest way to get a decent generic component that never existed before.",[41,1776,1777,1779],{},[45,1778,1314],{}," the output looks like v0 output. If the goal is \"match this existing brand\", a prompt cannot carry a design system's worth of values.",[41,1781,1782,1784],{},[45,1783,1721],{}," greenfield UI with no reference to match.",[30,1786,1788],{"id":1787},"input-a-live-url","Input: a live URL",[12,1790,1791,1792,1794],{},"This is the gap. When the reference already exists as a website - a client's current site, a competitor's pricing page, a section your PM screenshotted - none of the above reads the real CSS. Our ",[24,1793,1399],{"href":1398}," endpoint does:",[138,1796,1798],{"className":360,"code":1797,"language":362,"meta":143,"style":143},"curl \"https://miromiro.app/api/v1/code?url=stripe.com&format=tailwind\" \\\n  -H \"Authorization: Bearer $MIROMIRO_API_KEY\"\n",[16,1799,1800,1813],{"__ignoreMap":143},[147,1801,1802,1804,1806,1809,1811],{"class":149,"line":150},[147,1803,615],{"class":181},[147,1805,205],{"class":153},[147,1807,1808],{"class":208},"https://miromiro.app/api/v1/code?url=stripe.com&format=tailwind",[147,1810,167],{"class":153},[147,1812,391],{"class":390},[147,1814,1815,1817,1819,1821,1823],{"class":149,"line":157},[147,1816,629],{"class":208},[147,1818,205],{"class":153},[147,1820,634],{"class":208},[147,1822,637],{"class":390},[147,1824,404],{"class":153},[12,1826,1827,1828,1831],{},"It parses the live page's actual cascade - specificity, media queries, nested ",[16,1829,1830],{},"var()"," chains - and returns the section as Tailwind, JSX, Vue, or plain HTML, with the extracted token set alongside. Auto-detects the hero if you do not pass a selector; 25 credits a call.",[64,1833,1834,1839,1844],{},[41,1835,1836,1838],{},[45,1837,1308],{}," the values are real, not guessed - the exact colors, spacing, and fonts the site ships. It is an API, so it runs inside agents and products, which almost nothing else in this category does.",[41,1840,1841,1843],{},[45,1842,1314],{}," section-level, not whole-app cloning - it will not produce a working multi-page app from a URL. Static HTML + CSS; fully client-rendered apps can come back thin.",[41,1845,1846,1848],{},[45,1847,1721],{}," an agent or product needs faithful code from a page that already exists.",[30,1850,1852],{"id":1851},"the-whole-landscape-in-one-table","The whole landscape in one table",[440,1854,1855,1874],{},[443,1856,1857],{},[446,1858,1859,1862,1865,1868,1871],{},[449,1860,1861],{},"Tool",[449,1863,1864],{},"Input",[449,1866,1867],{},"Output",[449,1869,1870],{},"API available",[449,1872,1873],{},"Values real or guessed?",[461,1875,1876,1893,1908,1924,1940],{},[446,1877,1878,1881,1884,1887,1890],{},[466,1879,1880],{},"Anima / Locofy",[466,1882,1883],{},"Figma file",[466,1885,1886],{},"React/HTML components",[466,1888,1889],{},"Limited",[466,1891,1892],{},"Real (from Figma)",[446,1894,1895,1898,1900,1903,1906],{},[466,1896,1897],{},"Builder.io Visual Copilot",[466,1899,1883],{},[466,1901,1902],{},"Components",[466,1904,1905],{},"Yes (Builder ecosystem)",[466,1907,1892],{},[446,1909,1910,1912,1915,1918,1921],{},[466,1911,1735],{},[466,1913,1914],{},"Image",[466,1916,1917],{},"HTML/Tailwind draft",[466,1919,1920],{},"Self-host",[466,1922,1923],{},"Guessed",[446,1925,1926,1928,1931,1934,1937],{},[466,1927,1766],{},[466,1929,1930],{},"Prompt",[466,1932,1933],{},"React/Tailwind",[466,1935,1936],{},"Yes",[466,1938,1939],{},"Invented",[446,1941,1942,1945,1948,1951,1953],{},[466,1943,1944],{},"MiroMiro /v1/code",[466,1946,1947],{},"Live URL",[466,1949,1950],{},"Tailwind/JSX/Vue + tokens",[466,1952,1936],{},[466,1954,1955],{},"Real (from the site's CSS)",[30,1957,1572],{"id":1571},[12,1959,1960,1961,1963,1964,100],{},"Match the tool to the input you actually have. Figma file: Anima or Locofy. Nothing yet: v0. A picture and low stakes: screenshot-to-code. A live site that must be matched faithfully, inside a pipeline: that is URL-to-code, and it is the lane we picked precisely because the others do not drive in it. ",[24,1962,456],{"href":574}," (100 credits a month, no card) or try a URL in the ",[24,1965,1583],{"href":1582},[30,1967,650],{"id":649},[652,1969,1971],{"id":1970},"what-is-the-best-way-to-turn-an-existing-website-into-code","What is the best way to turn an existing website into code?",[12,1973,1974],{},"If fidelity matters, read the live CSS (URL-to-code). If speed matters more, screenshot-to-code gives a fast approximation.",[652,1976,1978],{"id":1977},"can-ai-convert-a-screenshot-to-code-accurately","Can AI convert a screenshot to code accurately?",[12,1980,1981],{},"Plausibly, not accurately. Every color, spacing, and font value is a model's guess from pixels.",[652,1983,1985],{"id":1984},"figma-to-code-vs-url-to-code","Figma-to-code vs URL-to-code?",[12,1987,1988],{},"Input: a design file you own vs any live website. The first automates handoff; the second works when no design file exists.",[652,1990,1992],{"id":1991},"which-of-these-have-real-apis","Which of these have real APIs?",[12,1994,1995],{},"v0 and Builder.io within their platforms, and MiroMiro as a plain REST endpoint. The Figma plugins are primarily apps - a hard filter if you are building a pipeline.",[688,1997,1622],{},{"title":143,"searchDepth":157,"depth":157,"links":1999},[2000,2001,2002,2003,2004,2005,2006],{"id":1677,"depth":157,"text":1678},{"id":1725,"depth":157,"text":1726},{"id":1756,"depth":157,"text":1757},{"id":1787,"depth":157,"text":1788},{"id":1851,"depth":157,"text":1852},{"id":1571,"depth":157,"text":1572},{"id":649,"depth":157,"text":650,"children":2007},[2008,2009,2010,2011],{"id":1970,"depth":175,"text":1971},{"id":1977,"depth":175,"text":1978},{"id":1984,"depth":175,"text":1985},{"id":1991,"depth":175,"text":1992},"The design-to-code landscape in 2026 sorted by input - Figma files, screenshots, prompts, or live URLs - and which tool honestly wins for each job.",[2014,2016,2018,2021],{"question":1971,"answer":2015},"Depends on fidelity needs. For a quick approximation, screenshot-to-code tools work from a picture. For code that uses the site's real CSS values - exact colors, spacing, fonts - you need something that reads the live page's stylesheets, which is what a URL-to-code API does.",{"question":1978,"answer":2017},"It produces plausible code fast, but the model guesses every value: colors approximated from pixels, spacing eyeballed, fonts inferred. Great for prototypes; risky when the output must match a real design system.",{"question":2019,"answer":2020},"What is the difference between Figma-to-code and URL-to-code?","Input. Figma-to-code tools (Anima, Locofy, Builder.io) need a Figma file and turn design layers into components. URL-to-code starts from any live website - no design file required - and derives the code from the real rendered CSS.",{"question":2022,"answer":2023},"Which design-to-code tools have an actual API?","Fewer than the category suggests. Most are plugins or apps. If you need code generation inside your own product or agent pipeline, check for a documented REST endpoint first - that requirement alone eliminates most of the list.",{},"/api-blog/best-design-to-code-apis-2026",[1653,1654,729],{"title":1666,"description":2012},"api-blog/best-design-to-code-apis-2026",[2030,2031,2032,1662],"design to code","tailwind","code generation","Ds8Hb1IY5fXrd4oGyzAN3SLgbiC7wqiv79mad9KMYu8",{"id":2035,"title":2036,"author":7,"body":2037,"category":1637,"date":1242,"dateModified":710,"description":2436,"extension":712,"faqs":2437,"image":710,"meta":2449,"navigation":409,"path":2450,"readingTime":257,"relatedSlugs":2451,"seo":2452,"stem":2453,"tags":2454,"__hash__":2457},"apiBlog/api-blog/best-design-token-extraction-tools-2026.md","Best Design Token Extraction Tools in 2026",{"type":9,"value":2038,"toc":2420},[2039,2042,2046,2054,2071,2075,2083,2100,2104,2112,2129,2133,2144,2161,2165,2172,2189,2193,2196,2226,2246,2250,2359,2361,2371,2373,2377,2380,2384,2398,2402,2409,2413,2418],[12,2040,2041],{},"There are six honest ways to get design tokens out of a live website in 2026, and the right one depends on whether you are doing this once, weekly, or inside a product. Here is the list, with the tradeoffs stated plainly. Yes, our own API is on it - ranked where I would rank it if it were not ours.",[30,2043,2045],{"id":2044},"_1-css-stats-fastest-free-look","1. CSS Stats - fastest free look",[12,2047,2048,2053],{},[24,2049,2052],{"href":2050,"rel":2051},"https://cssstats.com",[1292],"cssstats.com"," has been the \"paste a URL, see the CSS\" tool for a decade. It reports every color, font size, and font family the stylesheet declares, plus specificity graphs nobody asks for but everyone screenshots.",[64,2055,2056,2061,2066],{},[41,2057,2058,2060],{},[45,2059,1308],{}," free, no signup, instant.",[41,2062,2063,2065],{},[45,2064,1314],{}," it reads declared CSS, not used CSS. A site shipping a big framework stylesheet reports hundreds of colors that never render. There is no export worth automating against.",[41,2067,2068,2070],{},[45,2069,1320],{}," you want a two-minute overview of a site's CSS health, not a token set.",[30,2072,2074],{"id":2073},"_2-project-wallace-the-analyzer-that-kept-evolving","2. Project Wallace - the analyzer that kept evolving",[12,2076,2077,2082],{},[24,2078,2081],{"href":2079,"rel":2080},"https://www.projectwallace.com",[1292],"Project Wallace"," does what CSS Stats does but deeper: color counts, typography breakdowns, and change tracking over time if you create an account.",[64,2084,2085,2090,2095],{},[41,2086,2087,2089],{},[45,2088,1308],{}," the most thorough free CSS analysis on the web, actively maintained.",[41,2091,2092,2094],{},[45,2093,1314],{}," same fundamental limit - declared, not rendered. Still a report you read, not a token file you use.",[41,2096,2097,2099],{},[45,2098,1320],{}," auditing your own CSS over time, or studying how a site's stylesheet is put together.",[30,2101,2103],{"id":2102},"_3-superposition-tokens-on-your-desktop","3. Superposition - tokens on your desktop",[12,2105,2106,2111],{},[24,2107,2110],{"href":2108,"rel":2109},"https://superposition.design",[1292],"Superposition"," is a desktop app that crawls a page and exports design tokens to CSS variables, Sass, JS, and even Adobe XD. It was ahead of its time.",[64,2113,2114,2119,2124],{},[41,2115,2116,2118],{},[45,2117,1308],{}," genuine token export formats, one-click, offline app.",[41,2120,2121,2123],{},[45,2122,1314],{}," desktop-only, so there is nothing to call from code or CI. Development has been quiet for a while - check that the export you need still works.",[41,2125,2126,2128],{},[45,2127,1320],{}," you are a designer who wants tokens from a reference site without touching a terminal.",[30,2130,2132],{"id":2131},"_4-a-diy-playwright-script-full-control","4. A DIY Playwright script - full control",[12,2134,2135,2136,2139,2140,100],{},"Load the page in a headless browser, read ",[16,2137,2138],{},"getComputedStyle"," off every element, count what repeats. We wrote the complete walkthrough in ",[24,2141,2143],{"href":2142},"/api/blog/how-to-extract-design-tokens-from-any-website-in-python","How to Extract Design Tokens from Any Website in Python",[64,2145,2146,2151,2156],{},[41,2147,2148,2150],{},[45,2149,1308],{}," free, sees exactly what renders (including runtime CSS-in-JS), infinitely customizable.",[41,2152,2153,2155],{},[45,2154,1314],{}," you now run browser infrastructure. Cookie banners, lazy fonts, and viewport-dependent styles are your problem, and the script that worked in April breaks in July.",[41,2157,2158,2160],{},[45,2159,1320],{}," one-off extractions, research, or when you need something no tool exposes.",[30,2162,2164],{"id":2163},"_5-miromiro-browser-extension-point-and-click","5. MiroMiro browser extension - point and click",[12,2166,1385,2167,2171],{},[24,2168,2170],{"href":2169},"/features/design-tokens","extension"," shows a site's tokens live as you browse: colors, typography, spacing, assets. Click an element, see its resolved styles, export what you want.",[64,2173,2174,2179,2184],{},[41,2175,2176,2178],{},[45,2177,1308],{}," zero setup, visual, great for picking tokens from a specific component rather than the whole page.",[41,2180,2181,2183],{},[45,2182,1314],{}," it is a human tool. Nothing to automate, no JSON endpoint, and it extracts what you look at, not a thousand URLs.",[41,2185,2186,2188],{},[45,2187,1320],{}," hand-picking design decisions from reference sites during design or build.",[30,2190,2192],{"id":2191},"_6-miromiro-extract-api-tokens-as-an-endpoint","6. MiroMiro extract API - tokens as an endpoint",[12,2194,2195],{},"One GET request returns the ranked token set as JSON, CSS variables, or a Tailwind theme block:",[138,2197,2199],{"className":360,"code":2198,"language":362,"meta":143,"style":143},"curl \"https://miromiro.app/api/v1/extract?url=stripe.com&format=tailwind\" \\\n  -H \"Authorization: Bearer $MIROMIRO_API_KEY\"\n",[16,2200,2201,2214],{"__ignoreMap":143},[147,2202,2203,2205,2207,2210,2212],{"class":149,"line":150},[147,2204,615],{"class":181},[147,2206,205],{"class":153},[147,2208,2209],{"class":208},"https://miromiro.app/api/v1/extract?url=stripe.com&format=tailwind",[147,2211,167],{"class":153},[147,2213,391],{"class":390},[147,2215,2216,2218,2220,2222,2224],{"class":149,"line":157},[147,2217,629],{"class":208},[147,2219,205],{"class":153},[147,2221,634],{"class":208},[147,2223,637],{"class":390},[147,2225,404],{"class":153},[64,2227,2228,2236,2241],{},[41,2229,2230,2232,2233,2235],{},[45,2231,1308],{}," deduped and ranked (not raw declared values), covers colors, type scale, spacing, radii, shadows, gradients, and motion, resolves ",[16,2234,1830],{}," chains and every declared breakpoint. It is the only option on this list you can put inside a product.",[41,2237,2238,2240],{},[45,2239,1314],{}," it parses static HTML + CSS and does not execute JavaScript, so a fully client-rendered app can come back thin. Costs credits past the free 100 a month.",[41,2242,2243,2245],{},[45,2244,1320],{}," tokens need to arrive programmatically - agent pipelines, onboarding flows, bulk analysis.",[30,2247,2249],{"id":2248},"the-comparison-in-one-table","The comparison in one table",[440,2251,2252,2268],{},[443,2253,2254],{},[446,2255,2256,2258,2260,2263,2266],{},[449,2257,1861],{},[449,2259,1867],{},[449,2261,2262],{},"Rendered or declared?",[449,2264,2265],{},"Automatable",[449,2267,522],{},[461,2269,2270,2285,2299,2313,2329,2343],{},[446,2271,2272,2275,2278,2281,2283],{},[466,2273,2274],{},"CSS Stats",[466,2276,2277],{},"Web report",[466,2279,2280],{},"Declared",[466,2282,512],{},[466,2284,525],{},[446,2286,2287,2289,2292,2294,2297],{},[466,2288,2081],{},[466,2290,2291],{},"Web report + tracking",[466,2293,2280],{},[466,2295,2296],{},"Partially",[466,2298,1483],{},[446,2300,2301,2303,2306,2309,2311],{},[466,2302,2110],{},[466,2304,2305],{},"Token files (CSS/Sass/JS)",[466,2307,2308],{},"Rendered",[466,2310,512],{},[466,2312,525],{},[446,2314,2315,2318,2321,2323,2326],{},[466,2316,2317],{},"DIY Playwright",[466,2319,2320],{},"Whatever you build",[466,2322,2308],{},[466,2324,2325],{},"Yes, you maintain it",[466,2327,2328],{},"Your time",[446,2330,2331,2334,2337,2339,2341],{},[466,2332,2333],{},"MiroMiro extension",[466,2335,2336],{},"Visual + export",[466,2338,2308],{},[466,2340,512],{},[466,2342,1483],{},[446,2344,2345,2348,2351,2354,2356],{},[466,2346,2347],{},"MiroMiro API",[466,2349,2350],{},"JSON / CSS vars / Tailwind",[466,2352,2353],{},"Parsed + resolved",[466,2355,1936],{},[466,2357,2358],{},"100 credits/mo free, then from €19/mo",[30,2360,1572],{"id":1571},[12,2362,2363,2364,2367,2368,2370],{},"For a quick look, Project Wallace. For hand-picking from a reference site, the extension. For anything that runs on a schedule or inside a product, the API - and I would say that even if it were someone else's, because it is the only entry here that returns a token file over HTTP. You can ",[24,2365,2366],{"href":574},"get a free API key"," (100 credits a month, no card) or poke it in the ",[24,2369,1583],{"href":1582}," first.",[30,2372,650],{"id":649},[652,2374,2376],{"id":2375},"what-is-the-fastest-free-way-to-extract-design-tokens-from-a-website","What is the fastest free way to extract design tokens from a website?",[12,2378,2379],{},"Paste the URL into CSS Stats or Project Wallace. Seconds, no signup. Expect raw declared values, not a cleaned token set.",[652,2381,2383],{"id":2382},"can-i-get-tokens-as-css-variables-or-a-tailwind-config-directly","Can I get tokens as CSS variables or a Tailwind config directly?",[12,2385,2386,2387,2390,2391,2394,2395,100],{},"Yes - ",[16,2388,2389],{},"format=css"," returns custom properties, ",[16,2392,2393],{},"format=tailwind"," returns a theme block. Details in the ",[24,2396,2397],{"href":1393},"design tokens docs",[652,2399,2401],{"id":2400},"do-these-tools-work-on-tailwind-sites","Do these tools work on Tailwind sites?",[12,2403,2404,2405,2408],{},"The ones reading real CSS or computed styles do; ",[16,2406,2407],{},"bg-indigo-600"," is just CSS once shipped. Tools that only look for CSS custom properties miss most of a Tailwind site's system.",[652,2410,2412],{"id":2411},"what-is-the-difference-between-a-token-set-and-a-color-palette","What is the difference between a token set and a color palette?",[12,2414,2415,2416,100],{},"A palette is one token group. A full token set adds the type scale, spacing, radii, shadows, motion, and z-index layers - see the ",[24,2417,1618],{"href":1617},[688,2419,1622],{},{"title":143,"searchDepth":157,"depth":157,"links":2421},[2422,2423,2424,2425,2426,2427,2428,2429,2430],{"id":2044,"depth":157,"text":2045},{"id":2073,"depth":157,"text":2074},{"id":2102,"depth":157,"text":2103},{"id":2131,"depth":157,"text":2132},{"id":2163,"depth":157,"text":2164},{"id":2191,"depth":157,"text":2192},{"id":2248,"depth":157,"text":2249},{"id":1571,"depth":157,"text":1572},{"id":649,"depth":157,"text":650,"children":2431},[2432,2433,2434,2435],{"id":2375,"depth":175,"text":2376},{"id":2382,"depth":175,"text":2383},{"id":2400,"depth":175,"text":2401},{"id":2411,"depth":175,"text":2412},"Six real ways to pull design tokens from a live website in 2026 - free analyzers, a DIY script, a browser extension, and an API - honestly ranked.",[2438,2440,2443,2446],{"question":2376,"answer":2439},"Paste the URL into a CSS analyzer like CSS Stats or Project Wallace. You get colors, font sizes, and specificity data in seconds with no signup. The output is raw declared values rather than a cleaned token set, so expect to filter it by hand.",{"question":2441,"answer":2442},"Can I get design tokens as CSS variables or a Tailwind config directly?","Yes. The MiroMiro extract endpoint takes format=css to return CSS custom properties or format=tailwind to return a theme config block, so the tokens drop straight into a codebase without a conversion step.",{"question":2444,"answer":2445},"Do design token extractors work on Tailwind sites?","Tools that read the site's actual CSS or computed styles do, because bg-indigo-600 is just CSS once shipped. Tools that look for CSS variables only will miss most of a Tailwind site's system, since utility-first sites often define few custom properties.",{"question":2447,"answer":2448},"What is the difference between extracting design tokens and extracting a color palette?","A palette is one token group. A design token set also covers the type scale, spacing, radii, shadows, motion, and z-index layers - the full set of decisions you need to rebuild UI in the site's style.",{},"/api-blog/best-design-token-extraction-tools-2026",[729,1260,730],{"title":2036,"description":2436},"api-blog/best-design-token-extraction-tools-2026",[1265,2455,1662,2456],"tools","css analysis","GhGcQEA8Q7VmKm_mMFRSpL45k0PaBwatHPzBRiFUK9g",{"id":2459,"title":2460,"author":7,"body":2461,"category":1637,"date":1242,"dateModified":710,"description":2786,"extension":712,"faqs":2787,"image":710,"meta":2799,"navigation":409,"path":2800,"readingTime":257,"relatedSlugs":2801,"seo":2803,"stem":2804,"tags":2805,"__hash__":2809},"apiBlog/api-blog/best-firecrawl-alternatives-2026.md","5 Best Firecrawl Alternatives in 2026",{"type":9,"value":2462,"toc":2771},[2463,2466,2469,2473,2481,2499,2503,2515,2532,2536,2548,2565,2569,2577,2594,2598,2605,2633,2639,2656,2660,2727,2729,2739,2741,2745,2748,2752,2755,2759,2762,2766,2769],[12,2464,2465],{},"\"Firecrawl alternative\" usually means one of five different complaints: it costs too much at volume, you want open source, you need heavier browser automation, you want search instead of crawling, or you need something it does not extract at all. Different complaint, different answer - here are the five, honestly.",[12,2467,2468],{},"For the record, Firecrawl is good. Their pricing (verified July 2026): free 1,000 credits, Hobby $16/mo for 5,000 pages, Standard $83/mo for 100k, up to Scale at $599/mo for a million. Simple unit, fair prices. If it is doing its job for you, keep it, and skip to the last section for the one case where it genuinely cannot help.",[30,2470,2472],{"id":2471},"_1-crawl4ai-the-open-source-answer","1. Crawl4AI - the open-source answer",[12,2474,2475,2480],{},[24,2476,2479],{"href":2477,"rel":2478},"https://github.com/unclecode/crawl4ai",[1292],"Crawl4AI"," is the most popular OSS route: a Python crawler built for LLM pipelines, outputting clean markdown, with an enthusiastic community and rapid development.",[64,2482,2483,2488,2493],{},[41,2484,2485,2487],{},[45,2486,1308],{}," free forever, self-hosted, no per-page anxiety, full data control.",[41,2489,2490,2492],{},[45,2491,1314],{}," you own the infrastructure - proxies, JS rendering, anti-bot upkeep, retries. The afternoon it saves in fees it can take back in maintenance.",[41,2494,2495,2498],{},[45,2496,2497],{},"Pick it when:"," volume is high, budgets are tight, and you have the engineering appetite.",[30,2500,2502],{"id":2501},"_2-jina-reader-the-simplest-possible-api","2. Jina Reader - the simplest possible API",[12,2504,2505,2510,2511,2514],{},[24,2506,2509],{"href":2507,"rel":2508},"https://jina.ai/reader/",[1292],"Jina Reader"," is the lowest-friction markdown-from-URL service that exists: prefix a URL with ",[16,2512,2513],{},"r.jina.ai/"," and get LLM-ready text, generous free tier, no signup for casual use.",[64,2516,2517,2522,2527],{},[41,2518,2519,2521],{},[45,2520,1308],{}," zero setup, genuinely free for light use, great output for articles and docs.",[41,2523,2524,2526],{},[45,2525,1314],{}," single-page reads, not site crawling; fewer knobs when a page fights back.",[41,2528,2529,2531],{},[45,2530,2497],{}," your agent reads pages one at a time and you want the cheapest possible path.",[30,2533,2535],{"id":2534},"_3-apify-when-scraping-is-an-application","3. Apify - when scraping is an application",[12,2537,2538,2543,2544,100],{},[24,2539,2542],{"href":2540,"rel":2541},"https://apify.com",[1292],"Apify"," is a platform, not an endpoint: thousands of prebuilt scrapers (\"Actors\") for specific sites, scheduling, storage, and a marketplace. We compared it in depth in our ",[24,2545,2547],{"href":2546},"/api/compare/apify-alternative","Apify alternative page",[64,2549,2550,2555,2560],{},[41,2551,2552,2554],{},[45,2553,1308],{}," the prebuilt Actors cover sites that generic crawlers struggle with; real orchestration features.",[41,2556,2557,2559],{},[45,2558,1314],{}," platform complexity and platform pricing. For \"give me this page as markdown\" it is a lot of machinery.",[41,2561,2562,2564],{},[45,2563,2497],{}," scraping specific difficult sites on a schedule is a core workflow.",[30,2566,2568],{"id":2567},"_4-tavily-search-instead-of-crawl","4. Tavily - search instead of crawl",[12,2570,2571,2576],{},[24,2572,2575],{"href":2573,"rel":2574},"https://tavily.com",[1292],"Tavily"," reframes the problem: instead of crawling sites, it gives agents a search API that returns answer-shaped, cited results.",[64,2578,2579,2584,2589],{},[41,2580,2581,2583],{},[45,2582,1308],{}," for research agents, search-shaped input beats crawl-shaped input - less context, more signal.",[41,2585,2586,2588],{},[45,2587,1314],{}," it is not extraction. You cannot point it at a specific page and get its full content faithfully.",[41,2590,2591,2593],{},[45,2592,2497],{}," the agent's question is \"what is true about X\" rather than \"what does this page say\".",[30,2595,2597],{"id":2596},"_5-miromiro-when-the-answer-isnt-text-at-all","5. MiroMiro - when the answer isn't text at all",[12,2599,2600,2601,2604],{},"Everything above ships text. If the reason you are searching for alternatives is that your agent builds ",[45,2602,2603],{},"UI"," - an app generator theming output to a user's brand, an onboarding flow that imports a company's look, a coding agent that needs real design context - then markdown was never the right output format, and that is the lane we occupy:",[138,2606,2607],{"className":360,"code":1151,"language":362,"meta":143,"style":143},[16,2608,2609,2621],{"__ignoreMap":143},[147,2610,2611,2613,2615,2617,2619],{"class":149,"line":150},[147,2612,615],{"class":181},[147,2614,205],{"class":153},[147,2616,1162],{"class":208},[147,2618,167],{"class":153},[147,2620,391],{"class":390},[147,2622,2623,2625,2627,2629,2631],{"class":149,"line":157},[147,2624,629],{"class":208},[147,2626,205],{"class":153},[147,2628,634],{"class":208},[147,2630,637],{"class":390},[147,2632,404],{"class":153},[12,2634,2635,2636,2638],{},"Design tokens (ranked colors, type scale, spacing, shadows, motion), brand data, SVGs, fonts, and section-level Tailwind/JSX/Vue via ",[24,2637,1399],{"href":1398},". MCP server included for Claude Code and Cursor. Free tier is 100 credits a month, no card; paid from €19/mo.",[64,2640,2641,2646,2651],{},[41,2642,2643,2645],{},[45,2644,1308],{}," the only entry here that returns a design system. Complements Firecrawl rather than replacing it - content from them, design from us is a real pattern.",[41,2647,2648,2650],{},[45,2649,1314],{}," it will not crawl a thousand documentation pages into markdown. Wrong tool for that; use the four above.",[41,2652,2653,2655],{},[45,2654,2497],{}," the deliverable is on-brand UI, not text.",[30,2657,2659],{"id":2658},"the-five-sorted-by-complaint","The five, sorted by complaint",[440,2661,2662,2675],{},[443,2663,2664],{},[446,2665,2666,2669,2672],{},[449,2667,2668],{},"Your complaint with Firecrawl",[449,2670,2671],{},"Alternative",[449,2673,2674],{},"Why",[461,2676,2677,2687,2697,2707,2717],{},[446,2678,2679,2682,2684],{},[466,2680,2681],{},"Cost at volume",[466,2683,2479],{},[466,2685,2686],{},"Self-hosted, no per-page fee",[446,2688,2689,2692,2694],{},[466,2690,2691],{},"Just want one page as markdown",[466,2693,2509],{},[466,2695,2696],{},"URL prefix, done",[446,2698,2699,2702,2704],{},[466,2700,2701],{},"Difficult specific sites, scheduling",[466,2703,2542],{},[466,2705,2706],{},"Prebuilt Actors + orchestration",[446,2708,2709,2712,2714],{},[466,2710,2711],{},"Agent needs answers, not pages",[466,2713,2575],{},[466,2715,2716],{},"Search-shaped API",[446,2718,2719,2722,2724],{},[466,2720,2721],{},"Need design, not text",[466,2723,74],{},[466,2725,2726],{},"Tokens, brand, assets, section code",[30,2728,1572],{"id":1571},[12,2730,2731,2732,2734,2735,100],{},"For text at scale, Crawl4AI or stay with Firecrawl - their pricing is honestly hard to beat below 100k pages. For agent research, Tavily. And if you landed here because no amount of markdown gets you a brand's colors, fonts, and components: that is not a Firecrawl weakness, it is a category boundary, and it is the one we built for. ",[24,2733,456],{"href":574},", or the full head-to-head is at ",[24,2736,2738],{"href":2737},"/api/compare/firecrawl-alternative","MiroMiro vs Firecrawl",[30,2740,650],{"id":649},[652,2742,2744],{"id":2743},"what-is-the-best-free-firecrawl-alternative","What is the best free Firecrawl alternative?",[12,2746,2747],{},"Crawl4AI self-hosted, or Jina Reader for zero-setup single pages.",[652,2749,2751],{"id":2750},"is-firecrawl-worth-it-vs-open-source","Is Firecrawl worth it vs open source?",[12,2753,2754],{},"Below serious volume, usually yes - $16/mo for 5,000 pages is cheaper than maintaining a crawler stack. OSS wins on volume and data control.",[652,2756,2758],{"id":2757},"what-cant-firecrawl-extract","What can't Firecrawl extract?",[12,2760,2761],{},"The design layer - tokens, brand, typography, assets, component code. Markdown strips it by design.",[652,2763,2765],{"id":2764},"which-alternative-is-best-for-ai-agents","Which alternative is best for AI agents?",[12,2767,2768],{},"Research agents: Tavily. Page-reading agents: Jina or Firecrawl. UI-building agents: MiroMiro.",[688,2770,1622],{},{"title":143,"searchDepth":157,"depth":157,"links":2772},[2773,2774,2775,2776,2777,2778,2779,2780],{"id":2471,"depth":157,"text":2472},{"id":2501,"depth":157,"text":2502},{"id":2534,"depth":157,"text":2535},{"id":2567,"depth":157,"text":2568},{"id":2596,"depth":157,"text":2597},{"id":2658,"depth":157,"text":2659},{"id":1571,"depth":157,"text":1572},{"id":649,"depth":157,"text":650,"children":2781},[2782,2783,2784,2785],{"id":2743,"depth":175,"text":2744},{"id":2750,"depth":175,"text":2751},{"id":2757,"depth":175,"text":2758},{"id":2764,"depth":175,"text":2765},"Five real Firecrawl alternatives in 2026, matched to the job: cheap markdown, open source, site automation, agent search, or design context for AI.",[2788,2790,2793,2796],{"question":2744,"answer":2789},"Crawl4AI if you can self-host - it is open source, actively developed, and produces LLM-ready markdown with no per-page cost beyond your infrastructure. Jina Reader's free tier is the no-setup option: prefix any URL with r.jina.ai and get markdown back.",{"question":2791,"answer":2792},"Is Firecrawl worth it compared to open source scrapers?","If your time has a price, usually yes. Self-hosting a crawler means owning proxies, JS rendering, retries, and anti-bot upkeep. Firecrawl's Hobby tier at $16/mo for 5,000 pages is cheaper than the first afternoon of maintaining that stack. Open source wins on volume and data control, not convenience.",{"question":2794,"answer":2795},"What can't Firecrawl extract from a website?","The design. Firecrawl converts pages to markdown and structured text for LLMs - by design, that strips the visual layer. Brand colors, typography, spacing, design tokens, SVGs, and component code need a design-extraction tool; no volume of markdown credits gets you there.",{"question":2797,"answer":2798},"Which Firecrawl alternative is best for AI agents?","Depends on the loop. For research agents that answer questions, Tavily's search-shaped API fits better than crawling. For agents that read specific pages, Jina Reader or Firecrawl itself. For agents that build UI and need a site's design system as ground truth, a design-extraction API like MiroMiro.",{},"/api-blog/best-firecrawl-alternatives-2026",[730,2802,1653],"best-brand-data-apis-2026",{"title":2460,"description":2786},"api-blog/best-firecrawl-alternatives-2026",[2806,2807,2808,737,1662],"firecrawl","alternatives","web scraping","IwAwF9vtdiqk_AlwZsEggBXVUHTcQNd0fz_Wwe13QZU",{"id":2811,"title":2812,"author":7,"body":2813,"category":3393,"date":1242,"dateModified":710,"description":3394,"extension":712,"faqs":3395,"image":710,"meta":3406,"navigation":409,"path":3407,"readingTime":251,"relatedSlugs":3408,"seo":3410,"stem":3411,"tags":3412,"__hash__":3416},"apiBlog/api-blog/design-token-types-reference.md","Design Token Types: Every Token a Website Exposes (Reference)",{"type":9,"value":2814,"toc":3382},[2815,2818,2825,2829,3192,3196,3202,3214,3220,3230,3234,3240,3270,3335,3337,3341,3344,3348,3351,3355,3358,3362,3380],[12,2816,2817],{},"This is the reference we wish had existed when we built our extraction engine: every token type a live website exposes, the CSS properties each one comes from, and the gotcha that silently corrupts it. Seventeen groups, one table each way.",[12,2819,2820,2821,2824],{},"If you want the extraction code itself, that is in the ",[24,2822,2823],{"href":2142},"Python tokens guide",". This page is the map.",[30,2826,2828],{"id":2827},"the-complete-token-table","The complete token table",[440,2830,2831,2847],{},[443,2832,2833],{},[446,2834,2835,2838,2841,2844],{},[449,2836,2837],{},"Token group",[449,2839,2840],{},"CSS sources",[449,2842,2843],{},"What good output looks like",[449,2845,2846],{},"The gotcha",[461,2848,2849,2877,2903,2919,2939,2963,2979,3001,3023,3045,3061,3077,3093,3109,3131,3156,3175],{},[446,2850,2851,2853,2871,2874],{},[466,2852,1474],{},[466,2854,2855,2858,2859,2858,2862,2858,2865,2858,2868],{},[16,2856,2857],{},"color",", ",[16,2860,2861],{},"background-color",[16,2863,2864],{},"border-*-color",[16,2866,2867],{},"fill",[16,2869,2870],{},"stroke",[466,2872,2873],{},"5-12 ranked values, not 200",[466,2875,2876],{},"Browser defaults and cookie banners pollute counts",[446,2878,2879,2882,2890,2897],{},[466,2880,2881],{},"Gradients",[466,2883,2884,2858,2887],{},[16,2885,2886],{},"background-image",[16,2888,2889],{},"mask-image",[466,2891,2892,2893,2896],{},"Full ",[16,2894,2895],{},"linear-gradient(...)"," strings",[466,2898,2899,2900,2902],{},"Not in ",[16,2901,2861],{}," - miss this and gradient brands report as white",[446,2904,2905,2908,2913,2916],{},[466,2906,2907],{},"Font families",[466,2909,2910],{},[16,2911,2912],{},"font-family",[466,2914,2915],{},"1-3 families with roles (heading/body/mono)",[466,2917,2918],{},"The stack lists what CSS asked for, not what loaded",[446,2920,2921,2924,2929,2932],{},[466,2922,2923],{},"Font sizes",[466,2925,2926],{},[16,2927,2928],{},"font-size",[466,2930,2931],{},"A scale: 12, 14, 16, 20, 24, 36, 56",[466,2933,2934,2935,2938],{},"Fluid ",[16,2936,2937],{},"clamp()"," values resolve differently per viewport",[446,2940,2941,2944,2949,2952],{},[466,2942,2943],{},"Font weights",[466,2945,2946],{},[16,2947,2948],{},"font-weight",[466,2950,2951],{},"3-5 weights actually used",[466,2953,2954,2955,2958,2959,2962],{},"Keyword values (",[16,2956,2957],{},"bold",") and numbers (",[16,2960,2961],{},"700",") need normalizing",[446,2964,2965,2968,2973,2976],{},[466,2966,2967],{},"Line heights",[466,2969,2970],{},[16,2971,2972],{},"line-height",[466,2974,2975],{},"Unitless ratios per size tier",[466,2977,2978],{},"Computed styles return px; divide by font-size to recover the ratio",[446,2980,2981,2984,2989,2996],{},[466,2982,2983],{},"Letter spacing",[466,2985,2986],{},[16,2987,2988],{},"letter-spacing",[466,2990,2991,2992,2995],{},"Tight values on headings, ",[16,2993,2994],{},"normal"," elsewhere",[466,2997,2998,3000],{},[16,2999,2994],{}," is a value, not an absence",[446,3002,3003,3006,3017,3020],{},[466,3004,3005],{},"Spacing",[466,3007,3008,2858,3011,2858,3014],{},[16,3009,3010],{},"margin",[16,3012,3013],{},"padding",[16,3015,3016],{},"gap",[466,3018,3019],{},"A grid: 4, 8, 12, 16, 24, 32, 48, 64",[466,3021,3022],{},"Snap to the base unit or you get 40 near-duplicates",[446,3024,3025,3028,3033,3036],{},[466,3026,3027],{},"Border radii",[466,3029,3030],{},[16,3031,3032],{},"border-radius",[466,3034,3035],{},"2-4 values (subtle, card, pill)",[466,3037,3038,1688,3041,3044],{},[16,3039,3040],{},"9999px",[16,3042,3043],{},"50%"," are the same intent, different strings",[446,3046,3047,3050,3055,3058],{},[466,3048,3049],{},"Border widths",[466,3051,3052],{},[16,3053,3054],{},"border-width",[466,3056,3057],{},"1-2 values",[466,3059,3060],{},"Mostly 1px everywhere; the outliers are the signal",[446,3062,3063,3066,3071,3074],{},[466,3064,3065],{},"Box shadows",[466,3067,3068],{},[16,3069,3070],{},"box-shadow",[466,3072,3073],{},"2-4 elevation levels",[466,3075,3076],{},"Multi-layer shadows are one token, not four",[446,3078,3079,3082,3087,3090],{},[466,3080,3081],{},"Text shadows",[466,3083,3084],{},[16,3085,3086],{},"text-shadow",[466,3088,3089],{},"Usually empty; notable when not",[466,3091,3092],{},"Rare enough that any hit is worth keeping",[446,3094,3095,3098,3103,3106],{},[466,3096,3097],{},"Opacities",[466,3099,3100],{},[16,3101,3102],{},"opacity",[466,3104,3105],{},"Disabled/overlay/hover steps",[466,3107,3108],{},"Transition intermediate values sneak in if you sample mid-animation",[446,3110,3111,3114,3122,3125],{},[466,3112,3113],{},"Durations",[466,3115,3116,2858,3119],{},[16,3117,3118],{},"transition-duration",[16,3120,3121],{},"animation-duration",[466,3123,3124],{},"150ms/300ms/500ms tiers",[466,3126,3127,3130],{},[16,3128,3129],{},"0s"," is a default, not a token",[446,3132,3133,3136,3144,3150],{},[466,3134,3135],{},"Easings",[466,3137,3138,2858,3141],{},[16,3139,3140],{},"transition-timing-function",[16,3142,3143],{},"animation-timing-function",[466,3145,3146,3147],{},"The brand's ",[16,3148,3149],{},"cubic-bezier(...)",[466,3151,3152,3155],{},[16,3153,3154],{},"ease"," defaults drown the custom curves - filter them",[446,3157,3158,3161,3166,3169],{},[466,3159,3160],{},"Z-index layers",[466,3162,3163],{},[16,3164,3165],{},"z-index",[466,3167,3168],{},"The layering scale: 10, 50, 100, 9999",[466,3170,3171,3174],{},[16,3172,3173],{},"auto"," everywhere except the 5 values that matter",[446,3176,3177,3180,3186,3189],{},[466,3178,3179],{},"Breakpoints",[466,3181,3182,3185],{},[16,3183,3184],{},"@media"," conditions",[466,3187,3188],{},"3-5 widths: 640, 768, 1024, 1280",[466,3190,3191],{},"Only visible in stylesheets - computed styles never expose them",[30,3193,3195],{"id":3194},"the-same-table-by-extraction-difficulty","The same table, by extraction difficulty",[12,3197,3198,3201],{},[45,3199,3200],{},"Free (one property read):"," colors, font families, font sizes, font weights, opacities, z-index.",[12,3203,3204,3207,3208,3210,3211,3213],{},[45,3205,3206],{},"Needs normalizing:"," line heights (px to ratio), spacing (grid snapping), radii (",[16,3209,3040],{}," = ",[16,3212,3043],{}," = pill), weights (keywords to numbers), shadows (multi-layer grouping).",[12,3215,3216,3219],{},[45,3217,3218],{},"Needs the stylesheet, not computed styles:"," breakpoints, and any media-query variant of everything else. This is the structural reason a computed-styles-only extractor reports one viewport and calls it the design system.",[12,3221,3222,3225,3226,3229],{},[45,3223,3224],{},"Needs judgment:"," which color is ",[16,3227,3228],{},"primary"," (frequency plus element context - what is on the buttons), which durations are intentional vs defaults, which fonts actually loaded.",[30,3231,3233],{"id":3232},"how-this-maps-to-the-api","How this maps to the API",[12,3235,3236,3237,136],{},"Every group in the table is a field on our extract endpoint, selectable with ",[16,3238,3239],{},"?fields=",[138,3241,3243],{"className":360,"code":3242,"language":362,"meta":143,"style":143},"curl \"https://miromiro.app/api/v1/extract?url=stripe.com&fields=colors,spacing,easings\" \\\n  -H \"Authorization: Bearer $MIROMIRO_API_KEY\"\n",[16,3244,3245,3258],{"__ignoreMap":143},[147,3246,3247,3249,3251,3254,3256],{"class":149,"line":150},[147,3248,615],{"class":181},[147,3250,205],{"class":153},[147,3252,3253],{"class":208},"https://miromiro.app/api/v1/extract?url=stripe.com&fields=colors,spacing,easings",[147,3255,167],{"class":153},[147,3257,391],{"class":390},[147,3259,3260,3262,3264,3266,3268],{"class":149,"line":157},[147,3261,629],{"class":208},[147,3263,205],{"class":153},[147,3265,634],{"class":208},[147,3267,637],{"class":390},[147,3269,404],{"class":153},[12,3271,3272,3273,2858,3276,2858,3279,2858,3282,2858,3285,2858,3288,2858,3291,2858,3294,2858,3297,2858,3300,2858,3303,2858,3306,2858,3309,2858,3312,2858,3315,2858,3318,2858,3321,3324,3325,3327,3328,3330,3331,3334],{},"The full field list: ",[16,3274,3275],{},"colors",[16,3277,3278],{},"gradients",[16,3280,3281],{},"fontFamilies",[16,3283,3284],{},"fontSizes",[16,3286,3287],{},"fontWeights",[16,3289,3290],{},"lineHeights",[16,3292,3293],{},"letterSpacings",[16,3295,3296],{},"spacing",[16,3298,3299],{},"borderRadiuses",[16,3301,3302],{},"borderWidths",[16,3304,3305],{},"boxShadows",[16,3307,3308],{},"textShadows",[16,3310,3311],{},"opacities",[16,3313,3314],{},"durations",[16,3316,3317],{},"easings",[16,3319,3320],{},"zIndexes",[16,3322,3323],{},"breakpoints",". Add ",[16,3326,2389],{}," or ",[16,3329,2393],{}," to get the same data as custom properties or a theme block instead of JSON. One call is 10 credits; ",[24,3332,3333],{"href":574},"100 credits a month are free",", no card.",[30,3336,650],{"id":649},[652,3338,3340],{"id":3339},"what-are-the-main-types-of-design-tokens","What are the main types of design tokens?",[12,3342,3343],{},"Color, typography (families/sizes/weights/line heights/letter spacing), spacing, borders (radii/widths), effects (shadows/gradients/opacities), motion (durations/easings), and structure (z-index, breakpoints). Seventeen groups in total from a live site.",[652,3345,3347],{"id":3346},"are-css-custom-properties-the-same-as-design-tokens","Are CSS custom properties the same as design tokens?",[12,3349,3350],{},"No. Custom properties are one storage format. Tailwind and CSS-in-JS sites often ship almost none while having rigorous token systems. Extract resolved values, not variable names.",[652,3352,3354],{"id":3353},"which-tokens-matter-most","Which tokens matter most?",[12,3356,3357],{},"Colors, type scale, spacing - in that order. They carry the identity. Radii, shadows, and motion are what make a rebuild feel like the original instead of a wireframe of it.",[652,3359,3361],{"id":3360},"can-i-extract-motion-tokens-from-a-website","Can I extract motion tokens from a website?",[12,3363,3364,3365,2858,3367,3369,3370,3372,3373,3375,3376,1688,3378,100],{},"Yes: ",[16,3366,3118],{},[16,3368,3121],{},", and their timing functions. Filter out ",[16,3371,3129],{}," and default ",[16,3374,3154],{}," or the real values drown. Our extract endpoint returns them as ",[16,3377,3314],{},[16,3379,3317],{},[688,3381,1622],{},{"title":143,"searchDepth":157,"depth":157,"links":3383},[3384,3385,3386,3387],{"id":2827,"depth":157,"text":2828},{"id":3194,"depth":157,"text":3195},{"id":3232,"depth":157,"text":3233},{"id":649,"depth":157,"text":650,"children":3388},[3389,3390,3391,3392],{"id":3339,"depth":175,"text":3340},{"id":3346,"depth":175,"text":3347},{"id":3353,"depth":175,"text":3354},{"id":3360,"depth":175,"text":3361},"Reference","All 17 design token types a website exposes - the CSS properties each comes from, what good output looks like, and the gotcha that silently corrupts it.",[3396,3398,3401,3403],{"question":3340,"answer":3397},"Color tokens, typography tokens (families, sizes, weights, line heights, letter spacing), spacing tokens, border tokens (radii, widths), effect tokens (shadows, gradients, opacities), motion tokens (durations, easings), and structural tokens (z-index layers, breakpoints). A complete extraction from a live site covers around 17 distinct groups.",{"question":3399,"answer":3400},"Which CSS properties do design tokens come from?","Each token group maps to specific CSS properties: colors from color, background-color and border-color; the type scale from font-size; spacing from margin, padding and gap; radii from border-radius; shadows from box-shadow; motion from transition-duration, animation-duration and their timing functions; breakpoints from media query conditions.",{"question":3347,"answer":3402},"No. Custom properties are one way a site stores tokens. Many production sites - especially Tailwind and CSS-in-JS codebases - ship few or no custom properties while still having a rigorous token system. Extract from resolved values, not variable names, or you will miss most real-world systems.",{"question":3404,"answer":3405},"What order should I extract tokens in?","Colors, type scale, and spacing first - they carry most of a site's identity and are enough to theme a rough UI. Radii, shadows, and motion are the polish layer that makes a rebuild feel like the original.",{},"/api-blog/design-token-types-reference",[729,1653,3409],"how-to-detect-fonts-a-website-uses",{"title":2812,"description":3394},"api-blog/design-token-types-reference",[1265,3413,3414,3415],"reference","css","cheatsheet","Q2H5fOPEU9oXwkGDkaF1v9diKxKTdrdCh3xo9PqdAaM",{"id":3418,"title":3419,"author":7,"body":3420,"category":4158,"date":1242,"dateModified":710,"description":4159,"extension":712,"faqs":4160,"image":710,"meta":4173,"navigation":409,"path":4174,"readingTime":251,"relatedSlugs":4175,"seo":4177,"stem":4178,"tags":4179,"__hash__":4184},"apiBlog/api-blog/how-to-detect-fonts-a-website-uses.md","How to Detect and Extract the Fonts a Website Uses",{"type":9,"value":3421,"toc":4143},[3422,3432,3435,3439,3668,3671,3675,3678,3755,3762,3766,3777,3781,3807,3934,3949,3953,4028,4032,4039,4069,4083,4087,4098,4100,4104,4107,4111,4117,4121,4133,4137,4140],[12,3423,3424,3425,3428,3429,3431],{},"The fastest way to see a website's fonts is DevTools: select an element and check the ",[45,3426,3427],{},"rendered fonts"," panel at the bottom of the Computed tab. It names what actually painted, which - and this is the whole subject of this post - is frequently not what the CSS asked for. ",[16,3430,2912],{}," is a wish list. The browser walks it until something works, and with lazy loading in the mix, what renders can differ from what is declared, per element, per moment.",[12,3433,3434],{},"For anything beyond one element, you want a script. Here are the three signals worth reading, and what each one can and cannot tell you.",[30,3436,3438],{"id":3437},"signal-1-computed-styles-what-css-resolved-to","Signal 1: computed styles (what CSS resolved to)",[138,3440,3444],{"className":3441,"code":3442,"language":3443,"meta":143,"style":143},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","// Paste in the console: ranked font stacks in use\nObject.entries([...document.querySelectorAll('body *')].reduce((acc, el) => {\n  const f = getComputedStyle(el).fontFamily;\n  acc[f] = (acc[f] || 0) + 1;\n  return acc;\n}, {})).sort((a, b) => b[1] - a[1]).slice(0, 5)\n","js",[16,3445,3446,3451,3516,3545,3589,3600],{"__ignoreMap":143},[147,3447,3448],{"class":149,"line":150},[147,3449,3450],{"class":422},"// Paste in the console: ranked font stacks in use\n",[147,3452,3453,3456,3458,3462,3465,3468,3471,3473,3476,3479,3482,3485,3487,3490,3492,3495,3497,3499,3503,3505,3508,3511,3514],{"class":149,"line":157},[147,3454,3455],{"class":390},"Object",[147,3457,100],{"class":153},[147,3459,3461],{"class":3460},"s2Zo4","entries",[147,3463,3464],{"class":390},"([",[147,3466,3467],{"class":153},"...",[147,3469,3470],{"class":390},"document",[147,3472,100],{"class":153},[147,3474,3475],{"class":3460},"querySelectorAll",[147,3477,3478],{"class":390},"(",[147,3480,3481],{"class":153},"'",[147,3483,3484],{"class":208},"body *",[147,3486,3481],{"class":153},[147,3488,3489],{"class":390},")]",[147,3491,100],{"class":153},[147,3493,3494],{"class":3460},"reduce",[147,3496,3478],{"class":390},[147,3498,3478],{"class":153},[147,3500,3502],{"class":3501},"sHdIc","acc",[147,3504,1050],{"class":153},[147,3506,3507],{"class":3501}," el",[147,3509,3510],{"class":153},")",[147,3512,3513],{"class":163}," =>",[147,3515,172],{"class":153},[147,3517,3518,3521,3524,3527,3530,3532,3535,3537,3539,3542],{"class":149,"line":175},[147,3519,3520],{"class":163},"  const",[147,3522,3523],{"class":390}," f",[147,3525,3526],{"class":153}," =",[147,3528,3529],{"class":3460}," getComputedStyle",[147,3531,3478],{"class":233},[147,3533,3534],{"class":390},"el",[147,3536,3510],{"class":233},[147,3538,100],{"class":153},[147,3540,3541],{"class":390},"fontFamily",[147,3543,3544],{"class":153},";\n",[147,3546,3547,3550,3553,3556,3559,3562,3565,3567,3569,3571,3573,3576,3579,3582,3584,3587],{"class":149,"line":191},[147,3548,3549],{"class":390},"  acc",[147,3551,3552],{"class":233},"[",[147,3554,3555],{"class":390},"f",[147,3557,3558],{"class":233},"] ",[147,3560,3561],{"class":153},"=",[147,3563,3564],{"class":233}," (",[147,3566,3502],{"class":390},[147,3568,3552],{"class":233},[147,3570,3555],{"class":390},[147,3572,3558],{"class":233},[147,3574,3575],{"class":153},"||",[147,3577,3578],{"class":197}," 0",[147,3580,3581],{"class":233},") ",[147,3583,47],{"class":153},[147,3585,3586],{"class":197}," 1",[147,3588,3544],{"class":153},[147,3590,3591,3595,3598],{"class":149,"line":216},[147,3592,3594],{"class":3593},"s7zQu","  return",[147,3596,3597],{"class":390}," acc",[147,3599,3544],{"class":153},[147,3601,3602,3605,3608,3611,3613,3616,3618,3620,3622,3624,3627,3629,3631,3634,3637,3639,3642,3645,3647,3650,3652,3655,3657,3660,3662,3665],{"class":149,"line":251},[147,3603,3604],{"class":153},"},",[147,3606,3607],{"class":153}," {}",[147,3609,3610],{"class":390},"))",[147,3612,100],{"class":153},[147,3614,3615],{"class":3460},"sort",[147,3617,3478],{"class":390},[147,3619,3478],{"class":153},[147,3621,24],{"class":3501},[147,3623,1050],{"class":153},[147,3625,3626],{"class":3501}," b",[147,3628,3510],{"class":153},[147,3630,3513],{"class":163},[147,3632,3633],{"class":390}," b[",[147,3635,3636],{"class":197},"1",[147,3638,3558],{"class":390},[147,3640,3641],{"class":153},"-",[147,3643,3644],{"class":390}," a[",[147,3646,3636],{"class":197},[147,3648,3649],{"class":390},"])",[147,3651,100],{"class":153},[147,3653,3654],{"class":3460},"slice",[147,3656,3478],{"class":390},[147,3658,3659],{"class":197},"0",[147,3661,1050],{"class":153},[147,3663,3664],{"class":197}," 5",[147,3666,3667],{"class":390},")\n",[12,3669,3670],{},"This tells you which stacks the cascade assigned where - the heading family vs the body family falls straight out of the counts. What it does not tell you: whether the first font in the stack ever loaded.",[30,3672,3674],{"id":3673},"signal-2-documentfonts-what-actually-loaded","Signal 2: document.fonts (what actually loaded)",[12,3676,3677],{},"The FontFace API is the receipt:",[138,3679,3681],{"className":3441,"code":3680,"language":3443,"meta":143,"style":143},"[...document.fonts].map(f => `${f.family} ${f.weight} - ${f.status}`)\n// e.g. [\"Sohne 400 - loaded\", \"Sohne 700 - loaded\", \"SohneMono 400 - unloaded\"]\n",[16,3682,3683,3750],{"__ignoreMap":143},[147,3684,3685,3687,3689,3691,3693,3696,3698,3701,3703,3705,3707,3710,3712,3714,3717,3720,3723,3725,3727,3730,3732,3735,3738,3740,3742,3745,3748],{"class":149,"line":150},[147,3686,3552],{"class":390},[147,3688,3467],{"class":153},[147,3690,3470],{"class":390},[147,3692,100],{"class":153},[147,3694,3695],{"class":390},"fonts]",[147,3697,100],{"class":153},[147,3699,3700],{"class":3460},"map",[147,3702,3478],{"class":390},[147,3704,3555],{"class":3501},[147,3706,3513],{"class":163},[147,3708,3709],{"class":153}," `${",[147,3711,3555],{"class":390},[147,3713,100],{"class":153},[147,3715,3716],{"class":390},"family",[147,3718,3719],{"class":153},"}",[147,3721,3722],{"class":153}," ${",[147,3724,3555],{"class":390},[147,3726,100],{"class":153},[147,3728,3729],{"class":390},"weight",[147,3731,3719],{"class":153},[147,3733,3734],{"class":208}," - ",[147,3736,3737],{"class":153},"${",[147,3739,3555],{"class":390},[147,3741,100],{"class":153},[147,3743,3744],{"class":390},"status",[147,3746,3747],{"class":153},"}`",[147,3749,3667],{"class":390},[147,3751,3752],{"class":149,"line":157},[147,3753,3754],{"class":422},"// e.g. [\"Sohne 400 - loaded\", \"Sohne 700 - loaded\", \"SohneMono 400 - unloaded\"]\n",[12,3756,3757,3758,3761],{},"Every registered webfont with its weight and load status. An ",[16,3759,3760],{},"unloaded"," entry means the CSS declared it but nothing on the page triggered the download - a font the site owns but this page does not use.",[30,3763,3765],{"id":3764},"signal-3-the-network-where-fonts-come-from","Signal 3: the network (where fonts come from)",[12,3767,3768,3769,3772,3773,3776],{},"Filter DevTools' Network tab by \"Font\" and you get provenance: ",[16,3770,3771],{},"fonts.gstatic.com"," means Google Fonts, ",[16,3774,3775],{},"use.typekit.net"," means Adobe, files off the site's own domain mean self-hosted (and usually a commercial license).",[30,3778,3780],{"id":3779},"all-three-from-python","All three from Python",[138,3782,3784],{"className":360,"code":3783,"language":362,"meta":143,"style":143},"pip install playwright && playwright install chromium\n",[16,3785,3786],{"__ignoreMap":143},[147,3787,3788,3791,3794,3797,3800,3802,3804],{"class":149,"line":150},[147,3789,3790],{"class":181},"pip",[147,3792,3793],{"class":208}," install",[147,3795,3796],{"class":208}," playwright",[147,3798,3799],{"class":153}," &&",[147,3801,3796],{"class":181},[147,3803,3793],{"class":208},[147,3805,3806],{"class":208}," chromium\n",[138,3808,3812],{"className":3809,"code":3810,"language":3811,"meta":143,"style":143},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","from playwright.sync_api import sync_playwright\n\nwith sync_playwright() as p:\n    browser = p.chromium.launch()\n    page = browser.new_page()\n    font_urls = []\n    page.on(\"response\", lambda r: font_urls.append(r.url)\n            if r.request.resource_type == \"font\" else None)\n    page.goto(\"https://stripe.com\", wait_until=\"networkidle\")\n    page.evaluate(\"document.fonts.ready\")\n\n    loaded = page.evaluate(\n        \"[...document.fonts].map(f => `${f.family} ${f.weight} ${f.status}`)\")\n    used = page.evaluate(\"\"\"\n      [...new Set([...document.querySelectorAll('h1,h2,h3,p,a,button')]\n        .map(el => getComputedStyle(el).fontFamily))]\n    \"\"\")\n    browser.close()\n\nprint(\"LOADED:\", loaded)\nprint(\"USED:\", used)\nprint(\"SERVED FROM:\", {u.split('/')[2] for u in font_urls})\n","python",[16,3813,3814,3819,3823,3828,3833,3838,3843,3848,3853,3858,3864,3869,3875,3881,3887,3893,3899,3905,3911,3916,3922,3928],{"__ignoreMap":143},[147,3815,3816],{"class":149,"line":150},[147,3817,3818],{},"from playwright.sync_api import sync_playwright\n",[147,3820,3821],{"class":149,"line":157},[147,3822,410],{"emptyLinePlaceholder":409},[147,3824,3825],{"class":149,"line":175},[147,3826,3827],{},"with sync_playwright() as p:\n",[147,3829,3830],{"class":149,"line":191},[147,3831,3832],{},"    browser = p.chromium.launch()\n",[147,3834,3835],{"class":149,"line":216},[147,3836,3837],{},"    page = browser.new_page()\n",[147,3839,3840],{"class":149,"line":251},[147,3841,3842],{},"    font_urls = []\n",[147,3844,3845],{"class":149,"line":257},[147,3846,3847],{},"    page.on(\"response\", lambda r: font_urls.append(r.url)\n",[147,3849,3850],{"class":149,"line":263},[147,3851,3852],{},"            if r.request.resource_type == \"font\" else None)\n",[147,3854,3855],{"class":149,"line":1101},[147,3856,3857],{},"    page.goto(\"https://stripe.com\", wait_until=\"networkidle\")\n",[147,3859,3861],{"class":149,"line":3860},10,[147,3862,3863],{},"    page.evaluate(\"document.fonts.ready\")\n",[147,3865,3867],{"class":149,"line":3866},11,[147,3868,410],{"emptyLinePlaceholder":409},[147,3870,3872],{"class":149,"line":3871},12,[147,3873,3874],{},"    loaded = page.evaluate(\n",[147,3876,3878],{"class":149,"line":3877},13,[147,3879,3880],{},"        \"[...document.fonts].map(f => `${f.family} ${f.weight} ${f.status}`)\")\n",[147,3882,3884],{"class":149,"line":3883},14,[147,3885,3886],{},"    used = page.evaluate(\"\"\"\n",[147,3888,3890],{"class":149,"line":3889},15,[147,3891,3892],{},"      [...new Set([...document.querySelectorAll('h1,h2,h3,p,a,button')]\n",[147,3894,3896],{"class":149,"line":3895},16,[147,3897,3898],{},"        .map(el => getComputedStyle(el).fontFamily))]\n",[147,3900,3902],{"class":149,"line":3901},17,[147,3903,3904],{},"    \"\"\")\n",[147,3906,3908],{"class":149,"line":3907},18,[147,3909,3910],{},"    browser.close()\n",[147,3912,3914],{"class":149,"line":3913},19,[147,3915,410],{"emptyLinePlaceholder":409},[147,3917,3919],{"class":149,"line":3918},20,[147,3920,3921],{},"print(\"LOADED:\", loaded)\n",[147,3923,3925],{"class":149,"line":3924},21,[147,3926,3927],{},"print(\"USED:\", used)\n",[147,3929,3931],{"class":149,"line":3930},22,[147,3932,3933],{},"print(\"SERVED FROM:\", {u.split('/')[2] for u in font_urls})\n",[12,3935,3936,3937,3940,3941,3944,3945,3948],{},"The ",[16,3938,3939],{},"document.fonts.ready"," await matters. Skip it and a ",[16,3942,3943],{},"font-display: swap"," site reports its fallback stack, because you measured during the swap window. This is the single most common reason font detection scripts return ",[16,3946,3947],{},"Arial"," for sites that very much do not use Arial.",[30,3950,3952],{"id":3951},"what-each-signal-can-tell-you","What each signal can tell you",[440,3954,3955,3971],{},[443,3956,3957],{},[446,3958,3959,3962,3965,3968],{},[449,3960,3961],{},"Question",[449,3963,3964],{},"Computed styles",[449,3966,3967],{},"document.fonts",[449,3969,3970],{},"Network",[461,3972,3973,3984,3995,4006,4017],{},[446,3974,3975,3978,3980,3982],{},[466,3976,3977],{},"Which family is used for headings vs body",[466,3979,1936],{},[466,3981,512],{},[466,3983,512],{},[446,3985,3986,3989,3991,3993],{},[466,3987,3988],{},"Did the webfont actually load",[466,3990,512],{},[466,3992,1936],{},[466,3994,1936],{},[446,3996,3997,4000,4002,4004],{},[466,3998,3999],{},"Weights the site ships",[466,4001,2296],{},[466,4003,1936],{},[466,4005,1936],{},[446,4007,4008,4011,4013,4015],{},[466,4009,4010],{},"Google Fonts vs self-hosted vs Adobe",[466,4012,512],{},[466,4014,512],{},[466,4016,1936],{},[446,4018,4019,4022,4024,4026],{},[466,4020,4021],{},"Works without executing the page",[466,4023,512],{},[466,4025,512],{},[466,4027,512],{},[30,4029,4031],{"id":4030},"or-use-one-api-call","Or use one API call",[12,4033,4034,4035,4038],{},"Our fonts endpoint reads the declared font sources from the page's HTML and CSS - ",[16,4036,4037],{},"@font-face"," rules, Google Fonts links, preloads - and returns families, weights, and where they are served from:",[138,4040,4042],{"className":360,"code":4041,"language":362,"meta":143,"style":143},"curl \"https://miromiro.app/api/v1/fonts?url=stripe.com\" \\\n  -H \"Authorization: Bearer $MIROMIRO_API_KEY\"\n",[16,4043,4044,4057],{"__ignoreMap":143},[147,4045,4046,4048,4050,4053,4055],{"class":149,"line":150},[147,4047,615],{"class":181},[147,4049,205],{"class":153},[147,4051,4052],{"class":208},"https://miromiro.app/api/v1/fonts?url=stripe.com",[147,4054,167],{"class":153},[147,4056,391],{"class":390},[147,4058,4059,4061,4063,4065,4067],{"class":149,"line":157},[147,4060,629],{"class":208},[147,4062,205],{"class":153},[147,4064,634],{"class":208},[147,4066,637],{"class":390},[147,4068,404],{"class":153},[12,4070,4071,4072,4074,4075,4079,4080,4082],{},"For the full typographic token set (sizes, weights, line heights, letter spacing alongside the families), use ",[24,4073,1394],{"href":1393}," instead. There is also a ",[24,4076,4078],{"href":4077},"/api/tools/font-extractor","free font extractor"," if you want to check one site right now with no key. ",[24,4081,456],{"href":574},": 100 credits a month, no card.",[30,4084,4086],{"id":4085},"summary","Summary",[12,4088,4089,4091,4092,4094,4095,4097],{},[16,4090,2912],{}," declares intent; ",[16,4093,3967],{}," reports reality; the network names the source. Cross-reference at least the first two, always after ",[16,4096,3939],{},", and be suspicious of any script that confidently reports a system font on a design-forward site - it probably measured mid-swap.",[30,4099,650],{"id":649},[652,4101,4103],{"id":4102},"how-do-i-find-a-websites-font-without-an-extension","How do I find a website's font without an extension?",[12,4105,4106],{},"DevTools → Computed tab → rendered fonts panel. It names what painted, not what was requested.",[652,4108,4110],{"id":4109},"why-does-font-family-show-a-font-that-isnt-rendering","Why does font-family show a font that isn't rendering?",[12,4112,4113,4114,4116],{},"Because it is a fallback list. With ",[16,4115,3943],{},", the fallback can render and stay whenever the webfont is slow or never loads.",[652,4118,4120],{"id":4119},"how-do-i-detect-fonts-from-a-script","How do I detect fonts from a script?",[12,4122,4123,4124,4126,4127,4129,4130,4132],{},"Playwright, await ",[16,4125,3939],{},", then read ",[16,4128,3967],{}," and computed ",[16,4131,3541],{}," values together (code above).",[652,4134,4136],{"id":4135},"can-i-use-a-font-i-found-on-a-website","Can I use a font I found on a website?",[12,4138,4139],{},"Identifying is fine, using needs a license. Google Fonts yes; a site's self-hosted commercial webfonts are licensed to that site, not to you.",[688,4141,4142],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}",{"title":143,"searchDepth":157,"depth":157,"links":4144},[4145,4146,4147,4148,4149,4150,4151,4152],{"id":3437,"depth":157,"text":3438},{"id":3673,"depth":157,"text":3674},{"id":3764,"depth":157,"text":3765},{"id":3779,"depth":157,"text":3780},{"id":3951,"depth":157,"text":3952},{"id":4030,"depth":157,"text":4031},{"id":4085,"depth":157,"text":4086},{"id":649,"depth":157,"text":650,"children":4153},[4154,4155,4156,4157],{"id":4102,"depth":175,"text":4103},{"id":4109,"depth":175,"text":4110},{"id":4119,"depth":175,"text":4120},{"id":4135,"depth":175,"text":4136},"Tutorials","Three ways to detect which fonts a website really uses - computed styles, document.fonts, and network sniffing - and why font-family lies to you.",[4161,4164,4167,4170],{"question":4162,"answer":4163},"How do I find out what font a website uses without an extension?","Open DevTools, select the element, and look at the Computed tab's rendered fonts section at the bottom - it names the font actually painted, not just the CSS stack. Or run document.fonts in the console to list every FontFace the page registered and whether each loaded.",{"question":4165,"answer":4166},"Why does font-family show a font that isn't actually rendering?","font-family is a wish list, not a receipt. The browser walks the stack until something is available; with font-display: swap a fallback renders first and stays if the webfont never finishes loading. Only the rendered-fonts panel or document.fonts tells you what really painted.",{"question":4168,"answer":4169},"How can I detect fonts from a script instead of DevTools?","Load the page with Playwright, await document.fonts.ready, then read both document.fonts (registered FontFaces with load status) and getComputedStyle fontFamily values across elements. Cross-referencing the two separates fonts that loaded from fonts that were merely requested.",{"question":4171,"answer":4172},"Can I legally use a font I found on a website?","Identifying a font is fine; using it needs a license. Google Fonts are open, but commercial webfonts (the files served from the site's own domain or a foundry CDN) are licensed to that site, not to you. Buy or substitute.",{},"/api-blog/how-to-detect-fonts-a-website-uses",[4176,729,1260],"how-to-extract-website-color-palette-programmatically",{"title":3419,"description":4159},"api-blog/how-to-detect-fonts-a-website-uses",[4180,4181,4182,3811,4183],"fonts","font detection","javascript","typography","j1PMCGDTmZ62w1LGv7I0rP5_XmM7utcCv420iy6QF7Q",{"id":4186,"title":4187,"author":7,"body":4188,"category":4158,"date":1242,"dateModified":710,"description":4618,"extension":712,"faqs":4619,"image":710,"meta":4631,"navigation":409,"path":4632,"readingTime":257,"relatedSlugs":4633,"seo":4634,"stem":4635,"tags":4636,"__hash__":4641},"apiBlog/api-blog/how-to-extract-brand-assets-in-bulk.md","How to Extract Brand Assets from 1,000 URLs in Bulk (Python)",{"type":9,"value":4189,"toc":4605},[4190,4193,4197,4207,4211,4225,4456,4466,4470,4489,4495,4505,4509,4558,4561,4563,4569,4571,4575,4578,4582,4585,4589,4592,4596,4602],[12,4191,4192],{},"The person with 1,000 URLs is never the person who wants to babysit 1,000 browser tabs. Bulk brand extraction in Python is an asyncio problem wearing a design-data hat: a semaphore for rate limits, retries for the 10% of URLs that misbehave, and checkpointing so a crash at URL 900 does not cost you the first 899. Here is the working script, then the failure math.",[30,4194,4196],{"id":4195},"why-not-1000-playwright-launches","Why not 1,000 Playwright launches",[12,4198,3936,4199,4202,4203,4206],{},[24,4200,4201],{"href":2142},"single-site Playwright approach"," does not scale linearly. A thousand headless Chromium sessions means gigabytes of RAM, cookie banners in six languages, and a long tail of sites that hang ",[16,4204,4205],{},"networkidle"," forever. It is buildable - a browser pool, per-site timeouts, crash recovery - but at that point you are maintaining scraping infrastructure as a hobby. For bulk, put the browser problem on someone else's side of the API.",[30,4208,4210],{"id":4209},"the-script","The script",[138,4212,4214],{"className":360,"code":4213,"language":362,"meta":143,"style":143},"pip install httpx\n",[16,4215,4216],{"__ignoreMap":143},[147,4217,4218,4220,4222],{"class":149,"line":150},[147,4219,3790],{"class":181},[147,4221,3793],{"class":208},[147,4223,4224],{"class":208}," httpx\n",[138,4226,4228],{"className":3809,"code":4227,"language":3811,"meta":143,"style":143},"import asyncio, csv, json\nimport httpx\n\nAPI = \"https://miromiro.app/api/v1/brand\"\nKEY = \"mm_live_your_key\"\nCONCURRENCY = 5          # stay under your plan's req/min cap\nRETRIES = 3\n\nasync def extract(client, sem, url):\n    async with sem:\n        for attempt in range(RETRIES):\n            try:\n                r = await client.get(\n                    API,\n                    params={\"url\": url, \"fields\": \"logo,palette\"},\n                    headers={\"Authorization\": f\"Bearer {KEY}\"},\n                    timeout=60,\n                )\n                if r.status_code == 429:            # rate limited: back off\n                    await asyncio.sleep(2 ** attempt * 5)\n                    continue\n                if r.status_code == 200:\n                    return {\"url\": url, \"ok\": True, \"data\": r.json()}\n                return {\"url\": url, \"ok\": False, \"error\": f\"HTTP {r.status_code}\"}\n            except httpx.HTTPError as e:\n                if attempt == RETRIES - 1:\n                    return {\"url\": url, \"ok\": False, \"error\": str(e)}\n                await asyncio.sleep(2 ** attempt)\n\nasync def main(urls):\n    sem = asyncio.Semaphore(CONCURRENCY)\n    async with httpx.AsyncClient() as client:\n        results = await asyncio.gather(*(extract(client, sem, u) for u in urls))\n    with open(\"brands.jsonl\", \"w\") as f:\n        for row in results:\n            f.write(json.dumps(row) + \"\\n\")\n    failed = [r for r in results if not r[\"ok\"]]\n    print(f\"{len(results) - len(failed)} ok, {len(failed)} failed\")\n\nif __name__ == \"__main__\":\n    urls = [row[0] for row in csv.reader(open(\"domains.csv\"))]\n    asyncio.run(main(urls))\n",[16,4229,4230,4235,4240,4244,4249,4254,4259,4264,4268,4273,4278,4283,4288,4293,4298,4303,4308,4313,4318,4323,4328,4333,4338,4344,4350,4356,4362,4368,4374,4379,4385,4391,4397,4403,4409,4415,4421,4427,4433,4438,4444,4450],{"__ignoreMap":143},[147,4231,4232],{"class":149,"line":150},[147,4233,4234],{},"import asyncio, csv, json\n",[147,4236,4237],{"class":149,"line":157},[147,4238,4239],{},"import httpx\n",[147,4241,4242],{"class":149,"line":175},[147,4243,410],{"emptyLinePlaceholder":409},[147,4245,4246],{"class":149,"line":191},[147,4247,4248],{},"API = \"https://miromiro.app/api/v1/brand\"\n",[147,4250,4251],{"class":149,"line":216},[147,4252,4253],{},"KEY = \"mm_live_your_key\"\n",[147,4255,4256],{"class":149,"line":251},[147,4257,4258],{},"CONCURRENCY = 5          # stay under your plan's req/min cap\n",[147,4260,4261],{"class":149,"line":257},[147,4262,4263],{},"RETRIES = 3\n",[147,4265,4266],{"class":149,"line":263},[147,4267,410],{"emptyLinePlaceholder":409},[147,4269,4270],{"class":149,"line":1101},[147,4271,4272],{},"async def extract(client, sem, url):\n",[147,4274,4275],{"class":149,"line":3860},[147,4276,4277],{},"    async with sem:\n",[147,4279,4280],{"class":149,"line":3866},[147,4281,4282],{},"        for attempt in range(RETRIES):\n",[147,4284,4285],{"class":149,"line":3871},[147,4286,4287],{},"            try:\n",[147,4289,4290],{"class":149,"line":3877},[147,4291,4292],{},"                r = await client.get(\n",[147,4294,4295],{"class":149,"line":3883},[147,4296,4297],{},"                    API,\n",[147,4299,4300],{"class":149,"line":3889},[147,4301,4302],{},"                    params={\"url\": url, \"fields\": \"logo,palette\"},\n",[147,4304,4305],{"class":149,"line":3895},[147,4306,4307],{},"                    headers={\"Authorization\": f\"Bearer {KEY}\"},\n",[147,4309,4310],{"class":149,"line":3901},[147,4311,4312],{},"                    timeout=60,\n",[147,4314,4315],{"class":149,"line":3907},[147,4316,4317],{},"                )\n",[147,4319,4320],{"class":149,"line":3913},[147,4321,4322],{},"                if r.status_code == 429:            # rate limited: back off\n",[147,4324,4325],{"class":149,"line":3918},[147,4326,4327],{},"                    await asyncio.sleep(2 ** attempt * 5)\n",[147,4329,4330],{"class":149,"line":3924},[147,4331,4332],{},"                    continue\n",[147,4334,4335],{"class":149,"line":3930},[147,4336,4337],{},"                if r.status_code == 200:\n",[147,4339,4341],{"class":149,"line":4340},23,[147,4342,4343],{},"                    return {\"url\": url, \"ok\": True, \"data\": r.json()}\n",[147,4345,4347],{"class":149,"line":4346},24,[147,4348,4349],{},"                return {\"url\": url, \"ok\": False, \"error\": f\"HTTP {r.status_code}\"}\n",[147,4351,4353],{"class":149,"line":4352},25,[147,4354,4355],{},"            except httpx.HTTPError as e:\n",[147,4357,4359],{"class":149,"line":4358},26,[147,4360,4361],{},"                if attempt == RETRIES - 1:\n",[147,4363,4365],{"class":149,"line":4364},27,[147,4366,4367],{},"                    return {\"url\": url, \"ok\": False, \"error\": str(e)}\n",[147,4369,4371],{"class":149,"line":4370},28,[147,4372,4373],{},"                await asyncio.sleep(2 ** attempt)\n",[147,4375,4377],{"class":149,"line":4376},29,[147,4378,410],{"emptyLinePlaceholder":409},[147,4380,4382],{"class":149,"line":4381},30,[147,4383,4384],{},"async def main(urls):\n",[147,4386,4388],{"class":149,"line":4387},31,[147,4389,4390],{},"    sem = asyncio.Semaphore(CONCURRENCY)\n",[147,4392,4394],{"class":149,"line":4393},32,[147,4395,4396],{},"    async with httpx.AsyncClient() as client:\n",[147,4398,4400],{"class":149,"line":4399},33,[147,4401,4402],{},"        results = await asyncio.gather(*(extract(client, sem, u) for u in urls))\n",[147,4404,4406],{"class":149,"line":4405},34,[147,4407,4408],{},"    with open(\"brands.jsonl\", \"w\") as f:\n",[147,4410,4412],{"class":149,"line":4411},35,[147,4413,4414],{},"        for row in results:\n",[147,4416,4418],{"class":149,"line":4417},36,[147,4419,4420],{},"            f.write(json.dumps(row) + \"\\n\")\n",[147,4422,4424],{"class":149,"line":4423},37,[147,4425,4426],{},"    failed = [r for r in results if not r[\"ok\"]]\n",[147,4428,4430],{"class":149,"line":4429},38,[147,4431,4432],{},"    print(f\"{len(results) - len(failed)} ok, {len(failed)} failed\")\n",[147,4434,4436],{"class":149,"line":4435},39,[147,4437,410],{"emptyLinePlaceholder":409},[147,4439,4441],{"class":149,"line":4440},40,[147,4442,4443],{},"if __name__ == \"__main__\":\n",[147,4445,4447],{"class":149,"line":4446},41,[147,4448,4449],{},"    urls = [row[0] for row in csv.reader(open(\"domains.csv\"))]\n",[147,4451,4453],{"class":149,"line":4452},42,[147,4454,4455],{},"    asyncio.run(main(urls))\n",[12,4457,4458,4459,4461,4462,4465],{},"Swap the endpoint for ",[16,4460,1394],{}," if you want full token sets instead of brand data, and adjust ",[16,4463,4464],{},"fields="," to trim the response to what you store - it does not change the credit cost, but it makes the output files dramatically smaller.",[30,4467,4469],{"id":4468},"the-three-things-that-make-bulk-runs-survive","The three things that make bulk runs survive",[12,4471,4472,4475,4476,4480,4481,4484,4485,4488],{},[45,4473,4474],{},"1. The semaphore is the contract."," Your plan has a requests-per-minute cap (the ladder is in the ",[24,4477,4479],{"href":4478},"/api/docs/rate-limits","rate limits docs","). Five concurrent with backoff on 429 stays comfortably inside the entry tiers; raise it as your plan allows. Blasting unbounded ",[16,4482,4483],{},"gather"," at any API gets you rate-limited into taking ",[1442,4486,4487],{},"longer"," than the polite version.",[12,4490,4491,4494],{},[45,4492,4493],{},"2. Expect 5-15% failures and capture them."," On a real 1,000-domain list some are dead, parked, or behind bot walls. The script writes per-URL errors instead of dying, so the run always finishes and the failures are a re-runnable list, not a mystery.",[12,4496,4497,4500,4501,4504],{},[45,4498,4499],{},"3. Re-runs are cheap because of caching."," Responses are cached for 24 hours and a cache hit costs 0 credits (",[16,4502,4503],{},"\"cached\": true"," in the usage object). Re-running today's failed batch only pays for URLs that actually re-extract - so retry freely.",[30,4506,4508],{"id":4507},"budgeting-a-1000-url-run","Budgeting a 1,000-URL run",[440,4510,4511,4523],{},[443,4512,4513],{},[446,4514,4515,4517,4520],{},[449,4516],{},[449,4518,4519],{},"Brand data (15 cr/URL)",[449,4521,4522],{},"Design tokens (10 cr/URL)",[461,4524,4525,4536,4547],{},[446,4526,4527,4530,4533],{},[466,4528,4529],{},"100 URLs",[466,4531,4532],{},"1,500 credits",[466,4534,4535],{},"1,000 credits",[446,4537,4538,4541,4544],{},[466,4539,4540],{},"1,000 URLs",[466,4542,4543],{},"15,000 credits",[466,4545,4546],{},"10,000 credits",[446,4548,4549,4552,4555],{},[466,4550,4551],{},"Fits in",[466,4553,4554],{},"Growth (€79/mo, 30k)",[466,4556,4557],{},"Growth, or 2× Developer months",[12,4559,4560],{},"The free 100 credits cover a 6-10 URL pilot - do that first, on your ugliest URLs, before committing a plan to the full list. Wall-clock: the rate limiter sets the pace, not the extraction; expect tens of minutes for 1,000 URLs, not hours.",[30,4562,4086],{"id":4085},[12,4564,4565,4566,4568],{},"Bulk extraction is a queue-discipline problem: bounded concurrency, backoff on 429, per-URL error capture, and a re-run list. The 24-hour cache turns retries from a cost into a free operation. Pilot on 10 URLs with a ",[24,4567,27],{"href":574},", then size the plan to the list.",[30,4570,650],{"id":649},[652,4572,4574],{"id":4573},"how-do-i-scrape-brand-colors-from-a-list-of-websites-in-python","How do I scrape brand colors from a list of websites in Python?",[12,4576,4577],{},"For a few sites, Playwright and computed styles. For hundreds or thousands, the async httpx script above against an extraction API.",[652,4579,4581],{"id":4580},"how-long-does-1000-urls-take","How long does 1,000 URLs take?",[12,4583,4584],{},"Tens of minutes - your requests-per-minute cap is the bottleneck, not the extraction itself.",[652,4586,4588],{"id":4587},"what-failure-rate-should-i-expect","What failure rate should I expect?",[12,4590,4591],{},"5-15% on real-world lists: dead domains, parked pages, bot walls. Capture and re-run rather than aiming for zero.",[652,4593,4595],{"id":4594},"do-repeated-extractions-cost-credits-again","Do repeated extractions cost credits again?",[12,4597,4598,4599,100],{},"Not within 24 hours - cache hits cost 0 credits and report ",[16,4600,4601],{},"cached: true",[688,4603,4604],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":143,"searchDepth":157,"depth":157,"links":4606},[4607,4608,4609,4610,4611,4612],{"id":4195,"depth":157,"text":4196},{"id":4209,"depth":157,"text":4210},{"id":4468,"depth":157,"text":4469},{"id":4507,"depth":157,"text":4508},{"id":4085,"depth":157,"text":4086},{"id":649,"depth":157,"text":650,"children":4613},[4614,4615,4616,4617],{"id":4573,"depth":175,"text":4574},{"id":4580,"depth":175,"text":4581},{"id":4587,"depth":175,"text":4588},{"id":4594,"depth":175,"text":4595},"Bulk-extract brand data and design tokens from thousands of domains in Python - async code with rate limiting and retries that survives a real URL list.",[4620,4622,4625,4628],{"question":4574,"answer":4621},"For a handful of sites, a Playwright script reading computed styles works. Past a few hundred URLs, run the extraction through an API with asyncio and httpx: a semaphore to respect rate limits, retries with backoff for the URLs that fail, and checkpointing so a crash does not restart the whole run.",{"question":4623,"answer":4624},"How long does it take to extract data from 1,000 URLs?","With an API at a typical plan rate limit, roughly 20-60 minutes for 1,000 URLs depending on your requests-per-minute cap - the limiter, not the extraction, sets the pace. A self-hosted browser farm is usually slower per URL once retries and crashes are counted.",{"question":4626,"answer":4627},"What percentage of URLs fail in a bulk extraction run?","Plan for 5-15% on a real-world list: dead domains, redirects to parked pages, bot walls, timeouts. This is why retries and per-URL error capture matter more than raw speed - a bulk script that stops on the first 404 never finishes.",{"question":4629,"answer":4630},"Do repeated extractions of the same URL cost credits again?","On MiroMiro, responses are cached for 24 hours and a cache hit costs 0 credits - the usage object reports cached: true. Re-running a failed batch the same day only pays for the URLs that actually re-extract.",{},"/api-blog/how-to-extract-brand-assets-in-bulk",[2802,729,728],{"title":4187,"description":4618},"api-blog/how-to-extract-brand-assets-in-bulk",[4637,3811,4638,4639,4640],"bulk extraction","async","brand data","automation","s_wWV-k0k55QCGaVhQYXcQuGKhALy3iIWTRlMgsjUVM",{"id":4643,"title":2143,"author":7,"body":4644,"category":4158,"date":1242,"dateModified":710,"description":5273,"extension":712,"faqs":5274,"image":710,"meta":5284,"navigation":409,"path":5285,"readingTime":263,"relatedSlugs":5286,"seo":5287,"stem":5288,"tags":5289,"__hash__":5291},"apiBlog/api-blog/how-to-extract-design-tokens-from-any-website-in-python.md",{"type":9,"value":4645,"toc":5257},[4646,4653,4656,4660,4663,4686,4689,4848,4851,4859,4873,4877,4880,4918,4921,4925,4928,4963,4965,4968,5026,5030,5109,5115,5117,5120,5148,5170,5178,5180,5190,5192,5196,5199,5203,5215,5219,5222,5226,5229,5233,5254],[12,4647,4648,4649,4652],{},"The fastest way to extract design tokens from a website in Python is Playwright plus ",[16,4650,4651],{},"getComputedStyle()",": load the page in a real browser, collect the computed styles of every element, and count what repeats. The counting is the important part. A site's design system is whatever it uses everywhere, not every value that appears once in a forgotten footer.",[12,4654,4655],{},"Parsing CSS files without a browser is the approach most tutorials suggest, and it is the one that works worst on modern sites. More on that below.",[30,4657,4659],{"id":4658},"extract-colors-with-playwright","Extract colors with Playwright",[12,4661,4662],{},"First, install Playwright and a browser:",[138,4664,4666],{"className":360,"code":4665,"language":362,"meta":143,"style":143},"pip install playwright\nplaywright install chromium\n",[16,4667,4668,4677],{"__ignoreMap":143},[147,4669,4670,4672,4674],{"class":149,"line":150},[147,4671,3790],{"class":181},[147,4673,3793],{"class":208},[147,4675,4676],{"class":208}," playwright\n",[147,4678,4679,4682,4684],{"class":149,"line":157},[147,4680,4681],{"class":181},"playwright",[147,4683,3793],{"class":208},[147,4685,3806],{"class":208},[12,4687,4688],{},"Then collect styles from every element and count them:",[138,4690,4692],{"className":3809,"code":4691,"language":3811,"meta":143,"style":143},"from collections import Counter\nfrom playwright.sync_api import sync_playwright\n\nCOLLECT = \"\"\"\n() => {\n  const els = [...document.querySelectorAll('body *')].slice(0, 5000);\n  const out = { colors: [], fonts: [], sizes: [], radii: [], shadows: [] };\n  for (const el of els) {\n    const s = getComputedStyle(el);\n    out.colors.push(s.color, s.backgroundColor, s.borderTopColor);\n    out.fonts.push(s.fontFamily);\n    out.sizes.push(s.fontSize);\n    out.radii.push(s.borderTopLeftRadius);\n    out.shadows.push(s.boxShadow);\n  }\n  return out;\n}\n\"\"\"\n\ndef top(values, n=8, skip=(\"\", \"none\", \"normal\", \"auto\", \"0px\", \"rgba(0, 0, 0, 0)\")):\n    return Counter(v for v in values if v not in skip).most_common(n)\n\nwith sync_playwright() as p:\n    browser = p.chromium.launch()\n    page = browser.new_page(viewport={\"width\": 1440, \"height\": 900})\n    page.goto(\"https://stripe.com\", wait_until=\"networkidle\")\n    raw = page.evaluate(COLLECT)\n    browser.close()\n\nfor name in (\"colors\", \"fonts\", \"sizes\", \"radii\", \"shadows\"):\n    print(f\"\\n== {name} ==\")\n    for value, count in top(raw[name]):\n        print(f\"{count:>5}  {value}\")\n",[16,4693,4694,4699,4703,4707,4712,4717,4722,4727,4732,4737,4742,4747,4752,4757,4762,4766,4771,4775,4780,4784,4789,4794,4798,4802,4806,4811,4815,4820,4824,4828,4833,4838,4843],{"__ignoreMap":143},[147,4695,4696],{"class":149,"line":150},[147,4697,4698],{},"from collections import Counter\n",[147,4700,4701],{"class":149,"line":157},[147,4702,3818],{},[147,4704,4705],{"class":149,"line":175},[147,4706,410],{"emptyLinePlaceholder":409},[147,4708,4709],{"class":149,"line":191},[147,4710,4711],{},"COLLECT = \"\"\"\n",[147,4713,4714],{"class":149,"line":216},[147,4715,4716],{},"() => {\n",[147,4718,4719],{"class":149,"line":251},[147,4720,4721],{},"  const els = [...document.querySelectorAll('body *')].slice(0, 5000);\n",[147,4723,4724],{"class":149,"line":257},[147,4725,4726],{},"  const out = { colors: [], fonts: [], sizes: [], radii: [], shadows: [] };\n",[147,4728,4729],{"class":149,"line":263},[147,4730,4731],{},"  for (const el of els) {\n",[147,4733,4734],{"class":149,"line":1101},[147,4735,4736],{},"    const s = getComputedStyle(el);\n",[147,4738,4739],{"class":149,"line":3860},[147,4740,4741],{},"    out.colors.push(s.color, s.backgroundColor, s.borderTopColor);\n",[147,4743,4744],{"class":149,"line":3866},[147,4745,4746],{},"    out.fonts.push(s.fontFamily);\n",[147,4748,4749],{"class":149,"line":3871},[147,4750,4751],{},"    out.sizes.push(s.fontSize);\n",[147,4753,4754],{"class":149,"line":3877},[147,4755,4756],{},"    out.radii.push(s.borderTopLeftRadius);\n",[147,4758,4759],{"class":149,"line":3883},[147,4760,4761],{},"    out.shadows.push(s.boxShadow);\n",[147,4763,4764],{"class":149,"line":3889},[147,4765,260],{},[147,4767,4768],{"class":149,"line":3895},[147,4769,4770],{},"  return out;\n",[147,4772,4773],{"class":149,"line":3901},[147,4774,266],{},[147,4776,4777],{"class":149,"line":3907},[147,4778,4779],{},"\"\"\"\n",[147,4781,4782],{"class":149,"line":3913},[147,4783,410],{"emptyLinePlaceholder":409},[147,4785,4786],{"class":149,"line":3918},[147,4787,4788],{},"def top(values, n=8, skip=(\"\", \"none\", \"normal\", \"auto\", \"0px\", \"rgba(0, 0, 0, 0)\")):\n",[147,4790,4791],{"class":149,"line":3924},[147,4792,4793],{},"    return Counter(v for v in values if v not in skip).most_common(n)\n",[147,4795,4796],{"class":149,"line":3930},[147,4797,410],{"emptyLinePlaceholder":409},[147,4799,4800],{"class":149,"line":4340},[147,4801,3827],{},[147,4803,4804],{"class":149,"line":4346},[147,4805,3832],{},[147,4807,4808],{"class":149,"line":4352},[147,4809,4810],{},"    page = browser.new_page(viewport={\"width\": 1440, \"height\": 900})\n",[147,4812,4813],{"class":149,"line":4358},[147,4814,3857],{},[147,4816,4817],{"class":149,"line":4364},[147,4818,4819],{},"    raw = page.evaluate(COLLECT)\n",[147,4821,4822],{"class":149,"line":4370},[147,4823,3910],{},[147,4825,4826],{"class":149,"line":4376},[147,4827,410],{"emptyLinePlaceholder":409},[147,4829,4830],{"class":149,"line":4381},[147,4831,4832],{},"for name in (\"colors\", \"fonts\", \"sizes\", \"radii\", \"shadows\"):\n",[147,4834,4835],{"class":149,"line":4387},[147,4836,4837],{},"    print(f\"\\n== {name} ==\")\n",[147,4839,4840],{"class":149,"line":4393},[147,4841,4842],{},"    for value, count in top(raw[name]):\n",[147,4844,4845],{"class":149,"line":4399},[147,4846,4847],{},"        print(f\"{count:>5}  {value}\")\n",[12,4849,4850],{},"Run it and you get something like:",[138,4852,4857],{"className":4853,"code":4855,"language":4856},[4854],"language-text","== colors ==\n 3021  rgb(255, 255, 255)\n 1204  rgb(26, 27, 37)\n  412  rgb(99, 91, 255)\n  118  rgb(66, 84, 102)\n","text",[16,4858,4855],{"__ignoreMap":143},[12,4860,4861,4862,4864,4865,4868,4869,4872],{},"That third line is the brand color, found by nothing smarter than counting. ",[16,4863,2138],{}," has already done the hard work for you: it resolved every CSS variable chain, every ",[16,4866,4867],{},"var(--a, var(--b))"," fallback, every cascade conflict, and turned Tailwind utility soup into plain ",[16,4870,4871],{},"rgb()"," values.",[30,4874,4876],{"id":4875},"fonts-and-the-type-scale","Fonts and the type scale",[12,4878,4879],{},"The same collection already has them. Font families come back as full stacks, so take the first entry:",[138,4881,4883],{"className":3809,"code":4882,"language":3811,"meta":143,"style":143},"families = Counter(f.split(\",\")[0].strip('\"\\' ') for f in raw[\"fonts\"])\nprint(families.most_common(5))\n\nsizes = sorted(\n    {float(s.replace(\"px\", \"\")) for s, c in top(raw[\"sizes\"], n=20)},\n)\nprint(sizes)  # e.g. [12.0, 14.0, 16.0, 18.0, 20.0, 24.0, 36.0, 56.0]\n",[16,4884,4885,4890,4895,4899,4904,4909,4913],{"__ignoreMap":143},[147,4886,4887],{"class":149,"line":150},[147,4888,4889],{},"families = Counter(f.split(\",\")[0].strip('\"\\' ') for f in raw[\"fonts\"])\n",[147,4891,4892],{"class":149,"line":157},[147,4893,4894],{},"print(families.most_common(5))\n",[147,4896,4897],{"class":149,"line":175},[147,4898,410],{"emptyLinePlaceholder":409},[147,4900,4901],{"class":149,"line":191},[147,4902,4903],{},"sizes = sorted(\n",[147,4905,4906],{"class":149,"line":216},[147,4907,4908],{},"    {float(s.replace(\"px\", \"\")) for s, c in top(raw[\"sizes\"], n=20)},\n",[147,4910,4911],{"class":149,"line":251},[147,4912,3667],{},[147,4914,4915],{"class":149,"line":257},[147,4916,4917],{},"print(sizes)  # e.g. [12.0, 14.0, 16.0, 18.0, 20.0, 24.0, 36.0, 56.0]\n",[12,4919,4920],{},"That sorted list is the site's type scale. If it has fifteen entries instead of seven, you are looking at a site without a design system, which is also useful to know.",[30,4922,4924],{"id":4923},"from-raw-values-to-actual-tokens","From raw values to actual tokens",[12,4926,4927],{},"Raw counts are not tokens yet. Three cleanup steps matter:",[38,4929,4930,4939,4951],{},[41,4931,4932,71,4935,4938],{},[45,4933,4934],{},"Drop the defaults.",[16,4936,4937],{},"rgb(0, 0, 0)"," near the top does not mean the brand is black. Browser resets and inherited defaults pollute every element, so check whether a value actually appears on visible, styled elements before promoting it.",[41,4940,4941,71,4944,1688,4947,4950],{},[45,4942,4943],{},"Merge near-duplicates.",[16,4945,4946],{},"rgb(99, 91, 255)",[16,4948,4949],{},"rgb(99, 92, 255)"," are the same token with a rounding disagreement. Snap channels to a small tolerance before counting, or you will split one token's count across five entries.",[41,4952,4953,4956,4957,4959,4960,4962],{},[45,4954,4955],{},"Name by role, not by value."," The most frequent saturated color on buttons and links is ",[16,4958,3228],{},". The dominant text color is ",[16,4961,4856],{},". Frequency plus element context gets you there; k-means clustering is overkill for this and I would skip it.",[30,4964,563],{"id":562},[12,4966,4967],{},"Real gotchas, in the order they will bite you:",[64,4969,4970,4980,4986,5000,5013],{},[41,4971,4972,4975,4976,4979],{},[45,4973,4974],{},"Your viewport is a filter."," Computed styles reflect the current viewport only. A ",[16,4977,4978],{},"@media (max-width: 640px)"," override is invisible at 1440px. Extract at two viewports (mobile and desktop) if you care about responsive tokens.",[41,4981,4982,4985],{},[45,4983,4984],{},"Cookie banners poison the palette."," Consent overlays and chat widgets bring their own colors and fonts, and they are often thousands of DOM nodes. Dismiss them or exclude their containers before collecting.",[41,4987,4988,71,4991,4993,4994,4996,4997,4999],{},[45,4989,4990],{},"Fonts can lie.",[16,4992,2912],{}," lists what CSS asked for, not what loaded. With ",[16,4995,3943],{}," the brand font may never arrive in a headless run. Await ",[16,4998,3939],{}," before collecting if exact loaded fonts matter.",[41,5001,5002,5008,5009,5012],{},[45,5003,5004,5005,100],{},"Gradients are not in ",[16,5006,5007],{},"backgroundColor"," They live in ",[16,5010,5011],{},"backgroundImage",". If a brand is gradient-heavy, collect that property too or you will report their hero as plain white.",[41,5014,5015,5018,5019,5021,5022,5025],{},[45,5016,5017],{},"Shadow DOM and iframes are invisible"," to ",[16,5020,3475],{},". Web-component-heavy sites need you to walk ",[16,5023,5024],{},"shadowRoot","s explicitly.",[30,5027,5029],{"id":5028},"diy-vs-parsing-css-vs-an-api","DIY vs parsing CSS vs an API",[440,5031,5032,5054],{},[443,5033,5034],{},[446,5035,5036,5039,5042,5045,5048,5051],{},[449,5037,5038],{},"Approach",[449,5040,5041],{},"Tailwind / utility CSS",[449,5043,5044],{},"Runtime CSS-in-JS",[449,5046,5047],{},"Resolves var() and cascade",[449,5049,5050],{},"Media queries",[449,5052,5053],{},"Maintenance",[461,5055,5056,5073,5091],{},[446,5057,5058,5061,5063,5065,5067,5070],{},[466,5059,5060],{},"Playwright + computed styles (this guide)",[466,5062,1936],{},[466,5064,1936],{},[466,5066,1936],{},[466,5068,5069],{},"Current viewport only",[466,5071,5072],{},"Browser infra is yours to run",[446,5074,5075,5078,5081,5083,5085,5088],{},[466,5076,5077],{},"Parse CSS files yourself (tinycss2, no browser)",[466,5079,5080],{},"Painful",[466,5082,512],{},[466,5084,512],{},[466,5086,5087],{},"Declared, but unused values included",[466,5089,5090],{},"Low, but wrong on modern sites",[446,5092,5093,5096,5098,5101,5103,5106],{},[466,5094,5095],{},"MiroMiro extract API",[466,5097,1936],{},[466,5099,5100],{},"No (static HTML + CSS, no JS execution)",[466,5102,1936],{},[466,5104,5105],{},"Yes, all declared breakpoints",[466,5107,5108],{},"None",[12,5110,5111,5112,5114],{},"Use the Playwright approach if you are extracting from a handful of sites, need runtime-injected styles, and want full control. Skip the hand-rolled CSS-parsing route: resolving the cascade, specificity, and nested ",[16,5113,1830],{}," fallbacks yourself is a project, not a script.",[30,5116,4031],{"id":4030},[12,5118,5119],{},"The dedupe, the ranking, the var() chains, the resets, and every declared breakpoint - that is what our extract endpoint does for a living:",[138,5121,5122],{"className":360,"code":1151,"language":362,"meta":143,"style":143},[16,5123,5124,5136],{"__ignoreMap":143},[147,5125,5126,5128,5130,5132,5134],{"class":149,"line":150},[147,5127,615],{"class":181},[147,5129,205],{"class":153},[147,5131,1162],{"class":208},[147,5133,167],{"class":153},[147,5135,391],{"class":390},[147,5137,5138,5140,5142,5144,5146],{"class":149,"line":157},[147,5139,629],{"class":208},[147,5141,205],{"class":153},[147,5143,634],{"class":208},[147,5145,637],{"class":390},[147,5147,404],{"class":153},[12,5149,5150,5151,2858,5153,2858,5155,2858,5157,2858,5159,2858,5161,2858,5163,5165,5166,5169],{},"The JSON response has ",[16,5152,3275],{},[16,5154,3281],{},[16,5156,3284],{},[16,5158,3287],{},[16,5160,3296],{},[16,5162,3299],{},[16,5164,3305],{},", and more, already deduped and ranked. Trim it with ",[16,5167,5168],{},"?fields=colors,spacing"," if you only need part of it.",[12,5171,5172,5173,5175,5176,100],{},"If your script from this guide breaks on some site's cookie banner at 2am, that is roughly the moment an API earns its keep. You can ",[24,5174,2366],{"href":574}," with 100 credits a month, no card, or try it without signing up in the ",[24,5177,1583],{"href":1582},[30,5179,4086],{"id":4085},[12,5181,5182,5183,5185,5186,5189],{},"Playwright plus ",[16,5184,2138],{}," plus a ",[16,5187,5188],{},"Counter"," is the honest way to extract design tokens in Python, and the frequency counting matters more than any clever clustering. Parse-the-CSS approaches are not worth your time on modern sites. I hope this saved you a debugging evening.",[30,5191,650],{"id":649},[652,5193,5195],{"id":5194},"can-i-extract-design-tokens-without-a-headless-browser","Can I extract design tokens without a headless browser?",[12,5197,5198],{},"You can parse the site's CSS with a library like tinycss2, but on modern sites this works badly: CSS-in-JS generates styles at runtime, Tailwind buries the palette in utility classes, and you cannot tell which declared values are actually used. Computed styles from a real browser are the ground truth.",[652,5200,5202],{"id":5201},"how-do-i-extract-tokens-from-a-tailwind-site-where-everything-is-utility-classes","How do I extract tokens from a Tailwind site where everything is utility classes?",[12,5204,5205,5206,5208,5209,5018,5211,5214],{},"Ignore the class names entirely and read computed styles. ",[16,5207,2138],{}," resolves ",[16,5210,2407],{},[16,5212,5213],{},"rgb(79, 70, 229)"," like any other CSS, so nothing in this guide changes.",[652,5216,5218],{"id":5217},"why-do-my-extracted-colors-include-values-the-site-never-visibly-uses","Why do my extracted colors include values the site never visibly uses?",[12,5220,5221],{},"Three usual suspects: browser defaults on elements you collected, cookie banners and chat widgets injecting their own palette, and transparent or inherited values you forgot to filter.",[652,5223,5225],{"id":5224},"how-many-pages-should-i-sample","How many pages should I sample?",[12,5227,5228],{},"One landing page is usually enough for brand color and typography. Add one product or app page for functional colors (success, error, info). More than three pages rarely changes the top tokens.",[30,5230,5232],{"id":5231},"related-reading","Related reading",[64,5234,5235,5242,5248],{},[41,5236,5237,5241],{},[24,5238,5240],{"href":5239},"/api/design-tokens-api","Design tokens API"," - the endpoint this post's shortcut uses",[41,5243,5244,5247],{},[24,5245,5246],{"href":1393},"Design tokens docs"," - full response shape and field list",[41,5249,5250,5253],{},[24,5251,5252],{"href":833},"Ground truth for AI agents"," - why agents need real tokens instead of guessing from screenshots",[688,5255,5256],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}",{"title":143,"searchDepth":157,"depth":157,"links":5258},[5259,5260,5261,5262,5263,5264,5265,5266,5272],{"id":4658,"depth":157,"text":4659},{"id":4875,"depth":157,"text":4876},{"id":4923,"depth":157,"text":4924},{"id":562,"depth":157,"text":563},{"id":5028,"depth":157,"text":5029},{"id":4030,"depth":157,"text":4031},{"id":4085,"depth":157,"text":4086},{"id":649,"depth":157,"text":650,"children":5267},[5268,5269,5270,5271],{"id":5194,"depth":175,"text":5195},{"id":5201,"depth":175,"text":5202},{"id":5217,"depth":175,"text":5218},{"id":5224,"depth":175,"text":5225},{"id":5231,"depth":157,"text":5232},"Extract colors, fonts, spacing, and shadows from any website with Playwright in Python - working code, the gotchas that bite, and the one-API-call shortcut.",[5275,5277,5279,5281],{"question":5195,"answer":5276},"You can parse the site's CSS files with a library like tinycss2, but on modern sites this works badly. CSS-in-JS generates styles at runtime, utility-class sites like Tailwind bury the palette in thousands of class rules, and you cannot tell which declared values are actually used. Computed styles from a real browser are the ground truth.",{"question":5202,"answer":5278},"Ignore the class names entirely and read computed styles. getComputedStyle resolves bg-indigo-600 to rgb(79, 70, 229) the same way it resolves any other CSS, so the frequency-counting approach in this guide works unchanged.",{"question":5218,"answer":5280},"Three usual suspects: browser default styles on elements you collected, cookie banners and chat widgets injecting their own palette, and transparent or inherited values you forgot to filter. Count frequency, filter defaults, and dismiss overlays before collecting.",{"question":5282,"answer":5283},"How many pages should I sample to get a reliable palette?","One page is usually enough for brand color and typography if it is the homepage or a landing page. Add one product or app page for the functional palette (success, error, info states). More than three pages rarely changes the top tokens.",{},"/api-blog/how-to-extract-design-tokens-from-any-website-in-python",[1653,1260,4176],{"title":2143,"description":5273},"api-blog/how-to-extract-design-tokens-from-any-website-in-python",[1265,3811,4681,3414,5290],"web extraction","L5qnHk0I6Xw2LZ4KpLx52C6CU18QlatcV45aNNbr8F0",{"id":5293,"title":5294,"author":7,"body":5295,"category":4158,"date":1242,"dateModified":710,"description":5916,"extension":712,"faqs":5917,"image":710,"meta":5929,"navigation":409,"path":5930,"readingTime":251,"relatedSlugs":5931,"seo":5932,"stem":5933,"tags":5934,"__hash__":5937},"apiBlog/api-blog/how-to-extract-website-color-palette-programmatically.md","How to Extract a Website's Color Palette Programmatically",{"type":9,"value":5296,"toc":5902},[5297,5307,5310,5314,5317,5341,5403,5406,5410,5413,5506,5512,5516,5519,5713,5716,5720,5814,5816,5846,5858,5860,5863,5865,5869,5874,5878,5881,5885,5888,5892,5899],[12,5298,5299,5300,5306],{},"There are two fundamentally different ways to extract a color palette from a website in code: quantize the pixels of a screenshot, or read the colors out of the CSS. They give different answers, and one of them is usually wrong for what you want. The short version: ",[45,5301,5302,5303,100],{},"pixel methods find what a page looks like; CSS methods find what the brand ",[1442,5304,5305],{},"is"," If you want brand colors, read the CSS.",[12,5308,5309],{},"Here are both, working, then the comparison.",[30,5311,5313],{"id":5312},"method-1-quantize-a-screenshot-fast-misleading","Method 1: quantize a screenshot (fast, misleading)",[12,5315,5316],{},"The classic approach - screenshot the page, run color quantization:",[138,5318,5320],{"className":360,"code":5319,"language":362,"meta":143,"style":143},"pip install playwright colorthief\nplaywright install chromium\n",[16,5321,5322,5333],{"__ignoreMap":143},[147,5323,5324,5326,5328,5330],{"class":149,"line":150},[147,5325,3790],{"class":181},[147,5327,3793],{"class":208},[147,5329,3796],{"class":208},[147,5331,5332],{"class":208}," colorthief\n",[147,5334,5335,5337,5339],{"class":149,"line":157},[147,5336,4681],{"class":181},[147,5338,3793],{"class":208},[147,5340,3806],{"class":208},[138,5342,5344],{"className":3809,"code":5343,"language":3811,"meta":143,"style":143},"from io import BytesIO\nfrom colorthief import ColorThief\nfrom playwright.sync_api import sync_playwright\n\nwith sync_playwright() as p:\n    browser = p.chromium.launch()\n    page = browser.new_page(viewport={\"width\": 1440, \"height\": 900})\n    page.goto(\"https://stripe.com\", wait_until=\"networkidle\")\n    shot = page.screenshot()\n    browser.close()\n\npalette = ColorThief(BytesIO(shot)).get_palette(color_count=6)\nprint(['#%02x%02x%02x' % c for c in palette])\n",[16,5345,5346,5351,5356,5360,5364,5368,5372,5376,5380,5385,5389,5393,5398],{"__ignoreMap":143},[147,5347,5348],{"class":149,"line":150},[147,5349,5350],{},"from io import BytesIO\n",[147,5352,5353],{"class":149,"line":157},[147,5354,5355],{},"from colorthief import ColorThief\n",[147,5357,5358],{"class":149,"line":175},[147,5359,3818],{},[147,5361,5362],{"class":149,"line":191},[147,5363,410],{"emptyLinePlaceholder":409},[147,5365,5366],{"class":149,"line":216},[147,5367,3827],{},[147,5369,5370],{"class":149,"line":251},[147,5371,3832],{},[147,5373,5374],{"class":149,"line":257},[147,5375,4810],{},[147,5377,5378],{"class":149,"line":263},[147,5379,3857],{},[147,5381,5382],{"class":149,"line":1101},[147,5383,5384],{},"    shot = page.screenshot()\n",[147,5386,5387],{"class":149,"line":3860},[147,5388,3910],{},[147,5390,5391],{"class":149,"line":3866},[147,5392,410],{"emptyLinePlaceholder":409},[147,5394,5395],{"class":149,"line":3871},[147,5396,5397],{},"palette = ColorThief(BytesIO(shot)).get_palette(color_count=6)\n",[147,5399,5400],{"class":149,"line":3877},[147,5401,5402],{},"print(['#%02x%02x%02x' % c for c in palette])\n",[12,5404,5405],{},"Works in ten lines. The problem is that a screenshot has no idea what is UI and what is content. A hero photo dominates the pixel math, anti-aliased text contributes hundreds of blend colors that exist nowhere in the stylesheet, and the accent color that defines the brand might be forty pixels of button. Use this method for artwork and photos; do not use it for brand palettes.",[30,5407,5409],{"id":5408},"method-2-read-the-css-the-right-answer","Method 2: read the CSS (the right answer)",[12,5411,5412],{},"Ask the browser what colors the stylesheet actually applies:",[138,5414,5416],{"className":3809,"code":5415,"language":3811,"meta":143,"style":143},"from collections import Counter\nfrom playwright.sync_api import sync_playwright\n\nCOLLECT = \"\"\"\n() => [...document.querySelectorAll('body *')].slice(0, 5000).flatMap(el => {\n  const s = getComputedStyle(el);\n  return [s.color, s.backgroundColor, s.borderTopColor];\n})\n\"\"\"\n\nwith sync_playwright() as p:\n    browser = p.chromium.launch()\n    page = browser.new_page()\n    page.goto(\"https://stripe.com\", wait_until=\"networkidle\")\n    values = page.evaluate(COLLECT)\n    browser.close()\n\nskip = {\"rgba(0, 0, 0, 0)\", \"rgb(0, 0, 0)\", \"rgb(255, 255, 255)\"}\nfor color, count in Counter(v for v in values if v not in skip).most_common(8):\n    print(f\"{count:>5}  {color}\")\n",[16,5417,5418,5422,5426,5430,5434,5439,5444,5449,5454,5458,5462,5466,5470,5474,5478,5483,5487,5491,5496,5501],{"__ignoreMap":143},[147,5419,5420],{"class":149,"line":150},[147,5421,4698],{},[147,5423,5424],{"class":149,"line":157},[147,5425,3818],{},[147,5427,5428],{"class":149,"line":175},[147,5429,410],{"emptyLinePlaceholder":409},[147,5431,5432],{"class":149,"line":191},[147,5433,4711],{},[147,5435,5436],{"class":149,"line":216},[147,5437,5438],{},"() => [...document.querySelectorAll('body *')].slice(0, 5000).flatMap(el => {\n",[147,5440,5441],{"class":149,"line":251},[147,5442,5443],{},"  const s = getComputedStyle(el);\n",[147,5445,5446],{"class":149,"line":257},[147,5447,5448],{},"  return [s.color, s.backgroundColor, s.borderTopColor];\n",[147,5450,5451],{"class":149,"line":263},[147,5452,5453],{},"})\n",[147,5455,5456],{"class":149,"line":1101},[147,5457,4779],{},[147,5459,5460],{"class":149,"line":3860},[147,5461,410],{"emptyLinePlaceholder":409},[147,5463,5464],{"class":149,"line":3866},[147,5465,3827],{},[147,5467,5468],{"class":149,"line":3871},[147,5469,3832],{},[147,5471,5472],{"class":149,"line":3877},[147,5473,3837],{},[147,5475,5476],{"class":149,"line":3883},[147,5477,3857],{},[147,5479,5480],{"class":149,"line":3889},[147,5481,5482],{},"    values = page.evaluate(COLLECT)\n",[147,5484,5485],{"class":149,"line":3895},[147,5486,3910],{},[147,5488,5489],{"class":149,"line":3901},[147,5490,410],{"emptyLinePlaceholder":409},[147,5492,5493],{"class":149,"line":3907},[147,5494,5495],{},"skip = {\"rgba(0, 0, 0, 0)\", \"rgb(0, 0, 0)\", \"rgb(255, 255, 255)\"}\n",[147,5497,5498],{"class":149,"line":3913},[147,5499,5500],{},"for color, count in Counter(v for v in values if v not in skip).most_common(8):\n",[147,5502,5503],{"class":149,"line":3918},[147,5504,5505],{},"    print(f\"{count:>5}  {color}\")\n",[12,5507,5508,5509,100],{},"Frequency ranking does the rest: the most-used saturated color is almost always the brand primary. For the full version with fonts, spacing, and the cleanup steps, see the ",[24,5510,5511],{"href":2142},"design tokens guide",[30,5513,5515],{"id":5514},"checking-one-site-by-hand-use-the-console","Checking one site by hand? Use the console",[12,5517,5518],{},"Paste this in DevTools on any page:",[138,5520,5522],{"className":3441,"code":5521,"language":3443,"meta":143,"style":143},"Object.entries([...document.querySelectorAll('body *')].reduce((acc, el) => {\n  const c = getComputedStyle(el).backgroundColor;\n  if (c !== 'rgba(0, 0, 0, 0)') acc[c] = (acc[c] || 0) + 1;\n  return acc;\n}, {})).sort((a, b) => b[1] - a[1]).slice(0, 8)\n",[16,5523,5524,5572,5595,5650,5658],{"__ignoreMap":143},[147,5525,5526,5528,5530,5532,5534,5536,5538,5540,5542,5544,5546,5548,5550,5552,5554,5556,5558,5560,5562,5564,5566,5568,5570],{"class":149,"line":150},[147,5527,3455],{"class":390},[147,5529,100],{"class":153},[147,5531,3461],{"class":3460},[147,5533,3464],{"class":390},[147,5535,3467],{"class":153},[147,5537,3470],{"class":390},[147,5539,100],{"class":153},[147,5541,3475],{"class":3460},[147,5543,3478],{"class":390},[147,5545,3481],{"class":153},[147,5547,3484],{"class":208},[147,5549,3481],{"class":153},[147,5551,3489],{"class":390},[147,5553,100],{"class":153},[147,5555,3494],{"class":3460},[147,5557,3478],{"class":390},[147,5559,3478],{"class":153},[147,5561,3502],{"class":3501},[147,5563,1050],{"class":153},[147,5565,3507],{"class":3501},[147,5567,3510],{"class":153},[147,5569,3513],{"class":163},[147,5571,172],{"class":153},[147,5573,5574,5576,5579,5581,5583,5585,5587,5589,5591,5593],{"class":149,"line":157},[147,5575,3520],{"class":163},[147,5577,5578],{"class":390}," c",[147,5580,3526],{"class":153},[147,5582,3529],{"class":3460},[147,5584,3478],{"class":233},[147,5586,3534],{"class":390},[147,5588,3510],{"class":233},[147,5590,100],{"class":153},[147,5592,5007],{"class":390},[147,5594,3544],{"class":153},[147,5596,5597,5600,5602,5605,5608,5611,5614,5616,5618,5620,5622,5624,5626,5628,5630,5632,5634,5636,5638,5640,5642,5644,5646,5648],{"class":149,"line":175},[147,5598,5599],{"class":3593},"  if",[147,5601,3564],{"class":233},[147,5603,5604],{"class":390},"c",[147,5606,5607],{"class":153}," !==",[147,5609,5610],{"class":153}," '",[147,5612,5613],{"class":208},"rgba(0, 0, 0, 0)",[147,5615,3481],{"class":153},[147,5617,3581],{"class":233},[147,5619,3502],{"class":390},[147,5621,3552],{"class":233},[147,5623,5604],{"class":390},[147,5625,3558],{"class":233},[147,5627,3561],{"class":153},[147,5629,3564],{"class":233},[147,5631,3502],{"class":390},[147,5633,3552],{"class":233},[147,5635,5604],{"class":390},[147,5637,3558],{"class":233},[147,5639,3575],{"class":153},[147,5641,3578],{"class":197},[147,5643,3581],{"class":233},[147,5645,47],{"class":153},[147,5647,3586],{"class":197},[147,5649,3544],{"class":153},[147,5651,5652,5654,5656],{"class":149,"line":191},[147,5653,3594],{"class":3593},[147,5655,3597],{"class":390},[147,5657,3544],{"class":153},[147,5659,5660,5662,5664,5666,5668,5670,5672,5674,5676,5678,5680,5682,5684,5686,5688,5690,5692,5694,5696,5698,5700,5702,5704,5706,5708,5711],{"class":149,"line":216},[147,5661,3604],{"class":153},[147,5663,3607],{"class":153},[147,5665,3610],{"class":390},[147,5667,100],{"class":153},[147,5669,3615],{"class":3460},[147,5671,3478],{"class":390},[147,5673,3478],{"class":153},[147,5675,24],{"class":3501},[147,5677,1050],{"class":153},[147,5679,3626],{"class":3501},[147,5681,3510],{"class":153},[147,5683,3513],{"class":163},[147,5685,3633],{"class":390},[147,5687,3636],{"class":197},[147,5689,3558],{"class":390},[147,5691,3641],{"class":153},[147,5693,3644],{"class":390},[147,5695,3636],{"class":197},[147,5697,3649],{"class":390},[147,5699,100],{"class":153},[147,5701,3654],{"class":3460},[147,5703,3478],{"class":390},[147,5705,3659],{"class":197},[147,5707,1050],{"class":153},[147,5709,5710],{"class":197}," 8",[147,5712,3667],{"class":390},[12,5714,5715],{},"Fastest palette check that exists. It just does not scale past the tab you are in.",[30,5717,5719],{"id":5718},"screenshot-vs-css-vs-api","Screenshot vs CSS vs API",[440,5721,5722,5736],{},[443,5723,5724],{},[446,5725,5726,5728,5731,5734],{},[449,5727],{},[449,5729,5730],{},"Screenshot quantization",[449,5732,5733],{},"CSS computed styles",[449,5735,2347],{},[461,5737,5738,5750,5762,5773,5789,5803],{},[446,5739,5740,5743,5746,5748],{},[466,5741,5742],{},"Finds brand accent colors",[466,5744,5745],{},"Poorly (pixel-count bias)",[466,5747,1936],{},[466,5749,1936],{},[446,5751,5752,5755,5758,5760],{},[466,5753,5754],{},"Affected by photos in the page",[466,5756,5757],{},"Badly",[466,5759,512],{},[466,5761,512],{},[446,5763,5764,5767,5769,5771],{},[466,5765,5766],{},"Invents colors (anti-aliasing)",[466,5768,1936],{},[466,5770,512],{},[466,5772,512],{},[446,5774,5775,5777,5780,5786],{},[466,5776,2881],{},[466,5778,5779],{},"Sort of (as pixels)",[466,5781,5782,5783,5785],{},"Needs ",[16,5784,5011],{}," too",[466,5787,5788],{},"Yes, as gradient strings",[446,5790,5791,5794,5797,5800],{},[466,5792,5793],{},"Output format",[466,5795,5796],{},"RGB tuples",[466,5798,5799],{},"rgb() strings",[466,5801,5802],{},"Hex, ranked, plus the rest of the token set",[446,5804,5805,5808,5810,5812],{},[466,5806,5807],{},"Runs without your own browser infra",[466,5809,512],{},[466,5811,512],{},[466,5813,1936],{},[30,5815,4031],{"id":4030},[138,5817,5819],{"className":360,"code":5818,"language":362,"meta":143,"style":143},"curl \"https://miromiro.app/api/v1/extract?url=stripe.com&fields=colors,gradients\" \\\n  -H \"Authorization: Bearer $MIROMIRO_API_KEY\"\n",[16,5820,5821,5834],{"__ignoreMap":143},[147,5822,5823,5825,5827,5830,5832],{"class":149,"line":150},[147,5824,615],{"class":181},[147,5826,205],{"class":153},[147,5828,5829],{"class":208},"https://miromiro.app/api/v1/extract?url=stripe.com&fields=colors,gradients",[147,5831,167],{"class":153},[147,5833,391],{"class":390},[147,5835,5836,5838,5840,5842,5844],{"class":149,"line":157},[147,5837,629],{"class":208},[147,5839,205],{"class":153},[147,5841,634],{"class":208},[147,5843,637],{"class":390},[147,5845,404],{"class":153},[12,5847,5848,5849,5853,5854,5857],{},"Ranked palette with gradients included, normalized, 10 credits a call. There is also a ",[24,5850,5852],{"href":5851},"/api/tools/color-palette-extractor","free color palette extractor"," that runs the same engine in the browser if you want to eyeball a site before writing any code. ",[24,5855,5856],{"href":574},"Free key here"," - 100 credits a month, no card.",[30,5859,4086],{"id":4085},[12,5861,5862],{},"Read the CSS, not the pixels. Screenshot quantization answers \"what does this image look like\", which is the wrong question for a brand palette, and it will confidently hand you the colors of a stock photo. The console snippet for one site, Playwright for a script, the API when it needs to run inside something.",[30,5864,650],{"id":649},[652,5866,5868],{"id":5867},"how-do-i-get-a-websites-color-palette-in-python","How do I get a website's color palette in Python?",[12,5870,5182,5871,5873],{},[16,5872,2138],{}," counting (code above). Skip ColorThief-on-screenshot for brand work - it measures content, not identity.",[652,5875,5877],{"id":5876},"why-does-screenshot-extraction-give-wrong-brand-colors","Why does screenshot extraction give wrong brand colors?",[12,5879,5880],{},"Pixel counts reward photos and anti-aliasing blends, not the forty pixels of button that carry the actual accent color.",[652,5882,5884],{"id":5883},"can-i-do-this-in-the-browser-console","Can I do this in the browser console?",[12,5886,5887],{},"Yes - the snippet above ranks background colors on any page you have open.",[652,5889,5891],{"id":5890},"how-do-i-get-hex-instead-of-rgb-strings","How do I get hex instead of rgb() strings?",[12,5893,5894,5895,5898],{},"Three ",[16,5896,5897],{},"parseInt"," calls per channel, or use an API that normalizes for you.",[688,5900,5901],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":143,"searchDepth":157,"depth":157,"links":5903},[5904,5905,5906,5907,5908,5909,5910],{"id":5312,"depth":157,"text":5313},{"id":5408,"depth":157,"text":5409},{"id":5514,"depth":157,"text":5515},{"id":5718,"depth":157,"text":5719},{"id":4030,"depth":157,"text":4031},{"id":4085,"depth":157,"text":4086},{"id":649,"depth":157,"text":650,"children":5911},[5912,5913,5914,5915],{"id":5867,"depth":175,"text":5868},{"id":5876,"depth":175,"text":5877},{"id":5883,"depth":175,"text":5884},{"id":5890,"depth":175,"text":5891},"Two working ways to get a site's color palette in code - screenshot quantization vs reading the CSS - and which one returns the actual brand colors.",[5918,5920,5923,5926],{"question":5868,"answer":5919},"Two ways: quantize a screenshot with Pillow or ColorThief (fast, but photos in the page skew the result), or load the page with Playwright and count computed color and background-color values across elements (slower, but returns the actual CSS colors the brand uses). For brand palettes, use the CSS approach.",{"question":5921,"answer":5922},"Why does screenshot color extraction give wrong brand colors?","Because a screenshot has no idea what is UI and what is content. A hero photo of a sunset dominates the pixel counts, anti-aliasing invents hundreds of blend colors that exist nowhere in the CSS, and the true accent color of buttons and links can occupy very few pixels.",{"question":5924,"answer":5925},"Can I extract a color palette in the browser console?","Yes - a few lines of JavaScript over getComputedStyle gives you ranked colors for the page you are on. It is the fastest way to check one site by hand; it just is not automatable across many URLs without a headless browser around it.",{"question":5927,"answer":5928},"How do I get hex values instead of rgb() strings?","Computed styles always return rgb()/rgba(). Convert with three parseInt calls, or use an extraction API that normalizes to hex for you.",{},"/api-blog/how-to-extract-website-color-palette-programmatically",[729,3409,1260],{"title":5294,"description":5916},"api-blog/how-to-extract-website-color-palette-programmatically",[5935,3811,4182,3275,5936],"color palette","extraction","ZuBrzYUURU-j3lr-muPvdPr3xAzZrFGkvJ17zfbtBkg",1784751600707]