# Customer brands in your product's jobs

> For products that make work for their customers' brands: an observed lookup at signup, then each customer's own brand context for every job, with code.

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.

```ts
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.

```ts
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);
  }
}
```

```python
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](https://brandcontextapi.com/recipes/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](https://docs.rainbrand.com/getting-started/embed-into-your-product?utm_source=brandcontextapi.com&utm_medium=satellite&utm_campaign=recipe-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.

---

Source: https://brandcontextapi.com/recipes/customer-brands-in-your-product
Last updated: 2026-11-11
Published by The Brand Object. The Brand Object is run by Rainbrand, which makes two of the endpoints described here. Their pages follow the same rules as the others.
