ai-content-autopilot 1.0.2
Python SDK for the Visibly Content Autopilot API - Pull API client, HMAC webhook verification, and Flask Blueprint.
License: MIT
Requires Python: >=3.8
Keywords: cms, content, autopilot, webhook, api, seo, flask

# ai-content-autopilot

Python SDK for the **[Visibly Content Autopilot](https://www.antonioblago.com/content-autopilot)** API.

Visibly Content Autopilot is an AI-powered content generation platform that creates SEO-optimized articles for your projects. It handles keyword research, content planning, and article generation — then delivers finished articles to your CMS via webhooks or a Pull API.

This SDK gives you everything you need to integrate Content Autopilot into any Python/Flask application:

- **Pull API Client** — fetch, list, and confirm articles programmatically
- **Webhook Receiver** — a ready-made Flask Blueprint that verifies HMAC signatures, fetches full article content, and calls your handler
- **HMAC-SHA256 Verification** — standalone signature verification for custom webhook implementations

## How It Works

```
1. Content Autopilot generates & approves an article
                    |
2. Webhook fires to your endpoint (POST /webhooks/visibly)
   with HMAC-SHA256 signature for security
                    |
3. Your app verifies the signature, then calls the Pull API
   to fetch the full article (HTML, Markdown, keywords, SEO score)
                    |
4. Your app saves/publishes the article in your CMS
                    |
5. Your app confirms publication back to Visibly
   (article status changes to "published")
```

## Installation

```bash
pip install ai-content-autopilot
```

**Requirements:** Python 3.8+, Flask 2.3+, Requests 2.25+

## Getting Started

### 1. Get your credentials

- **API Key**: Go to [Account > API Keys](https://www.antonioblago.com/account/api-keys) and create a new key (starts with `sk_live_`)
- **Webhook Secret**: Go to your Project > CMS Settings and configure a webhook endpoint. The secret is generated automatically.

Full API documentation: **[Developer Docs](https://www.antonioblago.com/developers)**

### 2. Choose your integration style

#### Option A: Full Flask Blueprint (recommended)

The easiest way to integrate. Register the blueprint and provide a handler function — the SDK handles signature verification, article fetching, and error responses automatically.

```python
from flask import Flask
from ai_content_autopilot import configure_visibly, contentpilot_webhook_bp

app = Flask(__name__)

def my_handler(article):
    """Called when a webhook delivers an article.

    article dict contains:
      id, title, slug, content_html, content_markdown,
      keywords, meta_description, seo_score, word_count,
      _webhook_event, _webhook_timestamp, _scheduled_date
    """
    # Save to your database, CMS, filesystem, etc.
    db.session.add(Post(
        title=article['title'],
        body=article['content_html'],
        slug=article['slug'],
        publish_at=article.get('_scheduled_date'),
    ))
    db.session.commit()
    return True  # Return False to reject (422 response)

configure_visibly(
    webhook_secret='your-webhook-secret',
    api_key='sk_live_your_api_key',
    on_article_received=my_handler,
)

app.register_blueprint(contentpilot_webhook_bp)
# POST /webhooks/visibly is now active and handles:
#   1. HMAC-SHA256 signature verification
#   2. Full article fetch via Pull API
#   3. Calls my_handler(article)
#   4. Returns {"success": true} or appropriate error
```

#### Option B: Standalone Pull API Client

Use the client directly to poll for articles or integrate into non-Flask applications.

```python
from ai_content_autopilot import VisiblyClient

client = VisiblyClient(api_key='sk_live_your_api_key')

# List approved articles ready for publishing
articles = client.list_articles(status='approved', project_id=5, limit=20)
for a in articles:
    print(a['id'], a['title'], a.get('scheduled_date'))

# Fetch a single article with full content
article = client.fetch_article(42, include_markdown=True)
print(article['content_html'])
print(article['keywords'])       # e.g. ["seo", "keyword research"]
print(article['seo_score'])      # 0-100

# Confirm publication (updates status to "published" in Visibly)
client.confirm_published(42, 'https://myblog.com/seo-guide-2026')
```

#### Option C: HMAC verification only

For custom webhook implementations in any framework.

```python
from ai_content_autopilot import verify_webhook_signature

# In your webhook endpoint:
payload_bytes = request.get_data()       # raw bytes, NOT request.json
signature = request.headers.get('X-Webhook-Signature', '')

if not verify_webhook_signature(payload_bytes, 'your-secret', signature):
    return {'error': 'Invalid signature'}, 401
```

## Article Lifecycle

| Status | Description | Webhook Event |
|--------|-------------|---------------|
| `queued` | Waiting to be generated | - |
| `generating` | AI is writing the article | - |
| `draft` | Draft ready for review | - |
| `approved` | Ready for publishing | `article.approved` |
| `published` | Confirmed published via API | `article.published` |
| `rejected` | Rejected by user | - |
| `failed` | Generation failed | `article.failed` |

## API Reference

| Function / Class | Description |
|---|---|
| `configure_visibly(webhook_secret, api_key, base_url, on_article_received)` | Configure the Blueprint with credentials and callback |
| `verify_webhook_signature(payload_bytes, secret, signature_header)` | Verify HMAC-SHA256 signature. Returns `True`/`False` |
| `VisiblyClient(api_key, base_url, timeout)` | Pull API client for fetching, listing, and confirming articles |
| `client.fetch_article(article_id, include_markdown)` | Returns article dict or `None` on error |
| `client.list_articles(status, project_id, limit, offset)` | Returns list of article dicts |
| `client.confirm_published(article_id, published_url)` | Confirms publication. Returns `True`/`False` |
| `contentpilot_webhook_bp` | Flask Blueprint. Register with `app.register_blueprint()`. Endpoint: `POST /webhooks/visibly` |
| `default_flask_blog_handler(article)` | Default handler: saves article as JSON to `./content_output/` |

## Webhook Payload

When a webhook fires, the `POST /webhooks/visibly` endpoint receives a JSON body with these fields:

```json
{
  "event": "article.approved",
  "article_id": 42,
  "title": "SEO Guide 2026",
  "slug": "seo-guide-2026",
  "project_id": 5,
  "scheduled_date": "2026-03-01T09:00:00",
  "pull_url": "https://www.antonioblago.com/content-autopilot/api/v1/articles/42",
  "timestamp": "2026-02-20T10:00:00Z"
}
```

| Field | Type | Description |
|-------|------|-------------|
| `event` | string | `article.approved`, `article.published`, or `article.failed` |
| `article_id` | int | Database ID of the article |
| `title` | string | Article title |
| `slug` | string | URL-safe slug |
| `project_id` | int | Owning project ID |
| `scheduled_date` | string/null | ISO 8601 scheduled publish date, or `null` |
| `pull_url` | string | Full URL to fetch article content via Pull API |
| `timestamp` | string | ISO 8601 UTC timestamp of the event |

The Blueprint automatically fetches the full article via `pull_url` and injects webhook metadata (`_webhook_event`, `_webhook_timestamp`, `_scheduled_date`) into the article dict before calling your handler.

## Article Response Fields

When fetching an article via `VisiblyClient.fetch_article()`, the returned dict contains:

| Field | Type | Description |
|-------|------|-------------|
| `id` | int | Unique article ID |
| `title` | string | Article title |
| `slug` | string | URL-friendly slug |
| `status` | string | Current status (see Article Lifecycle) |
| `content_html` | string | Full article as HTML |
| `content_markdown` | string | Article as Markdown (only if `include_markdown=True`) |
| `keywords` | list | Target keywords, e.g. `["seo", "keyword research"]` |
| `meta_description` | string | SEO meta description (max 160 chars) |
| `seo_score` | int | SEO optimization score (0-100) |
| `word_count` | int | Article word count |
| `project_id` | int | Owning project ID |
| `published_url` | string | Public URL after publication (empty if unpublished) |
| `scheduled_date` | string/null | Planned publish date (ISO 8601) |
| `created_at` | string | Creation timestamp |
| `updated_at` | string | Last update timestamp |

## Webhook Security

Every webhook request includes an `X-Webhook-Signature` header with an HMAC-SHA256 signature:

```
X-Webhook-Signature: sha256=<hex_digest>
```

The SDK verifies this automatically when using the Blueprint. The verification uses `hmac.compare_digest` for timing-safe comparison to prevent timing attacks.

## Error Handling

The `VisiblyClient` methods handle errors gracefully:

- `fetch_article()` returns `None` on any error (404, network failure, timeout)
- `list_articles()` returns `[]` on any error
- `confirm_published()` returns `False` on any error

All errors are logged via Python's `logging` module at WARNING or ERROR level.

When using the Blueprint, HTTP responses are:

| Code | Meaning |
|------|---------|
| 200 | Success — article processed |
| 400 | Invalid JSON in webhook payload |
| 401 | HMAC signature verification failed |
| 422 | Handler returned `False` (article rejected) |
| 500 | Webhook not configured, or handler raised an exception |
| 502 | Failed to fetch article from Pull API |

## Rate Limits

The Visibly API enforces the following rate limits:

| Endpoint | Limit |
|----------|-------|
| `GET /api/v1/articles` | 120 requests/min |
| `GET /api/v1/articles/{id}` | 60 requests/min |
| `POST /api/v1/articles/{id}/confirm` | 30 requests/min |

When rate-limited, the API returns HTTP 429 with a `Retry-After` header.

## Links

- [Developer Documentation](https://www.antonioblago.com/developers) — full API docs, endpoint reference, code examples in Python, Node.js, and PHP
- [Visibly Content Autopilot](https://www.antonioblago.com/content-autopilot) — the platform
- [API Keys](https://www.antonioblago.com/account/api-keys) — manage your API keys
- [GitHub Repository](https://github.com/AntonioBlago/ai-content-autopilot)
- [PyPI Package](https://pypi.org/project/ai-content-autopilot/)

## Development

```bash
git clone https://github.com/AntonioBlago/ai-content-autopilot.git
cd ai-content-autopilot
pip install -e ".[dev]"
pytest tests/ -v
```

## License

MIT - see [LICENSE](LICENSE) for details.