> ## 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 Content → Brand Voice Extraction

> Pull published WordPress posts, strip HTML, and create a Mavera brand voice profile capturing your blog's tone and editorial style

### Scenario

Your WordPress blog has years of published content that embodies your brand's tone, vocabulary, and editorial style. You pull the most recent published posts, strip HTML to extract clean text samples, send them to Mavera Brand Voices to create a voice profile, then verify the voice with a test generation using the OpenAI SDK pattern.

### Architecture

```mermaid theme={"dark"}
flowchart LR
    A["WordPress GET /wp-json/wp/v2/posts"] --> B["Strip HTML from content"]
    B --> C["POST /api/v1/brand-voices"]
    C --> D["Verify brand voice status"]
    D --> E["Test generation with OpenAI SDK"]
```

### Code

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

  WP, MV = os.environ["WORDPRESS_URL"], os.environ["MAVERA_API_KEY"]
  MB = "https://app.mavera.io/api/v1"
  MH = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
  strip_html = lambda h: re.sub(r"\s+", " ", re.sub(r"<[^>]+>", "", h or "")).strip()

  resp = requests.get(f"{WP}/wp-json/wp/v2/posts", params={
      "status": "publish", "per_page": 50, "orderby": "date", "order": "desc",
      "_fields": "id,title,content,date,slug"
  })
  resp.raise_for_status()
  posts = resp.json()
  print(f"Fetched {len(posts)} published posts")

  samples = []
  for p in posts:
      body = strip_html(p["content"]["rendered"])
      if len(body) > 100:
          samples.append(f"{p['title']['rendered']}\n\n{body[:2000]}")
  print(f"Prepared {len(samples)} samples")

  voice_resp = requests.post(f"{MB}/brand-voices", json={
      "name": "WordPress Blog Voice", "samples": samples[:20],
  }, headers=MH)
  voice_resp.raise_for_status()
  voice = voice_resp.json()
  voice_id = voice["id"]
  print(f"Brand voice created: {voice_id} — status: {voice.get('status', 'ready')}")

  client = OpenAI(api_key=MV, base_url=MB)
  test = client.responses.create(model="mavera-default", input=[
      {"role": "system", "content": "Write in the brand voice provided."},
      {"role": "user", "content": "Write a 2-sentence intro for a blog post about remote work productivity."},
  ], extra_body={"brand_voice_id": voice_id})
  print(f"\nTest generation:\n{test.output[0].content[0].text}")
  ```

  ```javascript JavaScript theme={"dark"}
  const WP = process.env.WORDPRESS_URL, MV = process.env.MAVERA_API_KEY;
  const MB = "https://app.mavera.io/api/v1";
  const MH = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };
  const stripHtml = h => (h || "").replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();

  (async () => {
    const resp = await fetch(`${WP}/wp-json/wp/v2/posts?status=publish&per_page=50&orderby=date&order=desc&_fields=id,title,content,date,slug`);
    const posts = await resp.json();
    console.log(`Fetched ${posts.length} published posts`);

    const samples = posts.map(p => ({ title: p.title.rendered, body: stripHtml(p.content.rendered) }))
      .filter(s => s.body.length > 100).map(s => `${s.title}\n\n${s.body.slice(0, 2000)}`);

    const vResp = await fetch(`${MB}/brand-voices`, { method: "POST", headers: MH,
      body: JSON.stringify({ name: "WordPress Blog Voice", samples: samples.slice(0, 20) }) });
    const voice = await vResp.json();
    console.log(`Brand voice created: ${voice.id} — status: ${voice.status || "ready"}`);

    const test = await fetch(`${MB}/responses`, { method: "POST", headers: MH,
      body: JSON.stringify({ model: "mavera-default", brand_voice_id: voice.id, input: [
        { role: "system", content: "Write in the brand voice provided." },
        { role: "user", content: "Write a 2-sentence intro for a blog post about remote work productivity." },
      ] }) });
    const result = await test.json();
    console.log(`\nTest generation:\n${result.output[0].content[0].text}`);
  })();
  ```
</CodeGroup>

### Example Output

```json theme={"dark"}
{
  "id": "bv_4e8a1c3f",
  "name": "WordPress Blog Voice",
  "status": "ready",
  "sample_count": 18,
  "voice_attributes": { "tone": "conversational-authoritative", "vocabulary_level": "accessible-professional" },
  "created_at": "2026-03-17T10:15:22Z"
}
```

```text theme={"dark"}
Test generation:
You don't need a corner office to do your best work — you need the right systems.
Here's how top remote teams are rethinking productivity without burning out.
```

### Error Handling

<AccordionGroup>
  <Accordion title="REST API returns 404 or HTML instead of JSON">
    If `/wp-json/wp/v2/posts` returns a 404 or an HTML page, your permalink structure is set to "Plain." Go to Settings → Permalinks and choose any other option (Post name is recommended). Flush rewrite rules by saving. Some security plugins (Wordfence, iThemes) block the REST API — whitelist your server's IP or disable the REST API restriction.
  </Accordion>

  <Accordion title="Mavera 422 — insufficient samples">
    Brand voice creation requires at least 3 text samples with meaningful content. If your blog has few posts, supplement with page content (`/wp-json/wp/v2/pages`) or manually add representative copy. Each sample should be at least 100 characters of clean text.
  </Accordion>
</AccordionGroup>
