ai-content-autopilot
Python SDK for the Visibly 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
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 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
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.
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.
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.
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:
{
"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()returnsNoneon any error (404, network failure, timeout)list_articles()returns[]on any errorconfirm_published()returnsFalseon 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 — full API docs, endpoint reference, code examples in Python, Node.js, and PHP
- Visibly Content Autopilot — the platform
- API Keys — manage your API keys
- GitHub Repository
- PyPI Package
Development
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 for details.
Metadata
Release files for ai-content-autopilot 1.0.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ai_content_autopilot-1.0.2.tar.gz | 16.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ai_content_autopilot-1.0.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 27.2 kB
Release files / ai_content_autopilot-1.0.2.tar.gz
| Download URL | ai_content_autopilot-1.0.2.tar.gz |
|---|---|
| Size | 16.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
63e0121be4264276758020bf350c0aa8bd505bfdc466d24f976b5f7a84383f9c
|
|
BLAKE2b-256 checksum How to use checksums |
332dfe9e5c3dc7cf3dd45784587478edda97bc240bf6a3d1998d8661a853d526
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.8
|
Release files / ai_content_autopilot-1.0.2-py3-none-any.whl
| Download URL | ai_content_autopilot-1.0.2-py3-none-any.whl |
|---|---|
| Size | 10.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fa7a7346cd3e0979006d17142f3e45ff574ae6e964cf4ddf4cf9821b8f1c4829
|
|
BLAKE2b-256 checksum How to use checksums |
4a25fa60c4c1088e936e882086bc5a10d42630ffe8be96df5ebef602e7961aea
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.8
|