Brand extraction API

Point it at a domain. The brand comes back.

The logo, the colour roles, the type — 85 properties in all, read off the page as it renders, at the moment you ask. Any domain works: there is no list to be on and no record to go stale.

Try it on a domain
Nothing sampled yet

Give it a domain and watch it being read — the mark, the colours, the type, measured from the live page. A domain sampled before comes back in milliseconds.

What this is

Nobody has to add a brand first.

The question people bring to a brand API is whether the domains they need are in it. That question does not apply here, and the reason is the whole product.

what you are probably expecting
  1. a database of brands
  2. somebody adds each brand to it
  3. you get what they added, until somebody updates it
so the question is
“is the domain I need in there?”
and the answer is
as old as the last edit
what this is
  1. a reader, pointed at a page
  2. you ask for a domain
  3. we open the site and read it, and the answer is the page as it is at the moment you asked
so there is no question
every site that serves a page
and every answer is dated
extracted_at

Try one nobody would have bothered to add. Sample a domain.

Where it goes

Anywhere your product shows another company.

The screens where a bare name becomes that company's own logo, colour and type.

Onboarding

Ask for a work email and the first screen already carries their logo and their colour. Activation moves because the product looks like it was set up for them.

CRM and enrichment

Every account, lead and company record with a real mark instead of two grey initials. One call at write time, cached from then on.

Generated documents and decks

Proposals, invoices and slides that pick up the recipient's brand at render time — colour roles, type and spacing, not just a logo drop.

Agents that build things

A generator needs more than a hex triple. Roles, contrast pairs, the faces that actually load and what to substitute when they do not.

In the cache

Read from these pages, cached for the next caller.

The most recent brands anyone asked for. None of them were submitted to us — each one is a page somebody pointed us at, held so the next caller reads it for nothing.

For brand owners

If we've already read your site, you can take it over.

Every brand we publish was read from the company's own pages, not submitted by anyone — which means the record is only ever as current as the last time we looked. Prove you control the domain and that stops.

Prove it

Name the domain and an address on it. No DNS, no code — we send a link, you click it.

We stop reading

The record freezes where you leave it. Nothing we run measures that page again.

You edit it

Correct the logo, the colours, the copy — one field at a time, from your own dashboard.

Claim your brand — free, and always will be.

What comes back

85 properties, from the hex roles to the tone of voice.

Every field is what the brand publishes on its own pages, measured from the page as it renders rather than read off the stylesheet, and versioned so a redesign never silently changes shape underneath you.

identity
  • name
  • tagline
  • description
  • industry
  • audience
  • positioning_statement
  • values
colour
  • hex
  • role
  • on
  • names
  • coverage
type
  • family
  • role
  • availability
  • font_confidence
logo
  • url
  • svg
  • on_dark_url
  • on_light_url
  • source
  • description
  • dominant_colors
  • luminance
  • has_transparency
  • contrast_profile
  • background_color
visual style
  • theme
  • corner_style
  • headline_transform
  • headline_tracking
  • headline_weight
  • shadow_style
  • uses_gradients
  • border_style
  • photography_treatment
  • density
  • aesthetic_tags
  • headline_letter_spacing
  • headline_font_weight
  • border_radius
  • box_shadow
  • icon_style
  • illustration_style
  • photography_subjects
  • photography_composition
  • photography_color_grading
  • patterns
motion
  • personality
  • intensity
  • speed
  • easing_style
  • signature_effects
  • detected_libraries
  • has_scroll_animations
  • has_hover_effects
  • reduced_motion_support
measurements
  • base_unit
  • scale
  • content_margin
  • contrast_target
  • min_font_size_px
  • colorblind_safe
voice
  • tone
  • voice
  • preferred_terms
  • avoided_terms
  • cta_style
  • headline_case
  • tone_by_context
intelligence
  • company_summary
  • known_for
  • design_personality
  • design_conventions
  • slide_guidance
  • brand_values
  • inferred_audience
  • writing_style
provenance
  • confidence
  • confidence_per_field
  • confidence_signals
  • gaps
  • platform
  • screenshot_blob_url
  • schema_version

Three of those groups are the ones you were expecting. The rest are what a generator needs and a decoration does not.

How it works

Reading the CSS is not the same as seeing the page.

Most extractors parse stylesheets and rank colours by how often they appear in the source. That breaks in ways your users notice, because a modern page paints almost nothing the way it declares it.

Dead tokens

Design systems ship dozens of custom properties that never get painted. Source readers return them as brand colours. Measurement never sees them, so it never reports them.

--brand-500 #f2542d painted 21%
--brand-ink #0e1e2b painted 38%
--legacy-teal #12a6a6 never painted
--accent-alt #8e7bc4 never painted

Gradients

A hero gradient has no single hex. Parsers pick a stop or average the ramp and return a colour the brand does not use. We sample the painted region and report what dominates it.

parser averages the ramp #b44a55, unused
measured dominant across region #f2542d, 62%

Weighting

Frequency in source is not prominence on screen. A border colour declared in forty rules loses to an accent painted across a fifth of the viewport. Roles come from rendered area and position.

#e3e6eb41 rules
0.4% area
#f2542d2 rules
21% area
primary = #f2542d

Fonts

The stack is a wish list, not the result. A declared family means nothing if the file fails to load or the glyphs fall through. We report the face that rendered, with the fallback that actually resolved.

declared"Tiempos Headline", Georgia, serif
loadedTiempos Headline failed, 403
renderedGeorgia
reportedGeorgia, fallback of Tiempos
The pass

Six passes over one page, and nothing returned unverified.

The candidates come from measurement. A model chooses among them — it is shown the page as it rendered, not the source, and asked one question at a time. Then measurement checks the choice, and if the model does not answer, a deterministic result is already waiting. Ranking is measurement, naming is judgement, and the two are kept apart so a wrong name can never move a right hex.

  1. 01renderwe open the site in a real browser and let it finish painting
  2. 02placewe recognise the platform the site is built on and read what it publishes about itself
  3. 03readfour readings of one page — colour, type, logo, voice — each answering one question alone
  4. 04reconcilewhere the readings agree, that is the answer. Where they do not, the score says so
  5. 05verifyevery candidate is checked before it is returned. Most are not returned
  6. 06scoreconfidence per field, and the fields nobody could resolve, named

What verification looks like

One page's logo candidates, and what happened to each. The rejections are the work — a mark returned is a mark that survived them.

header img returned
inline svg, nav returned — mark on dark
favicon, 32px rejected — upscaled raster
og:image rejected — a screenshot, not the mark
partner strip ×3 rejected — not this brand
Confidence

We tell you when we are not sure.

A brand read from a clean, well-marked page is not the same result as one pieced together from a page that fought the render. Both come back; only one says it is sure.

Every extraction scores itself

  • confidence — the overall figure, 0 to 1
  • confidence_per_field — the same, per extracted field
  • gaps — fields no specialist could resolve, named
  • confidence_signals — what drove the number, in words

Set a floor in your own code and fall back to your defaults below it, rather than shipping a guess to your customer. A low score is reported, never hidden and never rounded up.

And when we cannot read a page at all

Some pages will not be read — they block automated browsers, sit behind a login, or never resolve. When that happens we say so.

We never hand you someone's guess instead. The useful answer to a page we could not read is that we could not read it — an answer you can branch on, rather than a plausible brand you would have shipped to your customer.

The response

One versioned shape, with the evidence attached.

Every extraction reports how sure it is, per field, and names the fields it could not resolve. Set a floor in your own code and fall back to your defaults below it, rather than shipping a guess to your customer.

$ curl https://api.extractbrand.dev/v1/brands/helio.com \
  -H "Authorization: Bearer YOUR_API_KEY"

{
  "object": "brand",
  "domain": "helio.com",
  "cache_status": "hit",
  "extracted_at": "2026-07-30T09:14:22.104Z",
  "expires_at": "2026-08-29T09:14:22.104Z",
  "brand": {
    "name": "Helio",
    "tagline": "Power that follows the sun",
    "positioning_statement": "Helio sells rooftop solar to homeowners who …",

    "colors": [
      { "hex": "#f2542d", "role": "primary",    "on": "#14161a",
        "names": ["--brand-ember"], "coverage": 0.11 },
      { "hex": "#0e1e2b", "role": "secondary",  "on": "#ffffff",
        "names": [],                "coverage": 0.07 },
      { "hex": "#ffffff", "role": "background", "on": "#14161a",
        "names": ["--surface"],     "coverage": 0.62 },
      { "hex": "#14161a", "role": "text",       "on": "#ffffff",
        "names": [],                "coverage": 0.09 },
      { "hex": "#c8ccd0", "role": null,         "on": "#14161a",
        "names": [],                "coverage": 0.03 }
    ],

    "fonts": [
      { "family": "Tiempos", "role": "display", "availability": "custom-webfont" },
      { "family": "Inter",   "role": "body",    "availability": "google" }
    ],

    "logo": {
      "url":         "https://helio.com/logo.svg",
      "on_dark_url": "https://helio.com/logo-light.svg",
      "source":      "inline-svg",
      "dominant_colors": ["#f2542d"], …
    },

    "visual_style": { "theme": "light", "corner_style": "rounded", … },
    "motion":       { "intensity": "subtle", "speed": "moderate", … },
    "copywriting":  { "cta_style": "imperative — Get, Start, Book", … },
    "tone": ["direct", "warm"],

    "confidence": 0.94,
    "gaps": [],
    "schema_version": "v2"
  },
  "quota": { "used": 128, "limit": 5000, "period_end": "2026-08-01T00:00:00.000Z" }
}
Two ways in

An HTTP call, or one line in your agent's config.

The REST surface is the contract. The hosted MCP server is the same three operations behind the same key, the same cache and the same meter — so an agent that adds it can read a brand without anyone writing the fetch.

HTTP

Call it yourself

One GET per domain, cached from then on. The full request and response shape is in the reference.

curl https://api.extractbrand.dev/v1/brands/helio.com \
  -H "Authorization: Bearer YOUR_API_KEY"
MCP

Hand it to your agent

Streamable HTTP at https://api.extractbrand.dev/v1/mcp, authenticated with the same key. Paste this into Claude Code, or point Cursor and VS Code at the same endpoint.

claude mcp add extractbrand --transport http https://api.extractbrand.dev/v1/mcp --header "Authorization: Bearer YOUR_API_KEY"
  • get_brandRead a domain's brand — logos, colour roles, type, and the confidence behind each.
  • refresh_brandRe-extract a domain whose site has changed since we last read it.
  • get_usageCheck the key's reads against its allowance before spending another.
Pricing

Metered on calls, not on seats.

One unit per answered call, and nothing per seat. A domain nobody has sampled takes a browser, two model calls and a few seconds; one we already hold comes back in milliseconds — the wait differs, the price does not. Growth is $20 a month with 500 answered calls included, then $0.01 a call — billed in your local currency at checkout. Every answered call counts, whether we extract the brand for you or serve one we already hold.

Free

10 calls a month

  • Every answered call counts as one
  • No card to start
  • Full response shape, no trimmed fields
  • On-demand refresh needs a paid plan
Create a free key
Growth

$20 a month

  • 500 answered calls included
  • $0.01 per call after that, new domain or not
  • Set your own monthly ceiling
  • Refresh a brand on demand
Start with a free key
Embedded

Volume and platform terms

  • Committed-volume pricing
  • Hosted MCP server for your agents
  • Direct line to the people who built it
Talk to us

Start with a domain you know well.

You will know inside one read whether the colours and type match what you would have picked by hand — and a free key takes no card.