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

# REST API Integration

> Access your articles programmatically via REST API

The ChatFeatured REST API lets you list, fetch, and publish your articles programmatically, and track external content for AI citation attribution. Use it to build custom workflows, sync content to other platforms, or automate publishing.

**Status:** Available | **Complexity:** Intermediate | **Use cases:** Custom automation, third-party integrations, content syncing

<Info>
  The REST API is scoped to a single brand: each API key belongs to one brand and can only access that brand's articles and tracked content.
</Info>

***

## Authentication

All API requests require an API key. Pass it in either header:

```text theme={null}
Authorization: Bearer YOUR_API_KEY
```

or

```text theme={null}
X-API-Key: YOUR_API_KEY
```

An `?api_key=YOUR_API_KEY` query parameter is also accepted, but prefer headers: query strings often end up in server logs.

### Create an API Key

1. In ChatFeatured, go to **Integrations → REST API**
2. Click **Create API Key**
3. Copy the key immediately (it is only shown once)

Keys start with `cf_`. Each brand has one API key: you can **regenerate** it (the old key stops working immediately) or **remove** it from the same page.

<Warning>
  **Security:** Treat API keys like passwords. Never commit them to version control or expose them in client-side code. Store them in environment variables. If a key is compromised, regenerate it from the dashboard.
</Warning>

***

## Base URL

All API requests use:

```text theme={null}
https://chatfeatured.com/api/v1
```

<Tip>
  Use HTTPS only. HTTP requests will be rejected for security.
</Tip>

***

## Endpoints

### List Articles

List your brand's articles, filtered by status.

```text theme={null}
GET /articles
```

**Query Parameters:**

| Parameter | Type | Description |
| - | - | - |
| `status` | string | Filter: `published` (default), `review`, `awaiting_approval`, `approved`, `scheduled` |
| `limit` | number | Max results per page (default: 20, max: 100) |
| `offset` | number | Pagination offset (default: 0) |

**Example with curl:**

```bash theme={null}
curl "https://chatfeatured.com/api/v1/articles?status=published&limit=10" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Example with Python:**

```python theme={null}
import requests

response = requests.get(
    'https://chatfeatured.com/api/v1/articles',
    params={'status': 'published', 'limit': 10},
    headers={'Authorization': 'Bearer YOUR_API_KEY'}
)
articles = response.json()
```

**Example with JavaScript:**

```javascript theme={null}
const response = await fetch(
  'https://chatfeatured.com/api/v1/articles?status=published&limit=10',
  {
    headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
  }
);
const articles = await response.json();
```

**Response:**

```json theme={null}
{
  "data": [
    {
      "id": "abc123",
      "title": "How to Optimize for AI Search",
      "slug": "how-to-optimize-for-ai-search",
      "status": "published",
      "excerpt": "A comprehensive guide...",
      "metaDescription": "Learn how to optimize...",
      "content": "<p>Full HTML content...</p>",
      "featuredImageUrl": "https://example.com/image.jpg",
      "scheduledAt": null,
      "publishedAt": "2026-03-20T12:00:00.000Z",
      "priority": "medium",
      "wordCount": 1200,
      "readingTimeMinutes": 5,
      "createdAt": "2026-03-19T10:00:00.000Z",
      "updatedAt": "2026-03-20T12:00:00.000Z",
      "metadata": {
        "articleType": "guide",
        "description": "A comprehensive guide to AI search optimization"
      }
    }
  ],
  "pagination": {
    "total": 42,
    "limit": 10,
    "offset": 0
  }
}
```

***

### Get Single Article

Retrieve a specific article by ID or slug.

```text theme={null}
GET /articles/{idOrSlug}
```

**Example with curl:**

```bash theme={null}
curl https://chatfeatured.com/api/v1/articles/how-to-optimize-for-ai-search \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Example with Python:**

```python theme={null}
response = requests.get(
    'https://chatfeatured.com/api/v1/articles/how-to-optimize-for-ai-search',
    headers={'Authorization': 'Bearer YOUR_API_KEY'}
)
article = response.json()['data']
```

**Response:** a `data` object with the same article fields as List Articles. Returns `404` if no article matches the ID or slug.

***

### Publish Article

Publish an article. Only articles in `review` or `approved` status can be published; anything else returns `400`.

```text theme={null}
POST /articles/{idOrSlug}/publish
```

**Body (optional):**

| Parameter | Type | Description |
| - | - | - |
| `externalUrls` | string\[] | Live URLs where this article is published outside ChatFeatured (used for AI citation attribution) |

**Example with curl:**

```bash theme={null}
curl -X POST https://chatfeatured.com/api/v1/articles/abc123/publish \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Example with JavaScript:**

```javascript theme={null}
const response = await fetch(
  'https://chatfeatured.com/api/v1/articles/abc123/publish',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer YOUR_API_KEY'
    },
    body: JSON.stringify({ externalUrls: ['https://example.com/blog/my-article'] })
  }
);
```

**Response:**

```json theme={null}
{
  "success": true,
  "data": {
    "id": "abc123",
    "status": "published",
    "publishedAt": "2026-03-20T12:00:00.000Z"
  }
}
```

Publishing also completes any pending content tasks for the article and fires the `article.published` webhook if configured.

***

### Track External Content

Track an external article URL (for example, a LinkedIn post) so its AI citations are attributed to your brand in the content impact view.

```text theme={null}
POST /tracked-content
```

**Body:**

| Parameter | Type | Description |
| - | - | - |
| `url` | string | Required. The external URL to track (http or https) |
| `publishedAt` | string | Optional ISO-8601 date to override the publication date detected from page metadata |

**Example with curl:**

```bash theme={null}
curl -X POST https://chatfeatured.com/api/v1/tracked-content \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://www.linkedin.com/pulse/my-article"}'
```

**Response (201):**

```json theme={null}
{
  "success": true,
  "data": {
    "id": "abc123",
    "url": "https://www.linkedin.com/pulse/my-article",
    "normalizedUrl": "linkedin.com/pulse/my-article",
    "title": "My Article",
    "description": "A post about AI search...",
    "imageUrl": "https://example.com/image.jpg",
    "publishedAt": "2026-03-20T12:00:00.000Z",
    "domain": "linkedin.com",
    "createdAt": "2026-03-20T12:00:00.000Z",
    "updatedAt": "2026-03-20T12:00:00.000Z"
  }
}
```

Returns `409` if the URL is already tracked or already associated with a ChatFeatured article.

***

### List Tracked Content

List the external URLs tracked for your brand.

```text theme={null}
GET /tracked-content
```

**Query Parameters:**

| Parameter | Type | Description |
| - | - | - |
| `limit` | number | Max results per page (default: 20, max: 100) |
| `offset` | number | Pagination offset (default: 0) |

**Example with curl:**

```bash theme={null}
curl "https://chatfeatured.com/api/v1/tracked-content?limit=10" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response:** a `data` array of tracked content items (same fields as Track External Content) plus the standard `pagination` object.

***

## Webhooks

Get notified when articles change status via webhooks.

<Info>
  Webhooks let your application react to events in real-time. For example, automatically sync articles to your CMS when they're published, or send Slack notifications when articles are ready for review.
</Info>

### Configure Webhooks

1. Go to **Integrations → REST API**
2. Scroll to the **Webhooks** section
3. Enter your **Webhook URL**
4. (Optional) Enter a **Signing Secret** for verification
5. Toggle which events to receive:
   * Article ready for review
   * Article published
6. Click **Save Webhook Settings**

Use the **Send Test** button to deliver a sample payload to your endpoint and confirm it responds.

### Webhook Events

Webhooks are sent as `POST` requests with a JSON body. Both events carry the full article object:

**Event: `article.review`** fires when an article finishes generating and is ready for review.

**Event: `article.published`** fires when an article is published (manually, via API, or via schedule).

```json theme={null}
{
  "event": "article.published",
  "timestamp": "2026-03-20T12:00:00.000Z",
  "data": {
    "id": "abc123",
    "title": "Your Article Title",
    "slug": "your-article-title",
    "status": "published",
    "excerpt": "A brief excerpt...",
    "metaDescription": "Meta description...",
    "content": "<p>Full HTML content...</p>",
    "featuredImageUrl": "https://example.com/image.jpg",
    "scheduledAt": null,
    "publishedAt": "2026-03-20T12:00:00.000Z",
    "priority": "medium",
    "wordCount": 1200,
    "readingTimeMinutes": 5,
    "createdAt": "2026-03-19T10:00:00.000Z",
    "updatedAt": "2026-03-20T12:00:00.000Z",
    "metadata": {
      "articleType": "guide",
      "description": "A brief excerpt..."
    }
  }
}
```

<Tip>
  Webhook deliveries time out after 10 seconds. Respond with a `200` quickly and process the payload asynchronously if needed.
</Tip>

### Webhook Security

If you configured a **Signing Secret**, each webhook includes an `X-Webhook-Signature` header containing an HMAC-SHA256 hex digest of the raw request body, signed with your secret:

```text theme={null}
X-Webhook-Signature: hex_encoded_signature
```

**Verify the signature with Python:**

```python theme={null}
import hmac
import hashlib

def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)
```

**Verify the signature with JavaScript:**

```javascript theme={null}
const crypto = require('crypto');

function verifyWebhook(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected, 'hex'),
    Buffer.from(signature, 'hex')
  );
}
```

<Tip>
  Compute the digest over the raw request body bytes, before any JSON parsing, and always verify signatures to ensure requests are genuinely from ChatFeatured.
</Tip>

***

## Error Handling

All errors return a JSON object with an `error` field:

```json theme={null}
{
  "error": "Article not found"
}
```

| Status | Meaning |
| - | - |
| `400` | Invalid request (e.g. an unsupported `status` filter, or publishing an article that isn't in `review` or `approved` status) |
| `401` | Missing or invalid API key |
| `403` | The API key is valid but not authorized for this API |
| `404` | Article not found |
| `409` | Conflict (e.g. the external URL is already tracked or already linked to an article) |
| `500` | Internal server error |

***

## Use Cases

### Automated Publishing

```python theme={null}
import requests

def publish_approved_articles():
    response = requests.get(
        'https://chatfeatured.com/api/v1/articles?status=approved&limit=100',
        headers={'Authorization': f'Bearer {API_KEY}'}
    )
    for article in response.json()['data']:
        requests.post(
            f"https://chatfeatured.com/api/v1/articles/{article['id']}/publish",
            headers={'Authorization': f'Bearer {API_KEY}'}
        )
        print(f"Published: {article['title']}")
```

### Sync to External CMS

```python theme={null}
def sync_to_wordpress(article_id):
    # Get article from ChatFeatured
    cf_response = requests.get(
        f'https://chatfeatured.com/api/v1/articles/{article_id}',
        headers={'Authorization': f'Bearer {CF_API_KEY}'}
    )
    article = cf_response.json()['data']

    # Post to WordPress
    wp_response = requests.post(
        'https://yourwordpress.com/wp-json/wp/v2/posts',
        json={
            'title': article['title'],
            'content': article['content'],
            'status': 'publish'
        },
        auth=('user', 'password')
    )
    return wp_response.status_code
```

### Monitor Article Status

```python theme={null}
def check_articles_in_review():
    response = requests.get(
        'https://chatfeatured.com/api/v1/articles?status=review&limit=100',
        headers={'Authorization': f'Bearer {API_KEY}'}
    )

    articles = response.json()['data']
    print(f"Articles waiting for review: {len(articles)}")
    for article in articles:
        print(f"- {article['title']}")
```

***

## Best Practices

* Store API keys in environment variables
* Regenerate your key immediately if it may have been exposed
* Validate webhook signatures
* Handle errors gracefully and retry with exponential backoff

<Warning>
  **Don't** commit API keys to version control\
  **Don't** expose keys in client-side code\
  **Don't** pass keys as query parameters unless headers aren't an option
</Warning>

***

## Troubleshooting

<Accordion title="'401 Unauthorized' Error">
  **Cause:** Missing or invalid API key

  **Solution:**

  * Check the header format: `Authorization: Bearer KEY` or `X-API-Key: KEY`
  * Verify no extra spaces or truncation in the key
  * Check the key wasn't regenerated or removed in the dashboard (regenerating invalidates the old key immediately)
</Accordion>

<Accordion title="'403 Forbidden' Error">
  **Cause:** The key is valid but not an API integration key (for example, an MCP key)

  **Solution:**

  * Create the key from **Integrations → REST API**, not from another integration's settings
</Accordion>

<Accordion title="Webhooks Not Firing">
  **Cause:** URL is unreachable, events aren't enabled, or the endpoint responds too slowly

  **Solution:**

  * Verify the webhook URL is publicly accessible
  * Check that the event toggles (review/published) are enabled and settings were saved
  * Ensure your endpoint returns **200 OK** within 10 seconds
  * Use the **Send Test** button on the integration page and check your server logs
  * Verify the signing secret matches if you validate signatures
</Accordion>

***

## See Also

* [CMS Integrations](/integrations/wordpress): publish to other platforms
* [AI Assistants (MCP)](/integrations/mcp): manage content and analytics from MCP clients


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.