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.
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/brandWho the site says it is — identity, colour, marks and the links off the page.
POST /api/v1/styleguideThe design system it renders with, read off the stylesheets and the live DOM.
POST /api/v1/fontsThe type stack, down to the files the page really downloaded.
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 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.
| source | Where it was read from |
|---|---|
| theme-color | The <meta name="theme-color"> the page declares. The site naming its own brand colour outright. |
| manifest | theme_color and background_color from the web app manifest the page links, fetched and parsed. |
| css-variable | A colour-valued custom property the site declares on :root — its own design token, not a sampled pixel. |
| computed | Sampled off rendered elements: text and background colours, with weight counting how many elements used it. |
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.
colors.primary resolves
toThe 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.
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.
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.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.
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.
// 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
});
}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.
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);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.
/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 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.
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.
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.
# 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": "…" } }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.
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.
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.
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.