---
name: talkgenai
description: Write a researched, SEO-structured article and publish it to a WordPress, Webflow, Shopify, Wix or Framer site. Use when the user wants a blog post written or published to their own site, a content plan, or a report on Google clicks and visits from AI assistants.
homepage: https://app.talkgen.ai
---

# TalkGenAI

Generate a full SEO article and put it on the user's site.

Agents can already write. What they usually cannot do is get the result onto someone's
WordPress with a hero image, FAQ schema, internal links and a meta description in place.
That is what this API is for.

**Base URL:** `https://app.talkgen.ai`
**Docs:** <https://docs.talkgen.ai> (setup per client, every tool, every error code)
**Auth:** `Authorization: Bearer tgai_...` on every request.

**Where the key is:** look for `TALKGENAI_API_KEY` in the environment or in a local `.env`
file. If it is not there, stop and ask the user for it rather than guessing. They create one
at <https://app.talkgen.ai/connect-agent> (free account, no card, about 3 articles of
starting credit). Treat it like a password: it can publish to the user's live site. Never
print it, log it, or write it into a file you commit.

**Cost:** 5 credits per article, 8 with an image. Reading and publishing are free.

---

## If your client speaks MCP, use that instead

There is a Model Context Protocol server at `https://app.talkgen.ai/mcp`. It exposes the same
commands as native tools, so the client discovers them itself and you can ignore the rest of
this file.

```bash
# Claude Code
claude mcp add --transport http talkgenai https://app.talkgen.ai/mcp \
  --header "Authorization: Bearer $TALKGENAI_API_KEY"
```

```jsonc
// Cursor (.cursor/mcp.json) or VS Code (.vscode/mcp.json)
{
  "mcpServers": {
    "talkgenai": {
      "type": "http",
      "url": "https://app.talkgen.ai/mcp",
      "headers": { "Authorization": "Bearer tgai_your_key_here" }
    }
  }
}
```

Claude Desktop reaches remote servers through `mcp-remote`:

```jsonc
{
  "mcpServers": {
    "talkgenai": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://app.talkgen.ai/mcp",
               "--header", "Authorization: Bearer tgai_your_key_here"]
    }
  }
}
```

Tools: `get_company_brain` · `update_business_context` · `list_sites` · `get_content_plan` · `create_content_plan` · `update_plan_card` ·
`generate_article` · `get_article` · `list_articles` · `refine_article` · `publish_article` ·
`update_live_post` · `check_credits` · `get_rankings` · `get_report`. Same key, same credits, same
draft-by-default rule.

The rest of this file is the plain HTTP version, for agents without an MCP client.

---

## The whole loop

Write an article and leave it as a draft on the user's site:

```bash
# The key lives here. Set it once per shell.
export TALKGENAI_API_KEY=tgai_your_key_here
AUTH="Authorization: Bearer $TALKGENAI_API_KEY"

# 1. Which sites can I publish to?  (copy connection_id from the response)
curl -s -H "$AUTH" https://app.talkgen.ai/api/v1/sites

# 2. Start the article (costs 5 credits, returns immediately)
JOB=$(curl -s -X POST https://app.talkgen.ai/api/v1/articles \
  -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"title":"How to choose a CRM for a 5-person agency","length":"medium"}' \
  | jq -r .job_id)

# 3. Poll every 10s until status is "completed" (45-360s depending on length)
curl -s -H "$AUTH" https://app.talkgen.ai/api/v1/articles/$JOB

# 4. Publish it as a DRAFT to the site from step 1
curl -s -X POST https://app.talkgen.ai/api/v1/articles/$JOB/publish \
  -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"connection_id":"wpc_abc123"}'
```

Every example below assumes `$AUTH` is set as above.

Step 4 creates a **draft**, not a live post. To publish live, pass
`{"connection_id":"wpc_abc123","status":"publish"}` — see [Publishing safely](#publishing-safely).

---

## Read the Marketing Brain first

```bash
curl -s -H "$AUTH" "https://app.talkgen.ai/api/v1/company-brain?domain=example.com"
```

Returns the site's `business_context` (what they sell and do not sell), `brand_voice` (the
voice in force plus every voice with its `id`: pass one as `brand_voice_id` when you
generate), `brand_image` styles and `website`. If `brand_voice.mismatch` is true, the voice
was learned from a different site: tell the user. If `business_context.safe_for_generation`
is false, the text is not written for this site: confirm it before relying on it.

Create or fix the business context (free, replaces the current text, up to 900 characters):

```bash
curl -s -X PUT https://app.talkgen.ai/api/v1/company-brain/business-context \
  -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"domain":"example.com","business_context":"We sell X to Y. We do not sell Z."}'
```

Omit `domain` to set it for the whole account. An empty text is refused unless you pass
`"clear": true`. Show the user the new text before saving. Brand voice and brand image are
trained in the dashboard.

---

## Working the content plan (the better loop)

If the user has a content plan, work from it instead of inventing titles. The plan already
holds the target keyword, the H2 outline, the word count and the strategic reason, and the
card is marked off automatically when you write it.

```bash
# 1. What is still open?
curl -s -H "$AUTH" https://app.talkgen.ai/api/v1/content-plan
```

```json
{
  "has_plan": true,
  "plan_id": "plan_7f3a2b",
  "domain": "example.com",
  "totals": { "planned": 21, "manually_written": 2, "published": 9 },
  "matching": 21,
  "cards": [
    {
      "card_index": 4,
      "title": "How to choose a CRM for a 5-person agency",
      "keyword": "crm for small agency",
      "month": 3,
      "status": "planned",
      "search_intent": "Commercial",
      "content_type": "Comparison",
      "suggested_length": "long",
      "h2_outline": ["Start with cost per seat", "Onboarding time", "Verdict"],
      "strategic_reason": "Buyers compare tools here before choosing.",
      "search_volume": 450,
      "keyword_difficulty": 12
    }
  ]
}
```

```bash
# 2. Write that card. No title needed - the brief comes from the plan.
curl -s -X POST https://app.talkgen.ai/api/v1/articles \
  -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"plan_id":"plan_7f3a2b","card_index":4}'
```

The card is marked `manually_written` when generation finishes and `published` when you
publish it. You do not have to report anything back.

Filters: `?status=planned` · `?month=3` · `?limit=20` · `?include_done=true` ·
`?domain=example.com`. By default only OPEN cards are returned, so you will not repeat work.

### No plan yet? Create one

```bash
curl -s -X POST https://app.talkgen.ai/api/v1/content-plan \
  -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"domain":"example.com","language_code":"en","articles_per_month":2,
       "business_context":"We sell CRM onboarding for agencies. We do not sell software."}'
```

Returns `plan_id` straight away. The plan takes 3 to 5 minutes: poll
`GET /api/v1/content-plan?plan_id=...` every 30 to 60 seconds until it returns cards.
⚠️ It costs **20 credits** (the first plan is free on Starter), needs a paid plan, and some
tiers get only one. **Ask the user before you call it.** If a plan for that site is already
being built, you get that one back and nothing is charged.

### Skip a card, or mark it written by hand

```bash
curl -s -X PATCH https://app.talkgen.ai/api/v1/content-plan/plan_7f3a2b/cards/4 \
  -H "$AUTH" -H "Content-Type: application/json" -d '{"status":"skipped"}'
```

`status`: `skipped` (leaves the list) · `written` (the user wrote it themselves) · `open`
(undo either). You can also send `title`, `keyword` or `month` (1 to 12). Free. A published
card can be skipped (the post stays live) but not edited. Scheduled and generating cards are
changed from the dashboard only.

Scheduling and autopilot stay in the dashboard.

---

## Commands

### List sites

```bash
curl -s -H "$AUTH" https://app.talkgen.ai/api/v1/sites
```

```json
{
  "sites": [
    {
      "connection_id": "wpc_9f2c41d7a8b3e05c6d1f2a4b",
      "site_url": "https://example.com",
      "site_name": "Example Blog",
      "platform": "wordpress",
      "auth_method": "plugin_token"
    }
  ],
  "count": 1
}
```

If `count` is 0, the user has not connected a site yet. Send them to
<https://app.talkgen.ai/dashboard> to connect one. You can still generate articles without
a site and hand back the HTML.

### Generate an article

```bash
curl -s -X POST https://app.talkgen.ai/api/v1/articles \
  -H "$AUTH" -H "Content-Type: application/json" \
  -d '{
    "title": "How to choose a CRM for a 5-person agency",
    "topic": "Practical buying guide for small agencies, focused on cost per seat and onboarding time",
    "length": "medium",
    "image": true,
    "focus_keyword": "crm for small agency"
  }'
```

| Field | Default | Notes |
|---|---|---|
| `title` | required | The article's headline. |
| `topic` | falls back to `title` | The angle or brief. Worth writing: it drives the whole piece. |
| `length` | `medium` | `short` (~500w) · `medium` (~900w) · `long` (~1400w) · `very_large` · `pillar` |
| `image` | required | Hero image, +3 credits. If omitted the call returns `needs_input` and creates nothing: ask the user, then repeat with true or false. The saved brand voice and brand image style are applied automatically. |
| `focus_keyword` | none | Target keyword for on-page SEO. |
| `instructions` | none | Free text: tone, audience, things to avoid. |
| `include_faq` | `true` | Appends an FAQ block with FAQ schema. |
| `include_external_link` | `false` | Cites outbound sources. |
| `brand_voice_id` | none | Use a brand voice the user trained in the dashboard. |
| `plan_id` + `card_index` | none | Write a content-plan card. Fills in title, keyword, length, outline and angle, and marks the card off. |

```json
{
  "success": true,
  "job_id": "job_1a2b3c4d5e6f",
  "status": "pending",
  "estimated_time": 90,
  "credits_remaining": 26,
  "poll_url": "https://app.talkgen.ai/api/v1/articles/job_1a2b3c4d5e6f"
}
```

### Poll for the result

```bash
curl -s -H "$AUTH" https://app.talkgen.ai/api/v1/articles/job_1a2b3c4d5e6f
```

While it runs:

```json
{ "job_id": "job_1a2b3c4d5e6f", "status": "processing", "progress": 30 }
```

When it finishes:

```json
{
  "job_id": "job_1a2b3c4d5e6f",
  "status": "completed",
  "progress": 100,
  "title": "How to Choose a CRM for a 5-Person Agency",
  "html": "<h2>Start with cost per seat</h2><p>...",
  "seo_title": "How to Choose a CRM for a Small Agency (2026 Guide)",
  "meta_description": "A practical buying guide for agencies under 10 people...",
  "focus_keyword": "crm for small agency",
  "word_count": 912,
  "has_image": true
}
```

`status` is one of `pending`, `processing`, `completed`, `failed`. On `failed` there is an
`error` field. **Poll every 5 to 10 seconds, not continuously** — free endpoints are limited
to roughly 60 requests a minute per key.

### Publish

```bash
curl -s -X POST https://app.talkgen.ai/api/v1/articles/job_1a2b3c4d5e6f/publish \
  -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"connection_id":"wpc_9f2c41d7a8b3e05c6d1f2a4b"}'
```

```json
{
  "success": true,
  "post_id": 4471,
  "post_url": "https://example.com/?p=4471",
  "edit_url": "https://example.com/wp-admin/post.php?post=4471&action=edit",
  "status": "draft",
  "note": "Saved as a draft. It is not public yet. Open edit_url to review, or publish with {\"status\": \"publish\"}."
}
```

The hero image is uploaded and set as the featured image automatically. Publishing the same
article to the same site twice updates the existing post instead of creating a duplicate, so
a retry after a timeout is safe.

### Find earlier articles

```bash
curl -s -H "$AUTH" "https://app.talkgen.ai/api/v1/articles?limit=10&search=crm&published=true"
```

The workspace's finished articles, newest first: `job_id`, `title`, `word_count`,
`published_url`, `published_status`, and `plan_link` when written from a plan card. Free.
Then `GET /api/v1/articles/{job_id}` for the HTML.

### Refine an article

```bash
curl -s -X POST https://app.talkgen.ai/api/v1/articles/job_1a2b3c4d5e6f/refine \
  -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"instruction":"Shorten the intro and add a section on pricing."}'
```

Costs **2 credits**, paid plans only. Keeps the brand voice. Poll
`GET /api/v1/articles/{job_id}` until `refine.status` is `done` (or `failed`, which refunds);
the article's `html` is then the new version. One refine at a time per article.

### Update the live post after a refine

```bash
curl -s -X POST -H "$AUTH" \
  https://app.talkgen.ai/api/v1/articles/job_1a2b3c4d5e6f/update-live-post
```

Pushes the current title and text into the post the article is already on. Free. The post
keeps its URL and its status: a live post stays live, a draft stays a draft. Use this, not
`/publish`, for an article that is already published.

### Check credits

```bash
curl -s -H "$AUTH" https://app.talkgen.ai/api/v1/credits
```

```json
{
  "plan": "free",
  "credits_remaining": 24,
  "bonus_credits": 11,
  "total_available": 35,
  "costs": { "article": 5, "article_with_image": 8, "content_plan": 20, "refine": 2, "publish": 0, "read": 0 }
}
```

### Rankings (Growth plan and above)

```bash
curl -s -H "$AUTH" "https://app.talkgen.ai/api/v1/rankings?site=https://example.com"
```

Returns Google Search Console positions, clicks and impressions for the connected site.
Useful for deciding what to write next rather than guessing. Returns `403
plan_upgrade_required` on Free and Starter.

Add `sort=low_traffic` to get `low_traffic_pages`: pages Google shows but people rarely
click, fewest clicks first (`min_impressions`, default 20; `limit`, default 20). These are
the pages worth refreshing.

---

### Refresh an existing WordPress page (same URL)

For a page that is already live, including pages TalkGenAI did not write. Needs the
TalkGenAI plugin 2.7.12 or later. Page-builder pages, forms, shortcodes and the home page
are refused.

```bash
# 1. Look it up (free, read-only). Stop if safe_to_replace is false and tell the user why.
curl -s -X POST -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/blog/old-post/"}' \
  https://app.talkgen.ai/api/v1/pages/inspect

# 2. Write the new version. First call WITHOUT "image": it returns the image question
#    for this page and charges nothing. Ask the user, then send image true or false.
curl -s -X POST -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/blog/old-post/","image":false,"instructions":"update for 2026"}' \
  https://app.talkgen.ai/api/v1/pages/refresh
# -> {"job_id": "..."}  5 credits, 8 with image. Poll GET /api/v1/articles/{job_id}.

# 3. Replace the live page. First call WITHOUT confirm_overwrite returns the question.
#    Send confirm_overwrite true ONLY after the user explicitly says yes.
curl -s -X POST -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"confirm_overwrite":true}' \
  https://app.talkgen.ai/api/v1/articles/JOB_ID/replace-page
```

The refresh keeps the page's subject, language, business facts and its own internal links,
and does not invent numbers. Replace keeps the URL and also writes the new SEO title and
meta description. To change only the SEO title and description (works on page-builder pages
too): `POST /api/v1/pages/seo-meta` with `url`, `seo_title`, `meta_description` and the same
`confirm_overwrite` rule.

---

### Reports (paid plans)

```bash
curl -s -H "$AUTH" "https://app.talkgen.ai/api/v1/reports/performance?days=30"
```

`{report}`: `performance` (`days` 7 or 30) · `monthly` · `ai_traffic` (`month=YYYY-MM`) ·
`ai_impact` · `ai_funnel` · `ai_visibility` (`weeks`). Same numbers and plan limits as the
dashboard's Measure tab. Use them to choose what to refresh or write next.

---

## Publishing safely

**`status` defaults to `draft`.** You have to ask for a live post:

```json
{ "connection_id": "wpc_...", "status": "publish" }
```

This is deliberate, and it overrides whatever default the user set in their dashboard.
Publishing to someone's real website is not reversible in the way a draft is — the post can
be indexed, shared or seen before anyone notices a mistake.

**If you are an agent running without a human watching each step, publish drafts.** Give the
user the `edit_url` and let them press the button. Only pass `status: "publish"` when the
user has actually asked for the post to go live.

A workspace can also switch live publishing off for agents entirely. If it has,
`status: "publish"` returns `403 live_publish_disabled` and there is no way around it. Save
a draft and tell the user.

---

## Errors

Every error is JSON with a `code` and a `message` you can show the user.

| Status | `code` | What to do |
|---|---|---|
| 401 | `unauthorized` | Key is missing, malformed or revoked. Get a new one at `/connect-agent`. |
| 403 | `insufficient_credits` | Out of credits. The response has `credit_cost` and `upgrade_url`. |
| 403 | `domain_mismatch` | You are using a WordPress *plugin* key, which is locked to one site. Create an **agent key** at `/connect-agent` instead. |
| 403 | `plan_upgrade_required` | Feature is on a higher plan. `upgrade_url` is in the response. |
| 400 | `not_ready` | The article has not finished generating. Keep polling. |
| 404 | `site_not_found` | Bad `connection_id`. Re-read `GET /api/v1/sites`. |
| 404 | `plan_not_found` / `card_not_found` | Re-read `GET /api/v1/content-plan`. |
| 409 | `card_already_published` | That card is done. Pick another. |
| 200 | `needs_input` | `image` was not set. Nothing was created or charged. Ask the user whether they want a hero image (+3 credits), then repeat the call with `image` true or false. |
| 409 | `duplicate_article` | This keyword or title was already written in the workspace, or the site already has that page. Nothing was charged. The `existing` field shows it. Publish or `refine_article` that one. `allow_duplicate: true` only if the user explicitly wants a second. |
| 409 | `duplicate_slug` | On publish: the site already has a page at that URL. Same handling; `allow_duplicate: true` to override. |
| 409 | `card_scheduled` | The card is scheduled. Tell the user to change its status in the dashboard first (Content Plan), then retry. Do not work around it by writing the article without the card. |
| 409 | `card_already_covered` | That card was written by hand. Do not write it again; use `refine_article` + `update_live_post`, or pick another card. |
| 409 | `card_locked` | The card is published, scheduled or generating. Change it in the dashboard. |
| 403 | `plan_limit_reached` | The tier's content-plan allowance is used up. Show the message. |
| 400 | `too_long` | Business context is over 900 characters. Shorten it. |
| 403 | `viewer_read_only` | A viewer key cannot edit the Marketing Brain. |
| 409 | `refine_in_progress` | Wait for the current refine to finish, then send the next one. |
| 400 | `not_published` | `update-live-post` needs an article that is on a site. Publish it first. |
| 403 | `live_publish_disabled` | This workspace forbids agents publishing live. Save a draft instead. |
| 403 | `scope_missing` | This key was created without that permission (articles or publish). Tell the user; they can create a key with it in the dashboard. |
| 403 | `agent_key_not_allowed_here` | Agent keys only work on `/api/v1/*` and `/mcp`. |
| 429 | `rate_limited` | Slow the polling down. Retry after 60s. |

A free account cannot spend its monthly credits on images: image generation has to be
covered by bonus credits. If `image: true` returns `insufficient_credits` on a free plan
while `total_available` looks sufficient, generate without the image, or upgrade.

---

## Notes

- Generation is asynchronous. There is no synchronous "give me the article now" call, and a
  `long` or `pillar` article genuinely takes several minutes.
- The article HTML is returned to you as well as published, so you can post it somewhere
  else, save it to a file, or show it to the user before publishing.
- WordPress works for every account. Webflow, Shopify, Wix and Framer are on for every account too (the user connects them under Integrations in the dashboard); all five use the same `connection_id`. On Framer, a live publish publishes the whole Framer site, so when the project has other unpublished changes the article is saved but not put live, and the response `note` says so: tell the user. `GET /api/v1/sites` lists only the sites the account has connected.
- Read endpoints are free but limited to about 60 requests per minute per key. That limit is
  best effort, not a hard guarantee.
