If your product makes things for your customers, for example ads, pages, emails or decks, every job needs the brand of that customer. A domain lookup gets you started at signup. The same customer then comes back for many jobs. The question changes from "what does their website say" to "what has this customer told us about their brand". You must keep that context in a place where you can get it for each task.
Observed context is context read from a company's public website. Maintained context is context prepared from sources added for the brand. This recipe suggests one setup, and no vendor sets it as a rule. Use observed context for the first screen and for customers that you cannot ask. For each returning customer, keep a maintained brand, built from the sources that they give you.
At signup: an observed lookup
At signup, all you have is a work email. Ask the Brandfetch Brand API or the Context.dev Brand endpoint for the logo and colors. Show them to the customer, so that the customer can correct them.
async function prefill(email: string) {
const domain = email.split("@")[1];
const res = await fetch(`https://api.brandfetch.io/v2/brands/domain/${domain}`, {
headers: { Authorization: `Bearer ${process.env.BRANDFETCH_API_KEY}` },
});
if (!res.ok) return null;
const b = await res.json();
return {
name: b.name,
logo: b.logos?.find((l: { theme: string }) => l.theme === "light")?.formats[0]?.src,
colors: b.colors?.map((c: { hex: string }) => c.hex),
};
}
For Linear, the observed endpoints gave four different main colors. Let the customer pick. The Context.dev documentation gives the same advice about company details that are not certain.
Once they are a customer: their brand, per job
Rainbrand maintains a customer's brand from the sources they give you and supplies the guidance and assets relevant to each task. Your product still writes the prompt and chooses the model. Add the brand one time, from their website and any files. Then request a Brief for every job.
const RB = "https://www.rainbrand.com/api/v1";
const headers = { "x-api-key": process.env.RAINBRAND_API_KEY!, "Content-Type": "application/json" };
// Once per customer: add their brand from their website (and files, if they have them).
export async function addBrand(website: string) {
const res = await fetch(`${RB}/brands`, {
method: "POST",
headers,
body: JSON.stringify({ sources: [{ kind: "website", url: website }] }),
});
return (await res.json()).data.id as string; // the customer's brand ID
}
// For every job: request a Brief for this task and wait for it.
export async function contextForJob(brandId: string, task: string, jobId: string) {
const res = await fetch(`${RB}/briefs`, {
method: "POST",
headers: { ...headers, "idempotency-key": `brief-${jobId}` }, // safe to retry
body: JSON.stringify({ brandSessionId: brandId, task }),
});
const { data } = await res.json();
for (;;) {
const poll = await fetch(`${RB}/briefs/${data.id}?wait=true&timeout=120`, { headers });
const brief = (await poll.json()).data;
if (brief.status === "completed") return brief; // guidance, assets, skillId
if (brief.status === "failed") throw new Error(brief.error.message);
}
}
import os
import httpx
RB = "https://www.rainbrand.com/api/v1"
HEADERS = {"x-api-key": os.environ["RAINBRAND_API_KEY"]}
def context_for_job(brand_id: str, task: str) -> dict:
created = httpx.post(f"{RB}/briefs", headers=HEADERS, json={"brandSessionId": brand_id, "task": task}).json()
while True:
brief = httpx.get(f"{RB}/briefs/{created['data']['id']}", params={"wait": "true", "timeout": 120},
headers=HEADERS, timeout=130).json()["data"]
if brief["status"] == "completed":
return brief
if brief["status"] == "failed":
raise RuntimeError(brief["error"]["message"])
Put guidance into the prompt for your generator. Place the files in assets, and do not ask the model to draw them. Store skillId with the output (see Cache, refresh and pin).
If your customers already use Rainbrand, they can connect their own accounts to your product with OAuth. Then you do not hold an API key for them. Embed Rainbrand into your product gives both setups.
Keep both
The observed lookup stays useful after signup. You can use it for prospects and for customers who never add sources. It also shows what their public site says today. Keep the two kinds of context apart in your data. If they disagree, the customer's own sources win.
Last updated 11 November 2026