> ## 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.

# WordPress → Mavera Publish Pipeline

> Generate a brand-voice-consistent article with Mavera using an existing brand voice and persona, then publish it directly back to WordPress as a draft

### Scenario

Full round-trip: generate a brand-voice-consistent article with Mavera using an existing brand voice and persona, then publish it directly back to WordPress as a draft. This pipeline covers content ideation through to draft creation with proper auth, categories, and metadata.

### Architecture

```mermaid theme={"dark"}
flowchart LR
    A["GET /api/v1/brand-voices"] --> C["POST /api/v1/responses"]
    B["GET /api/v1/personas"] --> C
    C --> D["Generate article"]
    D --> E["POST /wp-json/wp/v2/posts (draft)"]
    E --> F["Editorial review"]
```

### Code

<CodeGroup>
  ```python Python theme={"dark"}
  import os, json, requests
  from base64 import b64encode

  WP, WP_USER, WP_PASS = os.environ["WORDPRESS_URL"], os.environ["WORDPRESS_USER"], os.environ["WORDPRESS_APP_PASSWORD"]
  MV = os.environ["MAVERA_API_KEY"]
  MB = "https://app.mavera.io/api/v1"
  MH = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
  wp_token = b64encode(f"{WP_USER}:{WP_PASS}".encode()).decode()
  WP_H = {"Authorization": f"Basic {wp_token}", "Content-Type": "application/json"}

  voices = requests.get(f"{MB}/brand-voices", headers=MH).json()
  voice_id = voices.get("brand_voices", voices.get("data", []))[0]["id"]
  personas = requests.get(f"{MB}/personas", headers=MH).json()
  persona_id = personas.get("personas", personas.get("data", []))[0]["id"]
  print(f"Voice: {voice_id} | Persona: {persona_id}")

  gen = requests.post(f"{MB}/responses", json={
      "model": "mavera-default", "brand_voice_id": voice_id, "persona_id": persona_id,
      "input": [
          {"role": "system", "content": "Content writer. Return JSON: title, body (HTML with h2/p/ul tags), excerpt (2 sentences), suggested_category."},
          {"role": "user", "content": "Write a 600-word article about building a personal brand on LinkedIn in 2026. Include actionable tips, trends, and a strong hook."},
      ],
      "response_format": {"type": "json_object"},
  }, headers=MH)
  gen.raise_for_status()
  article = json.loads(gen.json()["choices"][0]["message"]["content"])
  print(f"Generated: {article['title']}")

  cats_resp = requests.get(f"{WP}/wp-json/wp/v2/categories", params={"search": article.get("suggested_category", ""), "per_page": 5})
  cat_id = cats_resp.json()[0]["id"] if cats_resp.ok and cats_resp.json() else 1

  post_resp = requests.post(f"{WP}/wp-json/wp/v2/posts", json={
      "title": article["title"], "content": article["body"],
      "excerpt": article.get("excerpt", ""), "status": "draft", "categories": [cat_id],
  }, headers=WP_H)
  post_resp.raise_for_status()
  post = post_resp.json()
  print(f"Draft created: {post['link']} (ID: {post['id']}, status: {post['status']})")
  ```

  ```javascript JavaScript theme={"dark"}
  const WP = process.env.WORDPRESS_URL, WP_USER = process.env.WORDPRESS_USER;
  const WP_PASS = process.env.WORDPRESS_APP_PASSWORD, MV = process.env.MAVERA_API_KEY;
  const MB = "https://app.mavera.io/api/v1";
  const MH = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };
  const wpToken = btoa(`${WP_USER}:${WP_PASS}`);
  const WP_H = { Authorization: `Basic ${wpToken}`, "Content-Type": "application/json" };

  (async () => {
    const voices = await (await fetch(`${MB}/brand-voices`, { headers: MH })).json();
    const voiceId = (voices.brand_voices || voices.data || [])[0]?.id;
    const personas = await (await fetch(`${MB}/personas`, { headers: MH })).json();
    const personaId = (personas.personas || personas.data || [])[0]?.id;
    console.log(`Voice: ${voiceId} | Persona: ${personaId}`);

    const gen = await fetch(`${MB}/responses`, { method: "POST", headers: MH,
      body: JSON.stringify({ model: "mavera-default", brand_voice_id: voiceId, persona_id: personaId,
        input: [
          { role: "system", content: "Content writer. Return JSON: title, body (HTML h2/p/ul), excerpt, suggested_category." },
          { role: "user", content: "600-word article: building a personal brand on LinkedIn in 2026. Tips, trends, strong hook." },
        ], response_format: { type: "json_object" } }) });
    const article = JSON.parse((await gen.json()).output[0].content[0].text);
    console.log(`Generated: ${article.title}`);

    const catsResp = await fetch(`${WP}/wp-json/wp/v2/categories?search=${encodeURIComponent(article.suggested_category || "")}&per_page=5`);
    const cats = await catsResp.json();
    const catId = cats.length ? cats[0].id : 1;

    const postResp = await fetch(`${WP}/wp-json/wp/v2/posts`, { method: "POST", headers: WP_H,
      body: JSON.stringify({ title: article.title, content: article.body, excerpt: article.excerpt || "", status: "draft", categories: [catId] }) });
    const post = await postResp.json();
    console.log(`Draft created: ${post.link} (ID: ${post.id}, status: ${post.status})`);
  })();
  ```
</CodeGroup>

### Example Output

```json theme={"dark"}
{
  "id": 1247,
  "title": { "rendered": "LinkedIn Personal Branding in 2026: 7 Tactics That Actually Work" },
  "status": "draft",
  "link": "https://blog.example.com/?p=1247",
  "excerpt": {
    "rendered": "Your LinkedIn profile isn't a resume — it's a landing page. Here's how to turn it into a magnet for the opportunities you actually want."
  },
  "categories": [14],
  "date": "2026-03-17T10:42:15"
}
```

### Error Handling

<AccordionGroup>
  <Accordion title="WordPress 401 — invalid Application Password">
    Write operations require Basic auth. Ensure you're encoding `username:application_password` (not your login password) with base64. Application Passwords contain spaces — include them. If you regenerated the password, the old one is revoked immediately. Test with: `curl -u "user:xxxx xxxx xxxx" https://yoursite.com/wp-json/wp/v2/users/me`.
  </Accordion>

  <Accordion title="WordPress 403 — insufficient permissions">
    The authenticated user must have the `publish_posts` capability (Editor or Administrator role). Contributors can create posts but not publish them. The code sets `status: "draft"` which requires the `edit_posts` capability — Author role or above.
  </Accordion>
</AccordionGroup>
