If the whole response goes straight into a prompt, use Markdown. If your code picks fields, merges sources, or stores them to compare later, use JSON. Most endpoints offer only JSON. Two endpoints give you Markdown in some form.
| Endpoint | Formats |
|---|---|
| Brandfetch Brand Context API | JSON by default. Accept: text/markdown returns the same profile as a Markdown document, without the empty fields |
| Rainbrand Brief | JSON, with the task's guidance as a Markdown string in guidance |
| Rainbrand Brand Skill | JSON, with each guidance file's Markdown in files[].content |
| Brandfetch Brand API, Context.dev, Firecrawl, Logo.dev | JSON only |
Asking Brandfetch for Markdown
curl https://api.brandfetch.io/v2/context/linear.app \
-H "Authorization: Bearer $BRANDFETCH_API_KEY" \
-H "Accept: text/markdown"
The Brandfetch documentation says that the Markdown starts with the brand's name as a heading. The domain and the date of the context come next. The sections that follow are the same as in the JSON, in the same order.
When Markdown is the wrong choice
- You need to drop fields. For example, a cold email does not need the competitor list. To remove a section from Markdown, you must parse the Markdown again.
- You combine sources. In JSON, a merge of the Brandfetch voice with the Context.dev colors takes a few lines. In prose, the same merge is difficult.
- You store the context and compare it later. Dates and versions are easier to read from keys than from a heading.
Turning JSON into Markdown yourself
export function toMarkdown(name: string, sections: Record<string, string | string[] | undefined>): string {
const out = [`# ${name}`];
for (const [title, value] of Object.entries(sections)) {
if (!value || (Array.isArray(value) && value.length === 0)) continue;
out.push("", `## ${title}`, "");
out.push(Array.isArray(value) ? value.map((v) => `- ${v}`).join("\n") : value);
}
return out.join("\n");
}
// toMarkdown("Linear", { Voice: c.brand.voice?.summary, Avoid: c.brand.voice?.avoid })
def to_markdown(name: str, sections: dict) -> str:
out = [f"# {name}"]
for title, value in sections.items():
if not value:
continue
out += ["", f"## {title}", ""]
out.append("\n".join(f"- {v}" for v in value) if isinstance(value, list) else value)
return "\n".join(out)
Leave out empty sections. A model reads a heading with nothing under it as "this brand has no rules". An empty field does not mean that.
Last updated 11 November 2026