This page has one function for each endpoint. Each function turns the response of that endpoint into the BrandContext shape. The types cover only the fields that the adapters read. The key names come from the responses on this site and from the documentation of each vendor, as of 11 November 2026. The Rainbrand adapters follow its REST API, which uses camelCase inside data. Its Model Context Protocol (MCP) tools return the same parts in snake_case.
export type Kind = "observed" | "maintained";
export interface BrandContext {
name: string;
description?: string;
voice?: { summary?: string; attributes?: string[]; avoid?: string[]; guide?: string };
positioning?: {
valueProposition?: string;
audiences?: string[];
products?: { name: string; description?: string; pricingUrl?: string }[];
competitors?: string[];
};
colors?: { hex: string; role?: string; name?: string; official?: boolean | null; usage?: string }[];
typography?: { role: string; family: string; weight?: number; size?: string }[];
logos?: { url: string; format?: string; form?: string; background?: string; expiresAt?: string | null }[];
rules?: string[];
files?: { url: string; mediaType?: string; purpose?: string; expiresAt?: string | null }[];
source: { endpoint: string; kind: Kind; requestedAt: string; readAt?: string | null; version?: string | null };
}
const now = () => new Date().toISOString();
// Brandfetch Brand Context API: GET /v2/context/{domain}
export function fromBrandfetchContext(r: {
meta: { canonical_name: string; resolved_at: string };
identity: { description?: string };
positioning: {
value_proposition?: string;
target_audience?: { segment: string }[];
products_and_services?: { name: string; description?: string; pricing?: { url?: string } | null }[];
};
brand: { voice?: { summary?: string; attributes?: string[]; avoid?: string[] } };
competition?: { competitors?: { name: string }[] } | null;
}): BrandContext {
return {
name: r.meta.canonical_name,
description: r.identity.description,
voice: r.brand.voice,
positioning: {
valueProposition: r.positioning.value_proposition,
audiences: r.positioning.target_audience?.map((a) => a.segment),
products: r.positioning.products_and_services?.map((p) => ({
name: p.name,
description: p.description,
pricingUrl: p.pricing?.url,
})),
competitors: r.competition?.competitors?.map((c) => c.name),
},
rules: r.brand.voice?.avoid?.map((tone) => `Don't sound like: ${tone}.`),
source: { endpoint: "Brandfetch Brand Context API", kind: "observed", requestedAt: now(), readAt: r.meta.resolved_at },
};
}
// Brandfetch Brand API: GET /v2/brands/domain/{domain}
export function fromBrandfetchBrand(r: {
name: string;
description?: string;
colors?: { hex: string; type?: string }[];
fonts?: { name: string; type?: string }[];
logos?: { theme?: string; type?: string; formats: { src: string; format: string }[] }[];
}): BrandContext {
return {
name: r.name,
description: r.description,
colors: r.colors?.map((c) => ({ hex: c.hex, name: c.type, official: null })),
typography: r.fonts?.map((f) => ({ role: f.type ?? "body", family: f.name })),
logos: r.logos?.flatMap((l) =>
l.formats.map((f) => ({ url: f.src, format: f.format, form: l.type, background: l.theme }))
),
source: { endpoint: "Brandfetch Brand API", kind: "observed", requestedAt: now() },
};
}
// Context.dev Brand: POST /v1/brand/retrieve
export function fromContextDevBrand(r: {
brand: {
title?: string;
domain: string;
description?: string;
colors?: { hex: string; name?: string }[];
logos?: { url: string; mode?: string; type?: string }[];
backdrops?: { url: string }[];
};
}): BrandContext {
return {
name: r.brand.title ?? r.brand.domain,
description: r.brand.description,
colors: r.brand.colors?.map((c) => ({ hex: c.hex, name: c.name, official: null })),
logos: r.brand.logos?.map((l) => ({ url: l.url, form: l.type, background: l.mode })),
files: r.brand.backdrops?.map((b) => ({ url: b.url, purpose: "backdrop image from the website" })),
source: { endpoint: "Context.dev Brand", kind: "observed", requestedAt: now() },
};
}
// Context.dev Styleguide: GET /v1/web/styleguide?domain=
export function fromContextDevStyleguide(name: string, r: {
styleguide: {
colors?: { accent?: string; background?: string; text?: string };
typography?: { headings?: Record<string, { fontFamily: string; fontSize?: string; fontWeight?: number }> };
};
}): BrandContext {
const c = r.styleguide.colors ?? {};
const roles = { accent: c.accent, background: c.background, text: c.text };
return {
name,
colors: Object.entries(roles).flatMap(([role, hex]) => (hex ? [{ hex, role, official: null }] : [])),
typography: Object.entries(r.styleguide.typography?.headings ?? {}).map(([level, h]) => ({
role: level, family: h.fontFamily, weight: h.fontWeight, size: h.fontSize,
})),
source: { endpoint: "Context.dev Styleguide", kind: "observed", requestedAt: now() },
};
}
// Firecrawl: POST /v2/scrape with formats: ["branding"]; pass data.branding
export function fromFirecrawlBranding(b: {
brandName?: string;
colors?: Record<string, string>;
typography?: { fontFamilies?: Record<string, string> };
images?: { logo?: string };
personality?: { tone?: string };
}): BrandContext {
return {
name: b.brandName ?? "",
voice: b.personality?.tone ? { attributes: [b.personality.tone] } : undefined,
colors: Object.entries(b.colors ?? {}).map(([role, hex]) => ({ hex, role, official: null })),
typography: Object.entries(b.typography?.fontFamilies ?? {}).map(([role, family]) => ({ role, family })),
logos: b.images?.logo ? [{ url: b.images.logo, format: "svg" }] : [],
source: { endpoint: "Firecrawl branding", kind: "observed", requestedAt: now() },
};
}
// Logo.dev Brand API: GET /v2/brands?domain=
export function fromLogoDev(r: {
data: {
name: string;
description?: string;
indexed_at?: string;
colors?: { hex: string }[];
logo?: { url: string; expires_at?: string };
brandmark?: { url: string; expires_at?: string };
};
}): BrandContext {
const d = r.data;
return {
name: d.name,
description: d.description,
colors: d.colors?.map((c) => ({ hex: c.hex, official: null })),
logos: [
...(d.logo ? [{ url: d.logo.url, form: "icon", expiresAt: d.logo.expires_at }] : []),
...(d.brandmark ? [{ url: d.brandmark.url, form: "wordmark", expiresAt: d.brandmark.expires_at }] : []),
],
source: { endpoint: "Logo.dev Brand API", kind: "observed", requestedAt: now(), readAt: d.indexed_at },
};
}
// Rainbrand Brief: GET /briefs/{id} once status is "completed"
export function fromRainbrandBrief(name: string, r: {
data: {
skillId: string | null;
completedAt?: string;
guidance?: string;
assets?: { kind: string; mediaType: string; url: string }[];
};
}): BrandContext {
return {
name,
voice: { guide: r.data.guidance },
logos: r.data.assets?.filter((a) => a.kind === "logo").map((a) => ({ url: a.url, form: "logo" })),
files: r.data.assets?.filter((a) => a.kind !== "logo").map((a) => ({ url: a.url, mediaType: a.mediaType, purpose: a.kind })),
source: {
endpoint: "Rainbrand Brief", kind: "maintained",
requestedAt: r.data.completedAt ?? now(), version: r.data.skillId,
},
};
}
// Rainbrand Brand Skill: GET /brands/{id}/skill
export function fromRainbrandBrandSkill(r: {
data: {
skillId: string;
createdAt?: string;
profile: {
name: string;
colors?: { hex: string; name?: string; group?: string; usage?: string }[];
typography?: { roles?: { name: string; weight?: number; font?: { family?: string } }[] };
};
files?: { path: string; mediaType: string; content?: string; url?: string }[];
};
}): BrandContext {
const p = r.data.profile;
const guide = r.data.files?.find((f) => f.path === "references/copywriting.md")?.content;
return {
name: p.name,
voice: guide ? { guide } : undefined,
colors: p.colors?.map((c) => ({
hex: c.hex, name: c.name, usage: c.usage,
official: c.group ? /official/i.test(c.group) : null,
})),
typography: p.typography?.roles?.map((role) => ({
role: role.name, family: role.font?.family ?? "", weight: role.weight,
})),
files: r.data.files?.filter((f) => f.url).map((f) => ({ url: f.url!, mediaType: f.mediaType, purpose: f.path })),
source: { endpoint: "Rainbrand Brand Skill", kind: "maintained", requestedAt: now(), readAt: r.data.createdAt, version: r.data.skillId },
};
}
Notes
- The names of the guidance files in a Brand Skill come from the brand. The Linear Brand Skill has
references/copywriting.md, but another brand can use a different name. Read the list infilesand do not assume a path. officialis true only from a maintained palette, and only if its group says so. Maintained context is context prepared from sources added for the brand. The observed adapters setofficialto null.- The Firecrawl
logois an inline data URI, not a link. If you need a file, store it as one. - Merge with care. If you combine observed and maintained context, take rules, voice and official colors from the maintained context. Keep both
sourcerecords.
Last updated 11 November 2026