Put the brand in the system prompt as instructions, not as a pasted response. A model does better with "Write plainly. Never sound like breathless hype." than with a JSON object that it must interpret. Include only the fields that the task needs. Say where the context came from.
Which fields go in
| Task | Include | Leave out |
|---|---|---|
| Copy: emails, ads, landing pages | Name, description, voice, rules, the products that the copy is about | Colors, fonts, competitors, company data |
| Visual work: images, slides, pages | Rules, colors with roles, type, the logo file to place | Long descriptions, competitors |
| Research about the company | Description, positioning, competitors, dates | Logo files |
TypeScript
type Context = {
meta: { canonical_name: string; resolved_at: string };
identity: { description?: string };
brand: { voice?: { summary?: string; attributes?: string[]; avoid?: string[] } };
};
export async function brandPrompt(domain: string): Promise<string> {
const res = await fetch(`https://api.brandfetch.io/v2/context/${domain}`, {
headers: {
Authorization: `Bearer ${process.env.BRANDFETCH_API_KEY}`,
Accept: "application/json",
},
});
if (!res.ok) throw new Error(`Brand Context API answered ${res.status}`);
const c = (await res.json()) as Context;
const voice = c.brand.voice ?? {};
return [
`You are writing for ${c.meta.canonical_name}. ${c.identity.description ?? ""}`,
"",
"Rules. Follow these exactly:",
...(voice.avoid ?? []).map((tone) => `- Never sound like ${tone}.`),
"",
"Voice:",
voice.summary ?? "",
`Sounds: ${(voice.attributes ?? []).join(", ")}`,
"",
`Source: read from ${c.meta.canonical_name}'s public website on ${c.meta.resolved_at.slice(0, 10)}.`,
"Don't state prices, numbers or claims from it as fact without a check.",
].join("\n");
}
Python
import os
import httpx
def brand_prompt(domain: str) -> str:
res = httpx.get(
f"https://api.brandfetch.io/v2/context/{domain}",
headers={
"Authorization": f"Bearer {os.environ['BRANDFETCH_API_KEY']}",
"Accept": "application/json",
},
timeout=30,
)
res.raise_for_status()
c = res.json()
voice = c["brand"].get("voice") or {}
lines = [
f"You are writing for {c['meta']['canonical_name']}. {c['identity'].get('description', '')}",
"",
"Rules. Follow these exactly:",
*[f"- Never sound like {tone}." for tone in voice.get("avoid", [])],
"",
"Voice:",
voice.get("summary", ""),
f"Sounds: {', '.join(voice.get('attributes', []))}",
"",
f"Source: read from the public website on {c['meta']['resolved_at'][:10]}.",
"Don't state prices, numbers or claims from it as fact without a check.",
]
return "\n".join(lines)
For linear.app, the rules section contains "Never sound like breathless hype." and "Never sound like folksy banter." These two lines are everything that the response tells a model not to do.
With a maintained Brief
Maintained context is context prepared from sources added for the brand. The guidance in a Rainbrand Brief is Markdown for one task. Put it in the prompt without changes, below one line that says what the task is. Add the version from skillId to the source line. The system prompt template shows both versions filled in. JSON or Markdown? tells you when to keep the structure.
Before you pass Brandfetch content to a model, read the Brandfetch terms. The Brand Context API page gives the details.
Last updated 11 November 2026