[{"data":1,"prerenderedAt":2165},["ShallowReactive",2],{"api-blog-clearbit-logo-api-alternatives":3,"api-blog-related-clearbit-logo-api-alternatives":537},{"id":4,"title":5,"author":6,"body":7,"category":497,"date":498,"dateModified":498,"description":499,"extension":500,"faqs":501,"image":517,"meta":518,"navigation":88,"path":519,"readingTime":128,"relatedSlugs":520,"seo":524,"seoTitle":525,"stem":526,"tags":527,"__hash__":536},"apiBlog/api-blog/clearbit-logo-api-alternatives.md","Clearbit Logo API Alternatives in 2026: What to Use Now That logo.clearbit.com Is Gone","Soraia",{"type":8,"value":9,"toc":488},"minimark",[10,23,167,170,175,289,292,295,303,306,309,312,318,321,325,331,338,342,350,372,383,424,427,441,445,466,470,484],[11,12,13,14,18,19,22],"p",{},"Clearbit's free Logo API is gone. ",[15,16,17],"code",{},"logo.clearbit.com"," was deprecated in March 2025 and shut down in December 2025, so every ",[15,20,21],{},"\u003Cimg src=\"https://logo.clearbit.com/stripe.com\">"," on the web now shows a broken image. The fix is a one-line swap to a provider that works the same way:",[24,25,30],"pre",{"className":26,"code":27,"language":28,"meta":29,"style":29},"language-html shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003C!-- Before -->\n\u003Cimg src=\"https://logo.clearbit.com/stripe.com\" alt=\"Stripe\">\n\n\u003C!-- After: Brandfetch (free up to 1M requests/month) -->\n\u003Cimg src=\"https://cdn.brandfetch.io/domain/stripe.com?c=YOUR_CLIENT_ID\" alt=\"Stripe\">\n\n\u003C!-- Or: Logo.dev -->\n\u003Cimg src=\"https://img.logo.dev/stripe.com?token=YOUR_PUBLISHABLE_KEY\" alt=\"Stripe\">\n","html","",[15,31,32,41,83,90,96,126,131,137],{"__ignoreMap":29},[33,34,37],"span",{"class":35,"line":36},"line",1,[33,38,40],{"class":39},"sHwdD","\u003C!-- Before -->\n",[33,42,44,48,52,56,59,62,66,68,71,73,75,78,80],{"class":35,"line":43},2,[33,45,47],{"class":46},"sMK4o","\u003C",[33,49,51],{"class":50},"swJcz","img",[33,53,55],{"class":54},"spNyl"," src",[33,57,58],{"class":46},"=",[33,60,61],{"class":46},"\"",[33,63,65],{"class":64},"sfazB","https://logo.clearbit.com/stripe.com",[33,67,61],{"class":46},[33,69,70],{"class":54}," alt",[33,72,58],{"class":46},[33,74,61],{"class":46},[33,76,77],{"class":64},"Stripe",[33,79,61],{"class":46},[33,81,82],{"class":46},">\n",[33,84,86],{"class":35,"line":85},3,[33,87,89],{"emptyLinePlaceholder":88},true,"\n",[33,91,93],{"class":35,"line":92},4,[33,94,95],{"class":39},"\u003C!-- After: Brandfetch (free up to 1M requests/month) -->\n",[33,97,99,101,103,105,107,109,112,114,116,118,120,122,124],{"class":35,"line":98},5,[33,100,47],{"class":46},[33,102,51],{"class":50},[33,104,55],{"class":54},[33,106,58],{"class":46},[33,108,61],{"class":46},[33,110,111],{"class":64},"https://cdn.brandfetch.io/domain/stripe.com?c=YOUR_CLIENT_ID",[33,113,61],{"class":46},[33,115,70],{"class":54},[33,117,58],{"class":46},[33,119,61],{"class":46},[33,121,77],{"class":64},[33,123,61],{"class":46},[33,125,82],{"class":46},[33,127,129],{"class":35,"line":128},6,[33,130,89],{"emptyLinePlaceholder":88},[33,132,134],{"class":35,"line":133},7,[33,135,136],{"class":39},"\u003C!-- Or: Logo.dev -->\n",[33,138,140,142,144,146,148,150,153,155,157,159,161,163,165],{"class":35,"line":139},8,[33,141,47],{"class":46},[33,143,51],{"class":50},[33,145,55],{"class":54},[33,147,58],{"class":46},[33,149,61],{"class":46},[33,151,152],{"class":64},"https://img.logo.dev/stripe.com?token=YOUR_PUBLISHABLE_KEY",[33,154,61],{"class":46},[33,156,70],{"class":54},[33,158,58],{"class":46},[33,160,61],{"class":46},[33,162,77],{"class":64},[33,164,61],{"class":46},[33,166,82],{"class":46},[11,168,169],{},"Both are image CDNs keyed by domain - exactly what Clearbit was. The rest of this post compares the options and covers the case a logo CDN can't: when you need the company's colors and fonts too.",[171,172,174],"h2",{"id":173},"the-options-at-a-glance","The options at a glance",[176,177,178,200],"table",{},[179,180,181],"thead",{},[182,183,184,188,191,194,197],"tr",{},[185,186,187],"th",{},"Option",[185,189,190],{},"What you get",[185,192,193],{},"URL / call",[185,195,196],{},"Free tier",[185,198,199],{},"Best for",[201,202,203,226,246,267],"tbody",{},[182,204,205,212,215,220,223],{},[206,207,208],"td",{},[209,210,211],"strong",{},"Brandfetch Logo API",[206,213,214],{},"Logo image by domain",[206,216,217],{},[15,218,219],{},"cdn.brandfetch.io/domain/{domain}?c=ID",[206,221,222],{},"Free, 1M requests/month fair use, no attribution",[206,224,225],{},"Drop-in Clearbit replacement",[182,227,228,233,236,241,243],{},[206,229,230],{},[209,231,232],{},"Logo.dev",[206,234,235],{},"Logo image by domain, name or ticker",[206,237,238],{},[15,239,240],{},"img.logo.dev/{domain}?token=KEY",[206,242,196],{},[206,244,245],{},"Drop-in replacement; Clearbit's recommended successor",[182,247,248,253,256,261,264],{},[206,249,250],{},[209,251,252],{},"Google favicon service",[206,254,255],{},"The site's favicon",[206,257,258],{},[15,259,260],{},"google.com/s2/favicons?domain={domain}&sz=128",[206,262,263],{},"Free, unofficial",[206,265,266],{},"Fallback only",[182,268,269,275,278,283,286],{},[206,270,271,274],{},[209,272,273],{},"Brand API"," (e.g. MiroMiro)",[206,276,277],{},"Logo + colors + fonts + social image as JSON",[206,279,280],{},[15,281,282],{},"GET /v1/brand?url=…",[206,284,285],{},"300 credits/month",[206,287,288],{},"Theming, onboarding, \"brand from a URL\"",[171,290,211],{"id":291},"brandfetch-logo-api",[11,293,294],{},"Brandfetch's Logo API is the closest like-for-like swap. You register for a free client ID, then build image URLs:",[24,296,301],{"className":297,"code":299,"language":300},[298],"language-text","https://cdn.brandfetch.io/domain/stripe.com?c=YOUR_CLIENT_ID\n","text",[15,302,299],{"__ignoreMap":29},[11,304,305],{},"It's free with a fair-use limit of 1,000,000 requests a month and doesn't ask for attribution. Paid plans raise the volume. If you were happily using Clearbit's free tier, this is the least-effort move.",[171,307,232],{"id":308},"logodev",[11,310,311],{},"Logo.dev is the replacement Clearbit itself pointed users to. Same idea, a publishable key in the URL:",[24,313,316],{"className":314,"code":315,"language":300},[298],"https://img.logo.dev/stripe.com?token=YOUR_PUBLISHABLE_KEY\n",[15,317,315],{"__ignoreMap":29},[11,319,320],{},"It can also look logos up by company name or stock ticker, and has options for size, format, retina and dark/light variants. There's a free tier to start with.",[171,322,324],{"id":323},"googles-favicon-service-fallback-only","Google's favicon service (fallback only)",[24,326,329],{"className":327,"code":328,"language":300},[298],"https://www.google.com/s2/favicons?domain=stripe.com&sz=128\n",[15,330,328],{"__ignoreMap":29},[11,332,333,334,337],{},"No key, no signup - but it returns the ",[209,335,336],{},"favicon",", which is often a tiny square icon rather than the logo, and it's an unofficial endpoint with no guarantees. Use it as the last link in a fallback chain, not as your main source.",[171,339,341],{"id":340},"when-you-need-more-than-a-logo","When you need more than a logo",[11,343,344,345,349],{},"A logo CDN answers one question: ",[346,347,348],"em",{},"what does this company's logo look like?"," A lot of products that used Clearbit actually needed more:",[351,352,353,360,366],"ul",{},[354,355,356,359],"li",{},[209,357,358],{},"White-labelling or onboarding"," - \"enter your website and we'll theme your workspace\" needs the brand's primary and accent colors, not just the logo.",[354,361,362,365],{},[209,363,364],{},"Prospect-branded demos and proposals"," - logo, colors and fonts together.",[354,367,368,371],{},[209,369,370],{},"Small or new companies"," - logo CDNs index well-known domains; a ten-person startup may not be in them yet.",[11,373,374,375,378,379,382],{},"That's what a brand API is for. MiroMiro's ",[15,376,377],{},"/v1/brand"," reads the ",[209,380,381],{},"live website"," and returns the identity as JSON:",[24,384,388],{"className":385,"code":386,"language":387,"meta":29,"style":29},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","curl \"https://miromiro.app/api/v1/brand?url=https://stripe.com&fields=logo,palette,fonts\" \\\n  -H \"Authorization: Bearer $MIROMIRO_API_KEY\"\n","bash",[15,389,390,408],{"__ignoreMap":29},[33,391,392,396,399,402,404],{"class":35,"line":36},[33,393,395],{"class":394},"sBMFI","curl",[33,397,398],{"class":46}," \"",[33,400,401],{"class":64},"https://miromiro.app/api/v1/brand?url=https://stripe.com&fields=logo,palette,fonts",[33,403,61],{"class":46},[33,405,407],{"class":406},"sTEyZ"," \\\n",[33,409,410,413,415,418,421],{"class":35,"line":43},[33,411,412],{"class":64},"  -H",[33,414,398],{"class":46},[33,416,417],{"class":64},"Authorization: Bearer ",[33,419,420],{"class":406},"$MIROMIRO_API_KEY",[33,422,423],{"class":46},"\"\n",[11,425,426],{},"You get the best logo URL (and every candidate it found, tagged by source), color roles - primary, accent, background, text - with readable names, the brand palette, the fonts in use, the social share image and a confidence score. Because it reads the site itself, it works for any public domain, not just the ones a logo CDN has indexed.",[11,428,429,432,433,436,437,440],{},[209,430,431],{},"The honest trade-off:"," a brand call costs 10 credits and does real extraction work, so don't put it in an ",[15,434,435],{},"\u003Cimg src>"," that fires on every page view - for that, use the free logo URL, ",[15,438,439],{},"https://miromiro.app/logo/\u003Cdomain>",", which serves the logo as an image with no key. Call it once per company, store the result, and serve the logo from your own storage or a CDN. For \"just show the logo\", Brandfetch or Logo.dev is the better tool.",[171,442,444],{"id":443},"a-sensible-setup","A sensible setup",[446,447,448,454,460],"ol",{},[354,449,450,453],{},[209,451,452],{},"Logos in lists, tables and avatars:"," Brandfetch or Logo.dev as the primary source.",[354,455,456,459],{},[209,457,458],{},"Fallback chain:"," if that returns nothing, try Google's favicon, then your own placeholder.",[354,461,462,465],{},[209,463,464],{},"Full brand when it matters:"," call a brand API once per company at signup or import, store the logo, colors and fonts, and theme from your own data.",[171,467,469],{"id":468},"or-use-one-api-call","Or use one API call",[11,471,472,473,478,479,483],{},"If what you're building needs the brand - not just the mark - ",[474,475,477],"a",{"href":476},"/api/docs/brand","MiroMiro's brand API"," returns logo, colors and fonts from any URL in one request. You can start for free: ",[474,480,482],{"href":481},"/api/onboarding","get a free API key",", 300 credits a month, no card.",[485,486,487],"style",{},"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);}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 .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}",{"title":29,"searchDepth":43,"depth":43,"links":489},[490,491,492,493,494,495,496],{"id":173,"depth":43,"text":174},{"id":291,"depth":43,"text":211},{"id":308,"depth":43,"text":232},{"id":323,"depth":43,"text":324},{"id":340,"depth":43,"text":341},{"id":443,"depth":43,"text":444},{"id":468,"depth":43,"text":469},"Brand Data","2026-10-03","Clearbit's free Logo API shut down in December 2025. Here are the drop-in replacements - Logo.dev, Brandfetch, Google's favicon service - with URL formats, free tiers, and when you need a full brand API instead.","md",[502,505,508,511,514],{"question":503,"answer":504},"Is the Clearbit Logo API still working?","No. Clearbit's free Logo API (logo.clearbit.com) was deprecated in March 2025 and shut down in December 2025, after HubSpot's acquisition of Clearbit. Requests to logo.clearbit.com no longer return logos, so any \u003Cimg> pointing at it shows a broken image.",{"question":506,"answer":507},"What is the best free replacement for the Clearbit Logo API?","For a drop-in logo image by domain, Brandfetch's Logo API and Logo.dev both work the way logo.clearbit.com did: an image URL with the domain in it plus a free key. Brandfetch's Logo API is free up to a fair-use limit of 1,000,000 requests a month and needs no attribution. Logo.dev also has a free tier and was recommended by Clearbit as its replacement.",{"question":509,"answer":510},"How do I migrate from logo.clearbit.com?","Find every place you build https://logo.clearbit.com/{domain}, swap it for the new provider's URL with your key (for example https://img.logo.dev/{domain}?token=KEY or https://cdn.brandfetch.io/domain/{domain}?c=CLIENT_ID), and keep a fallback image for domains the provider doesn't have. It's usually a one-line change in a single helper.",{"question":512,"answer":513},"Can I get a company's brand colors and fonts as well as the logo?","Not from a logo CDN - those return an image only. A brand API returns the structured identity: MiroMiro's /v1/brand reads the live website and returns the logo plus color roles, the brand palette, fonts and the social share image as JSON from one request.",{"question":515,"answer":516},"Is Google's favicon service a good Clearbit replacement?","Only as a fallback. https://www.google.com/s2/favicons?domain={domain}&sz=128 returns the site's favicon, which is often small or square and not the full logo. It's an unofficial endpoint with no SLA, so don't depend on it as your primary source.",null,{},"/api-blog/clearbit-logo-api-alternatives",[521,522,523],"best-brand-data-apis-2026","how-to-extract-brand-assets-in-bulk","how-to-extract-website-color-palette-programmatically",{"title":5,"description":499},"Clearbit Logo API Alternatives (2026): What Replaced It","api-blog/clearbit-logo-api-alternatives",[528,529,530,531,532,533,534,535],"clearbit logo api","clearbit alternative","logo api","free logo api","company logo api","brand api","logo.dev","brandfetch","B9WR8KQv2jP_bZs7R5ybS6IVR1YdOPLEnbN60gOHJRA",[538,962,1446],{"id":539,"title":540,"author":6,"body":541,"category":938,"date":939,"dateModified":517,"description":940,"extension":500,"faqs":941,"image":517,"meta":952,"navigation":88,"path":953,"readingTime":133,"relatedSlugs":954,"seo":957,"seoTitle":517,"stem":958,"tags":959,"__hash__":961},"apiBlog/api-blog/best-brand-data-apis-2026.md","Best Brand Data APIs in 2026 (Logos, Colors, Fonts)",{"type":8,"value":542,"toc":924},[543,550,553,557,566,573,593,597,604,621,625,632,649,653,669,699,725,728,753,757,867,871,883,887,892,895,899,902,906,909,913,921],[11,544,545,546,549],{},"If you need to turn ",[15,547,548],{},"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.",[11,551,552],{},"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.",[171,554,556],{"id":555},"_1-brandfetch-the-category-owner","1. Brandfetch - the category owner",[11,558,559,565],{},[474,560,564],{"href":561,"rel":562},"https://brandfetch.com",[563],"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.",[11,567,568,569,572],{},"Their pricing (verified July 2026): free tier of 100 Brand API requests, then the Brand API at ",[209,570,571],{},"$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.",[351,574,575,581,587],{},[354,576,577,580],{},[209,578,579],{},"Good:"," the deepest logo coverage in the market, hand-curated for big brands, reliable.",[354,582,583,586],{},[209,584,585],{},"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.",[354,588,589,592],{},[209,590,591],{},"Use it when:"," logo quality and coverage matter more than design depth, and the budget is there.",[171,594,596],{"id":595},"_2-logodev-the-pragmatic-logo-endpoint","2. Logo.dev - the pragmatic logo endpoint",[11,598,599,603],{},[474,600,232],{"href":601,"rel":602},"https://logo.dev",[563]," does one thing: domain in, logo image out, with a generous free tier and simple img-tag integration.",[351,605,606,611,616],{},[354,607,608,610],{},[209,609,579],{}," dead simple, fast to integrate, free for most usage.",[354,612,613,615],{},[209,614,585],{}," logos only. No colors, no fonts, no JSON brand object.",[354,617,618,620],{},[209,619,591],{}," you literally just need company logos in a table or CRM.",[171,622,624],{"id":623},"_3-clearbit-logo-api-the-old-free-standby","3. Clearbit Logo API - the old free standby",[11,626,627,628,631],{},"Clearbit's ",[15,629,630],{},"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.",[351,633,634,639,644],{},[354,635,636,638],{},[209,637,579],{}," free, zero setup, still works.",[354,640,641,643],{},[209,642,585],{}," logos only, no SLA, and it lives at an acquirer's pleasure. I would not build a paid product on it in 2026.",[354,645,646,648],{},[209,647,591],{}," internal tools and prototypes.",[171,650,652],{"id":651},"_4-miromiro-brand-extract-api-identity-plus-the-design-system","4. MiroMiro brand + extract API - identity plus the design system",[11,654,655,656,658,659,663,664,668],{},"Our ",[474,657,377],{"href":476}," endpoint returns the logo, palette, and fonts like a brand API. The difference is what sits next to it: ",[474,660,662],{"href":661},"/api/docs/design-tokens","/v1/extract"," returns the site's working design system - ranked colors, type scale, spacing, radii, shadows, gradients, motion - and ",[474,665,667],{"href":666},"/api/docs/code","/v1/code"," turns a section into Tailwind/JSX/Vue. Same key, same call shape:",[24,670,672],{"className":385,"code":671,"language":387,"meta":29,"style":29},"curl \"https://miromiro.app/api/v1/brand?url=stripe.com&fields=logo,palette\" \\\n  -H \"Authorization: Bearer $MIROMIRO_API_KEY\"\n",[15,673,674,687],{"__ignoreMap":29},[33,675,676,678,680,683,685],{"class":35,"line":36},[33,677,395],{"class":394},[33,679,398],{"class":46},[33,681,682],{"class":64},"https://miromiro.app/api/v1/brand?url=stripe.com&fields=logo,palette",[33,684,61],{"class":46},[33,686,407],{"class":406},[33,688,689,691,693,695,697],{"class":35,"line":43},[33,690,412],{"class":64},[33,692,398],{"class":46},[33,694,417],{"class":64},[33,696,420],{"class":406},[33,698,423],{"class":46},[11,700,701,702,706,707,710,711,710,715,719,720,724],{},"If you want to judge the output before writing any code, the ",[474,703,705],{"href":704},"/brand-logos","logo and brand-color library"," publishes what /v1/brand returns for 179 well-known companies - ",[474,708,77],{"href":709},"/brand-logos/stripe",", ",[474,712,714],{"href":713},"/brand-logos/spotify","Spotify",[474,716,718],{"href":717},"/brand-logos/linear","Linear"," and the rest - the header logo in light and dark, role colors and fonts, each stamped with the date it was read. To eyeball one site's colors first, the ",[474,721,723],{"href":722},"/tools/color-palette-extractor","free color palette extractor"," runs the same engine in the browser.",[11,726,727],{},"Pricing: 300 free credits a month with no card, then from €19/mo for 5,000 credits. A brand call costs 10 credits, the same as a token extraction.",[351,729,730,739,748],{},[354,731,732,734,735,738],{},[209,733,579],{}," the only option here that returns enough to actually ",[346,736,737],{},"theme"," a product, not just badge it. Built for the \"enter your URL\" onboarding flow.",[354,740,741,743,744,747],{},[209,742,585],{}," 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 by default; fully client-rendered sites need ",[15,745,746],{},"render=true"," (+5 credits) to run the page's JavaScript in a real browser first.",[354,749,750,752],{},[209,751,591],{}," the goal is brand-personalized UI (onboarding, generated apps, email builders), not a logo lookup.",[171,754,756],{"id":755},"side-by-side","Side by side",[176,758,759,783],{},[179,760,761],{},[182,762,763,766,769,772,775,778,780],{},[185,764,765],{},"API",[185,767,768],{},"Logo",[185,770,771],{},"Colors",[185,773,774],{},"Fonts",[185,776,777],{},"Full design system",[185,779,196],{},[185,781,782],{},"Paid entry",[201,784,785,807,826,845],{},[182,786,787,789,792,795,798,801,804],{},[206,788,564],{},[206,790,791],{},"Excellent",[206,793,794],{},"Basic",[206,796,797],{},"Names",[206,799,800],{},"No",[206,802,803],{},"100 req",[206,805,806],{},"$99/mo (2,500 calls)",[182,808,809,811,814,816,818,820,823],{},[206,810,232],{},[206,812,813],{},"Good",[206,815,800],{},[206,817,800],{},[206,819,800],{},[206,821,822],{},"Generous",[206,824,825],{},"Cheap",[182,827,828,831,833,835,837,839,842],{},[206,829,830],{},"Clearbit Logo",[206,832,813],{},[206,834,800],{},[206,836,800],{},[206,838,800],{},[206,840,841],{},"Unlimited-ish",[206,843,844],{},"n/a",[182,846,847,850,852,855,858,861,864],{},[206,848,849],{},"MiroMiro",[206,851,813],{},[206,853,854],{},"Ranked, full palette",[206,856,857],{},"Families + weights + sizes",[206,859,860],{},"Yes (tokens + section code)",[206,862,863],{},"300 credits/mo",[206,865,866],{},"€19/mo (5,000 credits)",[171,868,870],{"id":869},"the-verdict","The verdict",[11,872,873,874,877,878,882],{},"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 ",[474,875,876],{"href":481},"get a free key"," and test your own domain in the ",[474,879,881],{"href":880},"/api/demo","playground"," in under a minute.",[171,884,886],{"id":885},"frequently-asked-questions","Frequently asked questions",[888,889,891],"h3",{"id":890},"what-is-a-brand-data-api","What is a brand data API?",[11,893,894],{},"Domain in, structured brand assets out: logo, colors, fonts. Products use them for onboarding personalization, CRM enrichment, and auto-theming.",[888,896,898],{"id":897},"is-there-a-free-logo-api","Is there a free logo API?",[11,900,901],{},"Yes, two: Brandfetch's Logo API tier (free to 500k req/mo) and Clearbit's endpoint. For logos alone, pay nothing.",[888,903,905],{"id":904},"why-fetch-brand-data-during-onboarding","Why fetch brand data during onboarding?",[11,907,908],{},"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.",[888,910,912],{"id":911},"brand-api-vs-design-token-api","Brand API vs design token API?",[11,914,915,916,920],{},"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 ",[474,917,919],{"href":918},"/api/blog/design-token-types-reference","design token types reference"," breaks down the difference.",[485,922,923],{},"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":29,"searchDepth":43,"depth":43,"links":925},[926,927,928,929,930,931,932],{"id":555,"depth":43,"text":556},{"id":595,"depth":43,"text":596},{"id":623,"depth":43,"text":624},{"id":651,"depth":43,"text":652},{"id":755,"depth":43,"text":756},{"id":869,"depth":43,"text":870},{"id":885,"depth":43,"text":886,"children":933},[934,935,936,937],{"id":890,"depth":85,"text":891},{"id":897,"depth":85,"text":898},{"id":904,"depth":85,"text":905},{"id":911,"depth":85,"text":912},"Comparisons","2026-07-18","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.",[942,944,946,949],{"question":891,"answer":943},"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":898,"answer":945},"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":947,"answer":948},"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":950,"answer":951},"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",[955,956,522],"clearbit-logo-api-alternatives","best-design-token-extraction-tools-2026",{"title":540,"description":940},"api-blog/best-brand-data-apis-2026",[533,535,530,960],"comparison","gYrF88wkL5FxDyVw-TXsTFiUb_tMRub4IMzy4GTuoj4",{"id":963,"title":964,"author":6,"body":965,"category":1419,"date":939,"dateModified":517,"description":1420,"extension":500,"faqs":1421,"image":517,"meta":1433,"navigation":88,"path":1434,"readingTime":133,"relatedSlugs":1435,"seo":1438,"seoTitle":517,"stem":1439,"tags":1440,"__hash__":1445},"apiBlog/api-blog/how-to-extract-brand-assets-in-bulk.md","How to Extract Brand Assets from 1,000 URLs in Bulk (Python)",{"type":8,"value":966,"toc":1406},[967,970,974,986,990,1006,1253,1263,1267,1286,1292,1302,1306,1355,1358,1362,1369,1371,1375,1378,1382,1385,1389,1392,1396,1403],[11,968,969],{},"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.",[171,971,973],{"id":972},"why-not-1000-playwright-launches","Why not 1,000 Playwright launches",[11,975,976,977,981,982,985],{},"The ",[474,978,980],{"href":979},"/api/blog/how-to-extract-design-tokens-from-any-website-in-python","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 ",[15,983,984],{},"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.",[171,987,989],{"id":988},"the-script","The script",[24,991,993],{"className":385,"code":992,"language":387,"meta":29,"style":29},"pip install httpx\n",[15,994,995],{"__ignoreMap":29},[33,996,997,1000,1003],{"class":35,"line":36},[33,998,999],{"class":394},"pip",[33,1001,1002],{"class":64}," install",[33,1004,1005],{"class":64}," httpx\n",[24,1007,1011],{"className":1008,"code":1009,"language":1010,"meta":29,"style":29},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","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","python",[15,1012,1013,1018,1023,1027,1032,1037,1042,1047,1051,1057,1063,1069,1075,1081,1087,1093,1099,1105,1111,1117,1123,1129,1135,1141,1147,1153,1159,1165,1171,1176,1182,1188,1194,1200,1206,1212,1218,1224,1230,1235,1241,1247],{"__ignoreMap":29},[33,1014,1015],{"class":35,"line":36},[33,1016,1017],{},"import asyncio, csv, json\n",[33,1019,1020],{"class":35,"line":43},[33,1021,1022],{},"import httpx\n",[33,1024,1025],{"class":35,"line":85},[33,1026,89],{"emptyLinePlaceholder":88},[33,1028,1029],{"class":35,"line":92},[33,1030,1031],{},"API = \"https://miromiro.app/api/v1/brand\"\n",[33,1033,1034],{"class":35,"line":98},[33,1035,1036],{},"KEY = \"mm_live_your_key\"\n",[33,1038,1039],{"class":35,"line":128},[33,1040,1041],{},"CONCURRENCY = 5          # stay under your plan's req/min cap\n",[33,1043,1044],{"class":35,"line":133},[33,1045,1046],{},"RETRIES = 3\n",[33,1048,1049],{"class":35,"line":139},[33,1050,89],{"emptyLinePlaceholder":88},[33,1052,1054],{"class":35,"line":1053},9,[33,1055,1056],{},"async def extract(client, sem, url):\n",[33,1058,1060],{"class":35,"line":1059},10,[33,1061,1062],{},"    async with sem:\n",[33,1064,1066],{"class":35,"line":1065},11,[33,1067,1068],{},"        for attempt in range(RETRIES):\n",[33,1070,1072],{"class":35,"line":1071},12,[33,1073,1074],{},"            try:\n",[33,1076,1078],{"class":35,"line":1077},13,[33,1079,1080],{},"                r = await client.get(\n",[33,1082,1084],{"class":35,"line":1083},14,[33,1085,1086],{},"                    API,\n",[33,1088,1090],{"class":35,"line":1089},15,[33,1091,1092],{},"                    params={\"url\": url, \"fields\": \"logo,palette\"},\n",[33,1094,1096],{"class":35,"line":1095},16,[33,1097,1098],{},"                    headers={\"Authorization\": f\"Bearer {KEY}\"},\n",[33,1100,1102],{"class":35,"line":1101},17,[33,1103,1104],{},"                    timeout=60,\n",[33,1106,1108],{"class":35,"line":1107},18,[33,1109,1110],{},"                )\n",[33,1112,1114],{"class":35,"line":1113},19,[33,1115,1116],{},"                if r.status_code == 429:            # rate limited: back off\n",[33,1118,1120],{"class":35,"line":1119},20,[33,1121,1122],{},"                    await asyncio.sleep(2 ** attempt * 5)\n",[33,1124,1126],{"class":35,"line":1125},21,[33,1127,1128],{},"                    continue\n",[33,1130,1132],{"class":35,"line":1131},22,[33,1133,1134],{},"                if r.status_code == 200:\n",[33,1136,1138],{"class":35,"line":1137},23,[33,1139,1140],{},"                    return {\"url\": url, \"ok\": True, \"data\": r.json()}\n",[33,1142,1144],{"class":35,"line":1143},24,[33,1145,1146],{},"                return {\"url\": url, \"ok\": False, \"error\": f\"HTTP {r.status_code}\"}\n",[33,1148,1150],{"class":35,"line":1149},25,[33,1151,1152],{},"            except httpx.HTTPError as e:\n",[33,1154,1156],{"class":35,"line":1155},26,[33,1157,1158],{},"                if attempt == RETRIES - 1:\n",[33,1160,1162],{"class":35,"line":1161},27,[33,1163,1164],{},"                    return {\"url\": url, \"ok\": False, \"error\": str(e)}\n",[33,1166,1168],{"class":35,"line":1167},28,[33,1169,1170],{},"                await asyncio.sleep(2 ** attempt)\n",[33,1172,1174],{"class":35,"line":1173},29,[33,1175,89],{"emptyLinePlaceholder":88},[33,1177,1179],{"class":35,"line":1178},30,[33,1180,1181],{},"async def main(urls):\n",[33,1183,1185],{"class":35,"line":1184},31,[33,1186,1187],{},"    sem = asyncio.Semaphore(CONCURRENCY)\n",[33,1189,1191],{"class":35,"line":1190},32,[33,1192,1193],{},"    async with httpx.AsyncClient() as client:\n",[33,1195,1197],{"class":35,"line":1196},33,[33,1198,1199],{},"        results = await asyncio.gather(*(extract(client, sem, u) for u in urls))\n",[33,1201,1203],{"class":35,"line":1202},34,[33,1204,1205],{},"    with open(\"brands.jsonl\", \"w\") as f:\n",[33,1207,1209],{"class":35,"line":1208},35,[33,1210,1211],{},"        for row in results:\n",[33,1213,1215],{"class":35,"line":1214},36,[33,1216,1217],{},"            f.write(json.dumps(row) + \"\\n\")\n",[33,1219,1221],{"class":35,"line":1220},37,[33,1222,1223],{},"    failed = [r for r in results if not r[\"ok\"]]\n",[33,1225,1227],{"class":35,"line":1226},38,[33,1228,1229],{},"    print(f\"{len(results) - len(failed)} ok, {len(failed)} failed\")\n",[33,1231,1233],{"class":35,"line":1232},39,[33,1234,89],{"emptyLinePlaceholder":88},[33,1236,1238],{"class":35,"line":1237},40,[33,1239,1240],{},"if __name__ == \"__main__\":\n",[33,1242,1244],{"class":35,"line":1243},41,[33,1245,1246],{},"    urls = [row[0] for row in csv.reader(open(\"domains.csv\"))]\n",[33,1248,1250],{"class":35,"line":1249},42,[33,1251,1252],{},"    asyncio.run(main(urls))\n",[11,1254,1255,1256,1258,1259,1262],{},"Swap the endpoint for ",[15,1257,662],{}," if you want full token sets instead of brand data, and adjust ",[15,1260,1261],{},"fields="," to trim the response to what you store - it does not change the credit cost, but it makes the output files dramatically smaller.",[171,1264,1266],{"id":1265},"the-three-things-that-make-bulk-runs-survive","The three things that make bulk runs survive",[11,1268,1269,1272,1273,1277,1278,1281,1282,1285],{},[209,1270,1271],{},"1. The semaphore is the contract."," Your plan has a requests-per-minute cap (the ladder is in the ",[474,1274,1276],{"href":1275},"/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 ",[15,1279,1280],{},"gather"," at any API gets you rate-limited into taking ",[346,1283,1284],{},"longer"," than the polite version.",[11,1287,1288,1291],{},[209,1289,1290],{},"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.",[11,1293,1294,1297,1298,1301],{},[209,1295,1296],{},"3. Re-runs are cheap because of caching."," Responses are cached for 24 hours and a cache hit costs 0 credits (",[15,1299,1300],{},"\"cached\": true"," in the usage object). Re-running today's failed batch only pays for URLs that actually re-extract - so retry freely.",[171,1303,1305],{"id":1304},"budgeting-a-1000-url-run","Budgeting a 1,000-URL run",[176,1307,1308,1320],{},[179,1309,1310],{},[182,1311,1312,1314,1317],{},[185,1313],{},[185,1315,1316],{},"Brand data (15 cr/URL)",[185,1318,1319],{},"Design tokens (10 cr/URL)",[201,1321,1322,1333,1344],{},[182,1323,1324,1327,1330],{},[206,1325,1326],{},"100 URLs",[206,1328,1329],{},"1,500 credits",[206,1331,1332],{},"1,000 credits",[182,1334,1335,1338,1341],{},[206,1336,1337],{},"1,000 URLs",[206,1339,1340],{},"15,000 credits",[206,1342,1343],{},"10,000 credits",[182,1345,1346,1349,1352],{},[206,1347,1348],{},"Fits in",[206,1350,1351],{},"Growth (€79/mo, 30k)",[206,1353,1354],{},"Growth, or 2× Developer months",[11,1356,1357],{},"The free 300 credits cover a 20-30 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.",[171,1359,1361],{"id":1360},"summary","Summary",[11,1363,1364,1365,1368],{},"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 ",[474,1366,1367],{"href":481},"free key",", then size the plan to the list.",[171,1370,886],{"id":885},[888,1372,1374],{"id":1373},"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?",[11,1376,1377],{},"For a few sites, Playwright and computed styles. For hundreds or thousands, the async httpx script above against an extraction API.",[888,1379,1381],{"id":1380},"how-long-does-1000-urls-take","How long does 1,000 URLs take?",[11,1383,1384],{},"Tens of minutes - your requests-per-minute cap is the bottleneck, not the extraction itself.",[888,1386,1388],{"id":1387},"what-failure-rate-should-i-expect","What failure rate should I expect?",[11,1390,1391],{},"5-15% on real-world lists: dead domains, parked pages, bot walls. Capture and re-run rather than aiming for zero.",[888,1393,1395],{"id":1394},"do-repeated-extractions-cost-credits-again","Do repeated extractions cost credits again?",[11,1397,1398,1399,1402],{},"Not within 24 hours - cache hits cost 0 credits and report ",[15,1400,1401],{},"cached: true",".",[485,1404,1405],{},"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":29,"searchDepth":43,"depth":43,"links":1407},[1408,1409,1410,1411,1412,1413],{"id":972,"depth":43,"text":973},{"id":988,"depth":43,"text":989},{"id":1265,"depth":43,"text":1266},{"id":1304,"depth":43,"text":1305},{"id":1360,"depth":43,"text":1361},{"id":885,"depth":43,"text":886,"children":1414},[1415,1416,1417,1418],{"id":1373,"depth":85,"text":1374},{"id":1380,"depth":85,"text":1381},{"id":1387,"depth":85,"text":1388},{"id":1394,"depth":85,"text":1395},"Tutorials","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.",[1422,1424,1427,1430],{"question":1374,"answer":1423},"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":1425,"answer":1426},"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":1428,"answer":1429},"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":1431,"answer":1432},"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",[955,521,1436,1437],"how-to-extract-design-tokens-from-any-website-in-python","add-design-extraction-to-claude-code-mcp",{"title":964,"description":1420},"api-blog/how-to-extract-brand-assets-in-bulk",[1441,1010,1442,1443,1444],"bulk extraction","async","brand data","automation","yMBiXQ6hQjp4IiwEhjdFHas6tXlXi77EkepkhsiE62I",{"id":1447,"title":1448,"author":6,"body":1449,"category":1419,"date":939,"dateModified":517,"description":2139,"extension":500,"faqs":2140,"image":517,"meta":2152,"navigation":88,"path":2153,"readingTime":128,"relatedSlugs":2154,"seo":2157,"seoTitle":517,"stem":2158,"tags":2159,"__hash__":2164},"apiBlog/api-blog/how-to-extract-website-color-palette-programmatically.md","How to Extract a Website's Color Palette Programmatically",{"type":8,"value":1450,"toc":2125},[1451,1461,1464,1468,1471,1498,1566,1569,1573,1576,1673,1679,1683,1686,1932,1935,1939,2037,2039,2069,2079,2081,2084,2086,2090,2097,2101,2104,2108,2111,2115,2122],[11,1452,1453,1454,1460],{},"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: ",[209,1455,1456,1457,1402],{},"pixel methods find what a page looks like; CSS methods find what the brand ",[346,1458,1459],{},"is"," If you want brand colors, read the CSS.",[11,1462,1463],{},"Here are both, working, then the comparison.",[171,1465,1467],{"id":1466},"method-1-quantize-a-screenshot-fast-misleading","Method 1: quantize a screenshot (fast, misleading)",[11,1469,1470],{},"The classic approach - screenshot the page, run color quantization:",[24,1472,1474],{"className":385,"code":1473,"language":387,"meta":29,"style":29},"pip install playwright colorthief\nplaywright install chromium\n",[15,1475,1476,1488],{"__ignoreMap":29},[33,1477,1478,1480,1482,1485],{"class":35,"line":36},[33,1479,999],{"class":394},[33,1481,1002],{"class":64},[33,1483,1484],{"class":64}," playwright",[33,1486,1487],{"class":64}," colorthief\n",[33,1489,1490,1493,1495],{"class":35,"line":43},[33,1491,1492],{"class":394},"playwright",[33,1494,1002],{"class":64},[33,1496,1497],{"class":64}," chromium\n",[24,1499,1501],{"className":1008,"code":1500,"language":1010,"meta":29,"style":29},"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",[15,1502,1503,1508,1513,1518,1522,1527,1532,1537,1542,1547,1552,1556,1561],{"__ignoreMap":29},[33,1504,1505],{"class":35,"line":36},[33,1506,1507],{},"from io import BytesIO\n",[33,1509,1510],{"class":35,"line":43},[33,1511,1512],{},"from colorthief import ColorThief\n",[33,1514,1515],{"class":35,"line":85},[33,1516,1517],{},"from playwright.sync_api import sync_playwright\n",[33,1519,1520],{"class":35,"line":92},[33,1521,89],{"emptyLinePlaceholder":88},[33,1523,1524],{"class":35,"line":98},[33,1525,1526],{},"with sync_playwright() as p:\n",[33,1528,1529],{"class":35,"line":128},[33,1530,1531],{},"    browser = p.chromium.launch()\n",[33,1533,1534],{"class":35,"line":133},[33,1535,1536],{},"    page = browser.new_page(viewport={\"width\": 1440, \"height\": 900})\n",[33,1538,1539],{"class":35,"line":139},[33,1540,1541],{},"    page.goto(\"https://stripe.com\", wait_until=\"networkidle\")\n",[33,1543,1544],{"class":35,"line":1053},[33,1545,1546],{},"    shot = page.screenshot()\n",[33,1548,1549],{"class":35,"line":1059},[33,1550,1551],{},"    browser.close()\n",[33,1553,1554],{"class":35,"line":1065},[33,1555,89],{"emptyLinePlaceholder":88},[33,1557,1558],{"class":35,"line":1071},[33,1559,1560],{},"palette = ColorThief(BytesIO(shot)).get_palette(color_count=6)\n",[33,1562,1563],{"class":35,"line":1077},[33,1564,1565],{},"print(['#%02x%02x%02x' % c for c in palette])\n",[11,1567,1568],{},"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.",[171,1570,1572],{"id":1571},"method-2-read-the-css-the-right-answer","Method 2: read the CSS (the right answer)",[11,1574,1575],{},"Ask the browser what colors the stylesheet actually applies:",[24,1577,1579],{"className":1008,"code":1578,"language":1010,"meta":29,"style":29},"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",[15,1580,1581,1586,1590,1594,1599,1604,1609,1614,1619,1624,1628,1632,1636,1641,1645,1650,1654,1658,1663,1668],{"__ignoreMap":29},[33,1582,1583],{"class":35,"line":36},[33,1584,1585],{},"from collections import Counter\n",[33,1587,1588],{"class":35,"line":43},[33,1589,1517],{},[33,1591,1592],{"class":35,"line":85},[33,1593,89],{"emptyLinePlaceholder":88},[33,1595,1596],{"class":35,"line":92},[33,1597,1598],{},"COLLECT = \"\"\"\n",[33,1600,1601],{"class":35,"line":98},[33,1602,1603],{},"() => [...document.querySelectorAll('body *')].slice(0, 5000).flatMap(el => {\n",[33,1605,1606],{"class":35,"line":128},[33,1607,1608],{},"  const s = getComputedStyle(el);\n",[33,1610,1611],{"class":35,"line":133},[33,1612,1613],{},"  return [s.color, s.backgroundColor, s.borderTopColor];\n",[33,1615,1616],{"class":35,"line":139},[33,1617,1618],{},"})\n",[33,1620,1621],{"class":35,"line":1053},[33,1622,1623],{},"\"\"\"\n",[33,1625,1626],{"class":35,"line":1059},[33,1627,89],{"emptyLinePlaceholder":88},[33,1629,1630],{"class":35,"line":1065},[33,1631,1526],{},[33,1633,1634],{"class":35,"line":1071},[33,1635,1531],{},[33,1637,1638],{"class":35,"line":1077},[33,1639,1640],{},"    page = browser.new_page()\n",[33,1642,1643],{"class":35,"line":1083},[33,1644,1541],{},[33,1646,1647],{"class":35,"line":1089},[33,1648,1649],{},"    values = page.evaluate(COLLECT)\n",[33,1651,1652],{"class":35,"line":1095},[33,1653,1551],{},[33,1655,1656],{"class":35,"line":1101},[33,1657,89],{"emptyLinePlaceholder":88},[33,1659,1660],{"class":35,"line":1107},[33,1661,1662],{},"skip = {\"rgba(0, 0, 0, 0)\", \"rgb(0, 0, 0)\", \"rgb(255, 255, 255)\"}\n",[33,1664,1665],{"class":35,"line":1113},[33,1666,1667],{},"for color, count in Counter(v for v in values if v not in skip).most_common(8):\n",[33,1669,1670],{"class":35,"line":1119},[33,1671,1672],{},"    print(f\"{count:>5}  {color}\")\n",[11,1674,1675,1676,1402],{},"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 ",[474,1677,1678],{"href":979},"design tokens guide",[171,1680,1682],{"id":1681},"checking-one-site-by-hand-use-the-console","Checking one site by hand? Use the console",[11,1684,1685],{},"Paste this in DevTools on any page:",[24,1687,1691],{"className":1688,"code":1689,"language":1690,"meta":29,"style":29},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","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","js",[15,1692,1693,1760,1789,1854,1864],{"__ignoreMap":29},[33,1694,1695,1698,1700,1704,1707,1710,1713,1715,1718,1721,1724,1727,1729,1732,1734,1737,1739,1741,1745,1748,1751,1754,1757],{"class":35,"line":36},[33,1696,1697],{"class":406},"Object",[33,1699,1402],{"class":46},[33,1701,1703],{"class":1702},"s2Zo4","entries",[33,1705,1706],{"class":406},"([",[33,1708,1709],{"class":46},"...",[33,1711,1712],{"class":406},"document",[33,1714,1402],{"class":46},[33,1716,1717],{"class":1702},"querySelectorAll",[33,1719,1720],{"class":406},"(",[33,1722,1723],{"class":46},"'",[33,1725,1726],{"class":64},"body *",[33,1728,1723],{"class":46},[33,1730,1731],{"class":406},")]",[33,1733,1402],{"class":46},[33,1735,1736],{"class":1702},"reduce",[33,1738,1720],{"class":406},[33,1740,1720],{"class":46},[33,1742,1744],{"class":1743},"sHdIc","acc",[33,1746,1747],{"class":46},",",[33,1749,1750],{"class":1743}," el",[33,1752,1753],{"class":46},")",[33,1755,1756],{"class":54}," =>",[33,1758,1759],{"class":46}," {\n",[33,1761,1762,1765,1768,1771,1774,1776,1779,1781,1783,1786],{"class":35,"line":43},[33,1763,1764],{"class":54},"  const",[33,1766,1767],{"class":406}," c",[33,1769,1770],{"class":46}," =",[33,1772,1773],{"class":1702}," getComputedStyle",[33,1775,1720],{"class":50},[33,1777,1778],{"class":406},"el",[33,1780,1753],{"class":50},[33,1782,1402],{"class":46},[33,1784,1785],{"class":406},"backgroundColor",[33,1787,1788],{"class":46},";\n",[33,1790,1791,1795,1798,1801,1804,1807,1810,1812,1815,1817,1820,1822,1825,1827,1829,1831,1833,1835,1837,1840,1844,1846,1849,1852],{"class":35,"line":85},[33,1792,1794],{"class":1793},"s7zQu","  if",[33,1796,1797],{"class":50}," (",[33,1799,1800],{"class":406},"c",[33,1802,1803],{"class":46}," !==",[33,1805,1806],{"class":46}," '",[33,1808,1809],{"class":64},"rgba(0, 0, 0, 0)",[33,1811,1723],{"class":46},[33,1813,1814],{"class":50},") ",[33,1816,1744],{"class":406},[33,1818,1819],{"class":50},"[",[33,1821,1800],{"class":406},[33,1823,1824],{"class":50},"] ",[33,1826,58],{"class":46},[33,1828,1797],{"class":50},[33,1830,1744],{"class":406},[33,1832,1819],{"class":50},[33,1834,1800],{"class":406},[33,1836,1824],{"class":50},[33,1838,1839],{"class":46},"||",[33,1841,1843],{"class":1842},"sbssI"," 0",[33,1845,1814],{"class":50},[33,1847,1848],{"class":46},"+",[33,1850,1851],{"class":1842}," 1",[33,1853,1788],{"class":46},[33,1855,1856,1859,1862],{"class":35,"line":92},[33,1857,1858],{"class":1793},"  return",[33,1860,1861],{"class":406}," acc",[33,1863,1788],{"class":46},[33,1865,1866,1869,1872,1875,1877,1880,1882,1884,1886,1888,1891,1893,1895,1898,1901,1903,1906,1909,1911,1914,1916,1919,1921,1924,1926,1929],{"class":35,"line":98},[33,1867,1868],{"class":46},"},",[33,1870,1871],{"class":46}," {}",[33,1873,1874],{"class":406},"))",[33,1876,1402],{"class":46},[33,1878,1879],{"class":1702},"sort",[33,1881,1720],{"class":406},[33,1883,1720],{"class":46},[33,1885,474],{"class":1743},[33,1887,1747],{"class":46},[33,1889,1890],{"class":1743}," b",[33,1892,1753],{"class":46},[33,1894,1756],{"class":54},[33,1896,1897],{"class":406}," b[",[33,1899,1900],{"class":1842},"1",[33,1902,1824],{"class":406},[33,1904,1905],{"class":46},"-",[33,1907,1908],{"class":406}," a[",[33,1910,1900],{"class":1842},[33,1912,1913],{"class":406},"])",[33,1915,1402],{"class":46},[33,1917,1918],{"class":1702},"slice",[33,1920,1720],{"class":406},[33,1922,1923],{"class":1842},"0",[33,1925,1747],{"class":46},[33,1927,1928],{"class":1842}," 8",[33,1930,1931],{"class":406},")\n",[11,1933,1934],{},"Fastest palette check that exists. It just does not scale past the tab you are in.",[171,1936,1938],{"id":1937},"screenshot-vs-css-vs-api","Screenshot vs CSS vs API",[176,1940,1941,1956],{},[179,1942,1943],{},[182,1944,1945,1947,1950,1953],{},[185,1946],{},[185,1948,1949],{},"Screenshot quantization",[185,1951,1952],{},"CSS computed styles",[185,1954,1955],{},"MiroMiro API",[201,1957,1958,1971,1983,1994,2012,2026],{},[182,1959,1960,1963,1966,1969],{},[206,1961,1962],{},"Finds brand accent colors",[206,1964,1965],{},"Poorly (pixel-count bias)",[206,1967,1968],{},"Yes",[206,1970,1968],{},[182,1972,1973,1976,1979,1981],{},[206,1974,1975],{},"Affected by photos in the page",[206,1977,1978],{},"Badly",[206,1980,800],{},[206,1982,800],{},[182,1984,1985,1988,1990,1992],{},[206,1986,1987],{},"Invents colors (anti-aliasing)",[206,1989,1968],{},[206,1991,800],{},[206,1993,800],{},[182,1995,1996,1999,2002,2009],{},[206,1997,1998],{},"Gradients",[206,2000,2001],{},"Sort of (as pixels)",[206,2003,2004,2005,2008],{},"Needs ",[15,2006,2007],{},"backgroundImage"," too",[206,2010,2011],{},"Yes, as gradient strings",[182,2013,2014,2017,2020,2023],{},[206,2015,2016],{},"Output format",[206,2018,2019],{},"RGB tuples",[206,2021,2022],{},"rgb() strings",[206,2024,2025],{},"Hex, ranked, plus the rest of the token set",[182,2027,2028,2031,2033,2035],{},[206,2029,2030],{},"Runs without your own browser infra",[206,2032,800],{},[206,2034,800],{},[206,2036,1968],{},[171,2038,469],{"id":468},[24,2040,2042],{"className":385,"code":2041,"language":387,"meta":29,"style":29},"curl \"https://miromiro.app/api/v1/extract?url=stripe.com&fields=colors,gradients\" \\\n  -H \"Authorization: Bearer $MIROMIRO_API_KEY\"\n",[15,2043,2044,2057],{"__ignoreMap":29},[33,2045,2046,2048,2050,2053,2055],{"class":35,"line":36},[33,2047,395],{"class":394},[33,2049,398],{"class":46},[33,2051,2052],{"class":64},"https://miromiro.app/api/v1/extract?url=stripe.com&fields=colors,gradients",[33,2054,61],{"class":46},[33,2056,407],{"class":406},[33,2058,2059,2061,2063,2065,2067],{"class":35,"line":43},[33,2060,412],{"class":64},[33,2062,398],{"class":46},[33,2064,417],{"class":64},[33,2066,420],{"class":406},[33,2068,423],{"class":46},[11,2070,2071,2072,2074,2075,2078],{},"Ranked palette with gradients included, normalized, 10 credits a call. There is also a ",[474,2073,723],{"href":722}," that runs the same engine in the browser if you want to eyeball a site before writing any code. ",[474,2076,2077],{"href":481},"Free key here"," - 300 credits a month, no card.",[171,2080,1361],{"id":1360},[11,2082,2083],{},"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.",[171,2085,886],{"id":885},[888,2087,2089],{"id":2088},"how-do-i-get-a-websites-color-palette-in-python","How do I get a website's color palette in Python?",[11,2091,2092,2093,2096],{},"Playwright plus ",[15,2094,2095],{},"getComputedStyle"," counting (code above). Skip ColorThief-on-screenshot for brand work - it measures content, not identity.",[888,2098,2100],{"id":2099},"why-does-screenshot-extraction-give-wrong-brand-colors","Why does screenshot extraction give wrong brand colors?",[11,2102,2103],{},"Pixel counts reward photos and anti-aliasing blends, not the forty pixels of button that carry the actual accent color.",[888,2105,2107],{"id":2106},"can-i-do-this-in-the-browser-console","Can I do this in the browser console?",[11,2109,2110],{},"Yes - the snippet above ranks background colors on any page you have open.",[888,2112,2114],{"id":2113},"how-do-i-get-hex-instead-of-rgb-strings","How do I get hex instead of rgb() strings?",[11,2116,2117,2118,2121],{},"Three ",[15,2119,2120],{},"parseInt"," calls per channel, or use an API that normalizes for you.",[485,2123,2124],{},"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":29,"searchDepth":43,"depth":43,"links":2126},[2127,2128,2129,2130,2131,2132,2133],{"id":1466,"depth":43,"text":1467},{"id":1571,"depth":43,"text":1572},{"id":1681,"depth":43,"text":1682},{"id":1937,"depth":43,"text":1938},{"id":468,"depth":43,"text":469},{"id":1360,"depth":43,"text":1361},{"id":885,"depth":43,"text":886,"children":2134},[2135,2136,2137,2138],{"id":2088,"depth":85,"text":2089},{"id":2099,"depth":85,"text":2100},{"id":2106,"depth":85,"text":2107},{"id":2113,"depth":85,"text":2114},"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.",[2141,2143,2146,2149],{"question":2089,"answer":2142},"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":2144,"answer":2145},"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":2147,"answer":2148},"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":2150,"answer":2151},"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",[1436,2155,2156],"how-to-detect-fonts-a-website-uses","design-token-types-reference",{"title":1448,"description":2139},"api-blog/how-to-extract-website-color-palette-programmatically",[2160,1010,2161,2162,2163],"color palette","javascript","colors","extraction","FQqUforvcS0JPdMCP_Ynk7ERKOsMaVCx5c4-i-oXQEQ",1792517974829]