Every brand signal a domain gives away

One URL returns logos, a weighted colour palette, company details and social links — with the site’s full design system and font stack one call away.

Identity, design system, type stack

Three endpoints, each requiring nothing but a URL. Ask for the one you need — identity for a CRM row, the design system for theming, the type stack for an audit — or all 25 credits’ worth of them for one domain.

POST /api/v1/brand
10 credits / profile

Who the site says it is — identity, colour, marks and the links off the page.

  • name, legalName, description, slogan
  • industry and founded, one string each, lifted from the site’s own schema.org Organization markup — never a classification Wayfern assigns
  • email, phone and address when the page publishes them, null when it does not
  • colors.primary plus a weighted palette
  • logos[] with type, format, width, height and variant
  • links.social keyed by network, links.site for pricing, docs, careers and friends
POST /api/v1/styleguide
10 credits / site

The design system it renders with, read off the stylesheets and the live DOM.

  • colors.palette, plus separate text, background and accent short-lists
  • typography.families with the full declared stack and an inferred role (heading, body, mono, ui)
  • A type scale: each rendered size with its element count, paired line height and weight
  • weights, lineHeights and letterSpacings actually in use
  • radii, shadows, spacing and the breakpoints declared in the site’s own CSS
  • cssVariables — every custom property the site declares on :root
  • sources — which sheets, inline or linked, the report was built from
POST /api/v1/fonts
5 credits / site

The type stack, down to the files the page really downloaded.

  • family plus source: google, adobe, bunny, fontshare, self-hosted, third-party or system
  • host serving the files, when it is not a system font
  • weights, styles and the formats that were fetched
  • files[] with url, format and bytes
  • display and unicodeRanges declared in @font-face
  • usage — element count, a sample selector, and whether the family sets headings or body copy
  • totalBytes across every font file the page pulled
One domain in, a brand profile out
curl -X POST https://api.wayfern.com/api/v1/brand \
  -H "Authorization: Bearer wf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"url": "stripe.com"}'

The response above is abridged and illustrative — what a domain yields depends on what that domain publishes, and every nullable field really does come back null when the page is silent. There is no SDK to install: it is one POST, and the credits field hands back exactly what the call debited.

A palette that says where each hex came from

A flat list of hexes makes you guess which one is the brand. Every palette entry carries a source and a weight, so you can tell a colour the site declared about itself from one that merely covers a lot of pixels.

sourceWhere it was read from
theme-colorThe <meta name="theme-color"> the page declares. The site naming its own brand colour outright.
manifesttheme_color and background_color from the web app manifest the page links, fetched and parsed.
css-variableA colour-valued custom property the site declares on :root — its own design token, not a sampled pixel.
computedSampled off rendered elements: text and background colours, with weight counting how many elements used it.

How the list is ordered

Declarative sources first — theme-color, then manifest, then css-variable, then computed — and within a source, heaviest first. A declarative entry starts at weight 0, because nothing was sampled to produce it, and only gains weight if the rendered page painted that exact colour too. Either way it keeps the most authoritative source it was seen under.

What colors.primary resolves to

The declared theme-color if there is one, else the manifest’s theme colour, else the heaviest non-greyscale colour on the page, else the heaviest colour of any kind. When none of those exist it is null rather than a guess.

From a work email to a filled-in workspace

Split the work email, pass the domain, and the signup form you were about to make someone fill in is already answered. To be plain about the boundary: the email-to-domain step is ordinary code in your app — Wayfern has no email endpoint. What it takes is a domain.

Fields you stop asking for

Company name and legal name, a description and slogan in the company’s own words, the industry line it uses about itself, and any email, phone or address the site publishes. Whatever is missing comes back null, so a partial profile never poses as a complete one.

Links you would otherwise chase

links.social is keyed by network — twitter, linkedin, github, mastodon and the rest — with share widgets filtered out rather than mistaken for accounts. links.site picks up the notable first-party pages: pricing, docs, careers.

When the domain pushes back

Reports run in a real browser session on a fingerprinted browser context, and proxy: "auto" — the default — takes a datacenter exit first, retrying on a residential one only when the site blocks it. country pins the residential exit, so pair it with proxy: "residential" when a domain answers differently by region. Residential bytes are metered on top of the per-call rate, at 320 credits per GB.

Enrich on signup
onboarding.js
// The email split happens in YOUR code — there is no email endpoint.
// Drop the free-mail providers you do not want to enrich.
const FREEMAIL = new Set(["gmail.com", "outlook.com", "yahoo.com", "icloud.com"]);

function domainFromWorkEmail(email) {
  const domain = email.split("@")[1]?.trim().toLowerCase();
  return !domain || FREEMAIL.has(domain) ? null : domain;
}

// One hop from "signed up" to "workspace already looks like theirs".
const domain = domainFromWorkEmail(signup.email);
if (domain) {
  const res = await fetch("https://api.wayfern.com/api/v1/brand", {
    method: "POST",
    headers: {
      Authorization: "Bearer " + process.env.WF_TOKEN,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({ url: domain })
  });
  const { data: profile, credits } = await res.json();
  await workspaces.update(signup.workspaceId, {
    name: profile.name ?? profile.legalName,
    industry: profile.industry,
    accent: profile.colors.primary,
    socials: profile.links.social,
    enrichmentCredits: credits
  });
}

Their design tokens, straight into your custom properties

cssVariables is the tenant’s own :root token set, keys and all, with values already resolved by the browser — so a var(--a, var(--b)) chain arrives as the colour it ends up being. Write it into your shell and you are themed in their vocabulary, not a re-derived approximation of it.

Apply a styleguide
theme.js
const res = await fetch("https://api.wayfern.com/api/v1/styleguide", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + process.env.WF_TOKEN,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ url: domain })
});
const { data: guide } = await res.json();

const root = document.documentElement;

// 1. The tenant's own tokens, verbatim. Keys already carry their "--" prefix,
//    and the values are the resolved ones, so var() chains are collapsed.
for (const [name, value] of Object.entries(guide.cssVariables)) {
  root.style.setProperty(name, value);
}

// 2. The derived signals, for the parts of your shell that need a decision
//    rather than a token dump.
const [accent] = guide.colors.accent;
const [tightestRadius] = guide.radii;

// families are ranked by how many elements use them, and each carries an
// inferred role — so ask for the heading face by role, not by position.
const families = guide.typography.families;
const heading = families.find((f) => f.role === "heading") ?? families[0];

if (accent) root.style.setProperty("--tenant-accent", accent);
if (heading) root.style.setProperty("--tenant-font", heading.stack);
if (tightestRadius) root.style.setProperty("--tenant-radius", tightestRadius);

Beyond the tokens

Plenty of sites declare no tokens at all. The report still measures the rendered page: a type scale of sizes with their element counts and paired line heights, the weights and letter spacings in use, the distinct radii and shadows, the spacing steps, and the breakpoints found in the site’s own CSS.

Type, if that is all you need

/api/v1/fonts is the cheaper, narrower call at 5 credits: which families, served from where, at which weights and formats, which files were actually fetched and how many bytes they cost the page.

A visual reference, stored privately

A palette is easier to argue about next to a picture of the page. POST /api/v1/screenshot takes the same URL and captures the full scrollable page, dismissing cookie and consent overlays first. Render it in dark mode, or scope the capture to a single CSS selector when all you want is the header or the pricing table.

Private bucket, signed link

Captures are stored privately. The url returned with one is a signed link with an expiry stamped on it (urlExpiresAt), while the object itself stays put for its retention window.

Re-minting costs nothing

Store the artifact key, not the URL. Exchange it at /api/v1/artifacts/link for a fresh signed link whenever a stale one turns up in a brand kit you shipped last month. The endpoint is not metered, so it debits nothing.

Re-mint a download link
bash
# The signed url expires long before the object behind it does.
# Exchange the stored key for a fresh link whenever you need one.
# Keys are minted as screenshots/<your-user-id>/<uuid>.<ext> — send one back verbatim.
curl -X POST https://api.wayfern.com/api/v1/artifacts/link \
  -H "Authorization: Bearer wf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"key": "screenshots/usr_2f9c/9c1f4d02-7ab8-4e51-9d3f-2c60ba17e8c4.png"}'

# → { "success": true, "credits": 0,
#     "data": { "key": "…", "url": "https://…", "urlExpiresAt": "…" } }

Priced per call, from one balance

The same credits that run browser sessions, scrapes and search. Rates are identical on every plan, and each response repeats the charge back to you.

Brand profile
10 / profile
Identity, palette, logos, company details and links.
Styleguide
10 / site
Colours, type scale, radii, shadows, spacing, breakpoints, tokens.
Fonts
5 / site
Families, source, host, weights, formats, files and usage.
Screenshot
5 / capture
A visual reference, optionally dark-mode or scoped to one selector.
Artifact link
0 / link
Re-mint a signed download URL for a stored capture. Charges nothing.

A 1,000-domain enrichment run

1,000 brand profiles at 10 credits each is 10,000 credits — $100 at the 20,000-credit pack rate of $0.0100 / credit. Credit packs do not expire and roll over, so a one-off backfill does not have to become a plan decision.

Or from a monthly grant

Business includes 9,000 credits a month — about 900 brand profiles, if that is all you spent them on. Entry to the platform is $19 a month; the full rate card lists every endpoint.

Ready to scale?

Point it at one domain to see the shape, then run the list. Same balance, same rates, whether it is ten domains or ten thousand.