Python SDK for the Visibly Content Autopilot API - Pull API client, HMAC webhook verification, and Flask Blueprint.
Project description
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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ai_content_autopilot-1.0.2.tar.gz.
File metadata
- Download URL: ai_content_autopilot-1.0.2.tar.gz
- Upload date:
- Size: 16.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
63e0121be4264276758020bf350c0aa8bd505bfdc466d24f976b5f7a84383f9c
|
|
| MD5 |
193ff6ff90850b6ad40a48751eb9abe3
|
|
| BLAKE2b-256 |
332dfe9e5c3dc7cf3dd45784587478edda97bc240bf6a3d1998d8661a853d526
|
File details
Details for the file ai_content_autopilot-1.0.2-py3-none-any.whl.
File metadata
- Download URL: ai_content_autopilot-1.0.2-py3-none-any.whl
- Upload date:
- Size: 10.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fa7a7346cd3e0979006d17142f3e45ff574ae6e964cf4ddf4cf9821b8f1c4829
|
|
| MD5 |
3d6d65e32a5488863062a4a7de46d57d
|
|
| BLAKE2b-256 |
4a25fa60c4c1088e936e882086bc5a10d42630ffe8be96df5ebef602e7961aea
|