> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mavera.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Blog → Brand Voice

> Pull published blog posts from HubSpot CMS and create a Mavera Brand Voice profile for future content generation

## Scenario

Your 50+ published blog posts represent your established voice. Instead of writing a brand guide manually, you pull blog URLs from HubSpot CMS, pass them into Mavera's Brand Voice endpoint, and get a voice profile for all future content generation.

## Architecture

```mermaid theme={"dark"}
flowchart LR
    A[HubSpot CMS Content API] --> B[Published blog URLs] --> C["POST /brand-voices (URL sources)"] --> D[Brand Voice]
```

## Code

<CodeGroup>
  ```python Python theme={"dark"}
  import os, requests, time
  from openai import OpenAI

  HS = os.environ["HUBSPOT_ACCESS_TOKEN"]
  MV = os.environ["MAVERA_API_KEY"]
  MB = "https://app.mavera.io/api/v1"

  # 1. Published blog posts
  posts = requests.get("https://api.hubapi.com/cms/v3/blogs/posts",
      headers={"Authorization": f"Bearer {HS}"},
      params={"state": "PUBLISHED", "sort": "-publishDate", "limit": 30,
              "property": "name,url,postSummary,postBody"},
  ).json().get("results", [])

  urls = [p["url"] for p in posts if p.get("url")]
  samples = [f"Title: {p.get('name','')}\n{p.get('postSummary','') or p.get('postBody','')[:500]}"
             for p in posts if p.get("postSummary") or p.get("postBody")]

  # 2. Create Brand Voice
  payload = {"name": "HubSpot Blog Voice", "urls": urls[:15]}
  if samples:
      payload["samples"] = ["\n\n---\n\n".join(samples[:10])]

  bv = requests.post(f"{MB}/brand-voices",
      headers={"Authorization": f"Bearer {MV}", "Content-Type": "application/json"},
      json=payload).json()
  print(f"Brand Voice: {bv['id']}")

  # 3. Verify status
  time.sleep(3)
  detail = requests.get(f"{MB}/brand-voices/{bv['id']}",
      headers={"Authorization": f"Bearer {MV}"}).json()
  print(f"Status: {detail.get('status','unknown')}")

  # 4. Test generation
  mavera = OpenAI(api_key=MV, base_url=MB)
  test = mavera.responses.create(model="mavera-1",
      input=[{"role": "user", "content": "Write a 100-word blog intro about AI in marketing."}],
      extra_body={"brand_voice_id": bv["id"]})
  print(f"\n{test.output[0].content[0].text}")
  ```

  ```javascript JavaScript theme={"dark"}
  const OpenAI = require("openai").default;
  const HS = process.env.HUBSPOT_ACCESS_TOKEN;
  const MV = process.env.MAVERA_API_KEY;
  const MB = "https://app.mavera.io/api/v1";

  // 1. Published blog posts
  const posts = await fetch(`https://api.hubapi.com/cms/v3/blogs/posts?state=PUBLISHED&sort=-publishDate&limit=30&property=name,url,postSummary,postBody`,
    { headers: { Authorization: `Bearer ${HS}` } }).then(r => r.json()).then(d => d.results || []);

  const urls = posts.map(p => p.url).filter(Boolean);
  const samples = posts.filter(p => p.postSummary || p.postBody)
    .map(p => `Title: ${p.name}\n${p.postSummary || (p.postBody || "").slice(0, 500)}`);

  // 2. Brand Voice
  const payload = { name: "HubSpot Blog Voice", urls: urls.slice(0, 15) };
  if (samples.length) payload.samples = [samples.slice(0, 10).join("\n\n---\n\n")];
  const bv = await fetch(`${MB}/brand-voices`, {
    method: "POST",
    headers: { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  }).then(r => r.json());
  console.log(`Brand Voice: ${bv.id}`);

  // 3. Test generation
  await new Promise(r => setTimeout(r, 3000));
  const mavera = new OpenAI({ apiKey: MV, baseURL: MB });
  const test = await mavera.responses.create({
    model: "mavera-1",
    input: [{ role: "user", content: "Write a 100-word blog intro about AI in marketing." }],
    extra_body: { brand_voice_id: bv.id },
  });
  console.log(test.output[0].content[0].text);
  ```
</CodeGroup>

## Example Output

```text theme={"dark"}
Brand Voice: bv_blog_8k2m (status: ready)

AI isn't coming for your marketing job — it's coming for the busywork that
keeps you from doing it well. We've seen teams go from 60% reporting to 60%
strategy. That's not marginal — it's fundamental. Here's what we're seeing.
```

## Error Handling

<AccordionGroup>
  <Accordion title="CMS scope required">Blogs endpoint needs `content` scope. Add in HubSpot Settings → Private Apps → Scopes if you get 403.</Accordion>
  <Accordion title="Password-protected blogs">Mavera can't crawl protected pages. Pass `postBody` HTML as `samples` text instead of URLs.</Accordion>
</AccordionGroup>

***

<CardGroup cols={2}>
  <Card title="HubSpot Integration" icon="arrow-left" href="/integrations/hubspot" />

  <Card title="Brand Voice API" icon="microphone" href="/features/brand-voice" />
</CardGroup>
