# API reference

> All public endpoints: list posts, get post, RSS, JSON Feed, sitemap, errors. With request/response shapes and per-language snippets.

The Mentionwell public API is small, RESTful, and read-only. Two endpoints cover almost every integration; three more give you syndication feeds.

## Base URL

```text
https://app.mentionwell.com
```

Self-hosted deployments use their own origin. Set this in `MENTIONWELL_API_URL` on your destination site.

## Conventions

- **Auth:** `Authorization: Bearer <site-api-key>` on the `/api/public/*` endpoints. Feeds and sitemaps are open.
- **Content type:** `application/json` (or `application/rss+xml` / `application/feed+json` / `application/xml` for feeds and sitemap).
- **Timestamps:** ISO-8601, UTC (Z-suffixed).
- **Caching:**
  - Post endpoints (`/api/public/...`) return `Cache-Control: private, max-age=60, stale-while-revalidate=300, stale-if-error=86400` plus a weak `ETag`. Send `If-None-Match` on subsequent requests to get a `304 Not Modified` and skip the body.
  - Feed and sitemap endpoints (`/api/sites/.../feed.xml`, `feed.json`, `sitemap.xml`) return `Cache-Control: public, max-age=300, s-maxage=600`.
- **CORS:** `Access-Control-Allow-Origin: *`, with `OPTIONS` preflight support.
- **Envelope:** every JSON response is `{ "ok": boolean, ... }`. On error, `ok: false` with a `message` field.

---

## List posts

```text
GET /api/public/{siteSlug}/posts
```

Returns up to `limit` published posts for one site, newest first. Use this for your `/blog` index page.

### Path parameters

| Name | Type | Description |
|---|---|---|
| `siteSlug` | string (required) | The slug of your site, as shown in the dashboard URL. |

### Query parameters

| Name | Type | Description |
|---|---|---|
| `limit` | integer (optional) | Maximum posts to return. Default `20`, max `100`. Clamped server-side. |

### Example — cURL

```bash
curl https://app.mentionwell.com/api/public/your-site-slug/posts?limit=12 \
  -H "Authorization: Bearer <MENTIONWELL_API_KEY>"
```

### Example — JavaScript

```ts
const res = await fetch(
  `${process.env.MENTIONWELL_API_URL}/api/public/${siteSlug}/posts?limit=12`,
  {
    headers: { Authorization: `Bearer ${process.env.MENTIONWELL_API_KEY}` },
    next: { revalidate: 300, tags: ["mentionwell:posts"] }
  }
);
const { posts } = await res.json();
```

### Example — Python

```python
import os, httpx

r = httpx.get(
  f"{os.environ['MENTIONWELL_API_URL']}/api/public/{site_slug}/posts",
  params={"limit": 12},
  headers={"Authorization": f"Bearer {os.environ['MENTIONWELL_API_KEY']}"}
)
posts = r.json()["posts"]
```

### Response

```json
{
  "ok": true,
  "site": { "slug": "your-site-slug", "name": "Your Site", "domain": "yoursite.com" },
  "posts": [
    {
      "slug": "five-questions-to-ask-before-you-buy",
      "title": "Five Questions to Ask Before You Buy",
      "excerpt": "A short, scannable summary that drops into your card UI.",
      "metaDescription": "150-character SEO description.",
      "featuredImage": "https://cdn.mentionwell.com/.../hero.jpg",
      "readingTime": 7,
      "tags": ["buying", "first-home"],
      "category": { "title": "Buying", "slug": "buying" },
      "publishedAt": "2026-04-21T15:00:00.000Z",
      "updatedAt": "2026-04-21T15:00:00.000Z",
      "author": { "name": "Editorial Team", "avatarUrl": null, "url": null },
      "canonicalUrl": null
    }
  ]
}
```

The list endpoint omits heavy fields (`html`, `markdown`, `tldr`, `toc`, `faqs`, `jsonLd`) to keep payloads small. Fetch the detail endpoint for those.

---

## Get post

```text
GET /api/public/{siteSlug}/posts/{slug}
```

Returns the full post: HTML body, table of contents, FAQ, JSON-LD, author block. Use for `/blog/[slug]` detail pages.

### Path parameters

| Name | Type | Description |
|---|---|---|
| `siteSlug` | string (required) | The slug of your site. |
| `slug` | string (required) | The slug of a published post. |

### Example

```bash
curl https://app.mentionwell.com/api/public/your-site-slug/posts/five-questions-to-ask-before-you-buy \
  -H "Authorization: Bearer <MENTIONWELL_API_KEY>"
```

### Response

```json
{
  "ok": true,
  "post": {
    "slug": "five-questions-to-ask-before-you-buy",
    "title": "Five Questions to Ask Before You Buy",
    "metaTitle": "Five Questions to Ask Before You Buy a Home | Your Site",
    "metaDescription": "Short SEO description.",
    "excerpt": "...",
    "html": "<header class=\"wb-header\">...</header><section class=\"wb-section\">...</section>",
    "markdown": "# Five Questions...\n\n...",
    "featuredImage": "https://cdn.mentionwell.com/.../hero.jpg",
    "readingTime": 7,
    "tags": ["buying", "first-home"],
    "category": { "title": "Buying", "slug": "buying" },
    "publishedAt": "2026-04-21T15:00:00.000Z",
    "updatedAt": "2026-04-21T15:00:00.000Z",
    "author": { "name": "Editorial Team", "avatarUrl": null, "url": null },
    "tldr": { "items": ["Takeaway 1", "Takeaway 2", "Takeaway 3"] },
    "toc": [{ "id": "intro", "title": "Introduction", "level": 2 }],
    "faqs": [{ "question": "Is now a good time to buy?", "answer": "..." }],
    "canonicalUrl": null,
    "jsonLd": "{\"@context\":\"https://schema.org\",\"@type\":\"Article\",...}"
  }
}
```

---

## RSS feed

```text
GET /api/sites/{siteSlug}/feed.xml
```

Standard RSS 2.0. Public, no authentication. Add it to your `<head>`:

```html
<link rel="alternate" type="application/rss+xml"
      title="Your Site Blog"
      href="https://app.mentionwell.com/api/sites/your-site-slug/feed.xml" />
```

## JSON Feed

```text
GET /api/sites/{siteSlug}/feed.json
```

[JSON Feed 1.1](https://www.jsonfeed.org/) format. Useful for n8n / Zapier / Make automations and AI ingestion pipelines.

## Sitemap

```text
GET /api/sites/{siteSlug}/sitemap.xml
```

Standard sitemap.xml. Submit to Google Search Console and Bing Webmaster Tools.

---

## Errors

All errors return a JSON envelope:

```json
{ "ok": false, "message": "Human-readable description." }
```

| Status | Meaning | What to do |
|---|---|---|
| `400` | Malformed request | Check the path and query parameters against this reference. |
| `401` | Unauthorized | Missing or wrong `Authorization` header. |
| `404` | Site or post not found | Verify the slug; treat as a normal no-result state in your UI. |
| `5xx` | Origin error | Retry with exponential backoff. Cached responses still serve via SWR. |

---

## Dashboard / integration endpoints (authenticated)

These power the in-product Connect-destination wizard and the Custom Article tab. They require a logged-in dashboard session or a personal access token (`mw_pat_...`) with `sites:write`. Most customers never call these directly — the dashboard does — but they're documented here because they're the contract every architecture flows through.

### Connect-destination wizard

```text
GET  /api/sites/{siteSlug}/delivery/options
POST /api/sites/{siteSlug}/delivery/configure
POST /api/sites/{siteSlug}/delivery/test
```

- **`GET .../options`** returns the architecture catalog rendered for this site, with the per-site `webhookSecret`, `readApiKey`, `apiBaseUrl`, and copy-paste receiver templates substituted in.
- **`POST .../configure`** accepts `{ architecture, publishEndpoint?, deployHookUrl?, githubRepo?, githubBranch?, githubContentPath?, githubToken?, cmsAdapter?, autoPushPublishedPosts? }`. Cross-field validation enforces which inputs are required for each architecture (e.g. `static_deploy_hook` requires both `publishEndpoint` and `deployHookUrl`). Writes to `sites.delivery_config`.
- **`POST .../test`** fires a signed test ping for `static_deploy_hook`, `nextjs_isr`, and `cms_adapter`; probes the GitHub API for `github_mdx`; echoes the read key for `dynamic_reader`. Returns `{ ok, mode, message, detail }`.

The architecture IDs are: `static_deploy_hook`, `nextjs_isr`, `dynamic_reader`, `github_mdx`, `cms_adapter`. See [Connect wizard](/docs/connect-wizard) for what each picks.

### Custom Article

```text
POST /api/sites/{siteSlug}/headlines
```

Body for the custom-article flow:

```json
{
  "mode": "create_custom",
  "title": "Breaking: feature ships",
  "customBrief": "Optional. Pasted text the writer should treat as authoritative.",
  "customSourceUrls": ["https://primary-source.example.com"],
  "researchMode": "custom_plus_research",
  "intent": "info",
  "targetKeyword": "breaking feature",
  "wordCountTarget": 1800,
  "autoApprove": true
}
```

`researchMode` is one of `auto`, `custom_only`, or `custom_plus_research`. At least one of `customBrief` or `customSourceUrls` is required. See [Custom articles](/docs/custom-articles) for the full pipeline.

### Trigger a draft

```text
POST /api/sites/{siteSlug}/draft
```

Body: `{ "headlineId": "...", "includeGeoContext"?: boolean }`. Counts against the monthly quota. Returns a `jobId` you can poll via `/api/jobs/{id}`.

---

## Versioning

The public API is currently un-versioned because additive changes only. If a breaking change ever ships, it will land at a new path prefix and the existing one will continue to serve.


---

Canonical URL: https://mentionwell.com/docs/api
Live HTML version: https://mentionwell.com/docs/api
Section: API reference
Site index for AI ingestion: https://mentionwell.com/llms.txt
Full reference: https://mentionwell.com/llms-full.txt
