Documentation
Everything you need to integrate TrafficNinja into your stack - REST API reference and WordPress plugin guide.
API Integration
The TrafficNinja REST API is read-only: list your websites and read generated posts programmatically. All endpoints are prefixed with /api/v1/.
Base URL
https://trafficninja.io/api/v1Authentication
The Integration API uses Bearer token authentication. Send your integration key in the Authorization header on every request: Authorization: Bearer {integration-key}. This key is read-only - it can list your websites and read posts, but cannot create, update, or delete websites or manage webhooks (those are done in the dashboard). The WordPress plugin uses a separate per-site key sent via the X-API-Key header (see WordPress Plugin Endpoints below).
Copy your integration key from Dashboard → Settings → Integration Settings and pass it as a Bearer token in every request. The key is read-only.
Use your integration key in every request:
curl https://trafficninja.io/api/v1/websites \
-H "Authorization: Bearer {your-api-key}" \
-H "Accept: application/json"Core Endpoints
These endpoints use the Bearer integration key and are read-only. Rate limit: 60 requests per minute. Website management (create, update, delete) is dashboard-only and not part of the public API.
Websites
/api/v1/websitesList all websites AuthRequest
curl https://trafficninja.io/api/v1/websites \
-H "Authorization: Bearer {your-api-key}" \
-H "Accept: application/json"Response
{
"data": [
{
"uuid": "8a4d3737-a965-4867-ada7-40b95c02e48d",
"name": "Acme Marketing Blog",
"website_url": "https://acme.example.com",
"status": "active",
"language": "en-US",
"supported_languages": ["en-US", "ru-RU"],
"default_language": "en-US",
"timezone": "America/New_York",
"articles_per_day": 3,
"auto_publish": true,
"created_at": "2026-01-15T09:24:00.000000Z",
"updated_at": "2026-03-20T14:02:11.000000Z"
}
]
}Note: uuid is the website identifier — use it as website_id when filtering posts. api_token, wordpress_api_key, webhook settings, and internal numeric IDs are never included in this response - manage those in the dashboard.
Posts
/api/v1/postsList posts Auth/api/v1/posts/{id}Get post AuthOptional query filters: website_id (website UUID), language (e.g. en or en-US — a bare code matches the whole family), statuses[] (published|draft|archived), title (case-insensitive partial match), published_from / published_to (filter on publish time), created_from / created_to (filter on creation time) — dates all YYYY-MM-DD, inclusive, and independently combinable. group_by_translation (default true) returns one representative post per translation group; pass group_by_translation=false to list every language variant as its own item. Pagination via page and per_page.
Request — GET /api/v1/posts
curl "https://trafficninja.io/api/v1/posts?website_id=8a4d3737-a965-4867-ada7-40b95c02e48d&language=en&statuses[]=published&title=seo&published_from=2026-01-01&published_to=2026-03-31&created_from=2026-01-01&created_to=2026-03-31&page=1&per_page=20" \
-H "Authorization: Bearer {your-api-key}" \
-H "Accept: application/json"Response
{
"data": [
{
"id": 99,
"title": "10 Ways to Boost Local SEO",
"slug": "10-ways-boost-local-seo",
"content": "<h2>Why local SEO matters</h2><p>...</p>",
"meta_description": "Learn how to dominate local search...",
"language": "en-US",
"status": "published",
"generation_status": "completed",
"generation_error": null,
"created_at": "2026-02-10T08:00:00+00:00",
"published_at": "2026-02-11T08:00:00+00:00",
"scheduled_at": null,
"scheduled_generation_at": null,
"seo_score": 94,
"word_count": 2300,
"website_id": "8a4d3737-a965-4867-ada7-40b95c02e48d",
"category": { "id": 7, "name": "SEO" },
"theme": { "id": 41, "name": "Local SEO" },
"seo_metadata": {
"title": "10 Ways to Boost Local SEO",
"meta_description": "Learn how to dominate local search..."
},
"translation_group_id": 31,
"translation_group": {
"id": 31,
"article_plan_id": 55,
"created_at": "2026-02-10T08:00:00+00:00",
"variants": [
{
"locale": "en-US",
"post_id": 99,
"title": "10 Ways to Boost Local SEO",
"status": "published",
"generation_status": "completed",
"generation_error": null,
"published_at": "2026-02-11T08:00:00+00:00",
"manually_edited_at": null,
"seo_score": 94
}
]
},
"language_badges": [
{
"locale": "en-US",
"post_id": 99,
"status": "published",
"generation_status": "completed",
"badge_color": "success",
"has_error": false,
"seo_score": 94
}
],
"images": [
{
"url": "https://trafficninja.io/storage/images/posts/99/local-seo-1.png",
"type": "inline",
"position": 1
},
{
"url": "https://trafficninja.io/storage/images/posts/99/local-seo-2.png",
"type": "inline",
"position": 2
}
],
"can_edit": true
}
],
"links": {
"first": "https://trafficninja.io/api/v1/posts?page=1",
"last": "https://trafficninja.io/api/v1/posts?page=3",
"prev": null,
"next": "https://trafficninja.io/api/v1/posts?page=2"
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 3,
"path": "https://trafficninja.io/api/v1/posts",
"per_page": 20,
"to": 20,
"total": 47
}
}Post id is an integer. The website_id field returned in responses is a UUID string.
Request — GET /api/v1/posts/{id}
curl https://trafficninja.io/api/v1/posts/99 \
-H "Authorization: Bearer {your-api-key}" \
-H "Accept: application/json"Response
{
"id": 99,
"title": "10 Ways to Boost Local SEO",
"slug": "10-ways-boost-local-seo",
"content": "<h2>Why local SEO matters</h2><p>Local search is how nearby customers find you...</p><h2>1. Claim your Google Business Profile</h2><p>...</p>",
"meta_description": "Learn how to dominate local search...",
"language": "en-US",
"status": "published",
"generation_status": "completed",
"generation_error": null,
"created_at": "2026-02-10T08:00:00+00:00",
"published_at": "2026-02-11T08:00:00+00:00",
"scheduled_at": null,
"scheduled_generation_at": null,
"seo_score": 94,
"word_count": 2300,
"website_id": "8a4d3737-a965-4867-ada7-40b95c02e48d",
"category": { "id": 7, "name": "SEO" },
"theme": { "id": 41, "name": "Local SEO" },
"seo_metadata": {
"title": "10 Ways to Boost Local SEO",
"meta_description": "Learn how to dominate local search..."
},
"translation_group_id": 31,
"translation_group": {
"id": 31,
"article_plan_id": 55,
"created_at": "2026-02-10T08:00:00+00:00",
"variants": [
{
"locale": "en-US",
"post_id": 99,
"title": "10 Ways to Boost Local SEO",
"status": "published",
"generation_status": "completed",
"generation_error": null,
"published_at": "2026-02-11T08:00:00+00:00",
"manually_edited_at": null,
"seo_score": 94
}
]
},
"language_badges": [
{
"locale": "en-US",
"post_id": 99,
"status": "published",
"generation_status": "completed",
"badge_color": "success",
"has_error": false,
"seo_score": 94
}
],
"images": [
{
"url": "https://trafficninja.io/storage/images/posts/99/local-seo-1.png",
"type": "inline",
"position": 1
}
],
"can_edit": true
}Returns a single post object directly (no data wrapper). content holds the full sanitized HTML body.
WordPress Plugin Endpoints
These endpoints are consumed by the WordPress plugin using a per-site WordPress API Key (format tn_live_..., generated in the dashboard). They require an active or trialing subscription. Pass the key via the X-API-Key header:
X-API-Key: {your-wordpress-api-key}These endpoints authenticate with the X-API-Key header (the per-site WordPress plugin key, tn_live_...) — NOT the Bearer integration key used by the Core Endpoints above. They require an active subscription. Rate limit: 10 requests per minute.
/api/v1/wordpress/connection-testVerify API key + connectivity/api/v1/wordpress/content-queueFetch articles ready to publish/api/v1/wordpress/content/{id}Get full article contentGET /api/v1/wordpress/connection-test
Request
curl https://trafficninja.io/api/v1/wordpress/connection-test \
-H "X-API-Key: tn_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Accept: application/json"Response
{
"success": true,
"message": "Connection successful",
"data": {
"website_id": 12,
"website_name": "Acme Marketing Blog",
"supported_languages": ["en-US", "ru-RU"],
"default_language": "en-US",
"plugin_version_required": "1.0.0",
"subscription_status": "active",
"articles_remaining": 78
}
}GET /api/v1/wordpress/content-queue
Request
curl "https://trafficninja.io/api/v1/wordpress/content-queue?limit=10&status=published&since=2026-04-01T00:00:00Z" \
-H "X-API-Key: tn_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Accept: application/json"Response
{
"success": true,
"data": [
{
"type": "single-language",
"id": 99,
"article_plan_id": 55,
"locale": "en-US",
"title": "10 Ways to Boost Local SEO",
"content": "<h2>...</h2><p>...</p>",
"slug": "10-ways-boost-local-seo",
"excerpt": "A practical guide to local search...",
"seo_score": 94,
"meta_description": "Learn how to dominate local search...",
"focus_keyword": "local SEO tips",
"scheduled_for": "2026-04-11T08:00:00+00:00",
"json_ld": "{\"@context\":\"https://schema.org\",\"@type\":\"Article\"}",
"keywords": ["local SEO", "google business profile"],
"categories": ["SEO"],
"tags": ["seo", "local"],
"featured_image": {
"url": "https://trafficninja.io/storage/images/local-seo.jpg",
"alt": "Local SEO",
"title": "Local SEO"
},
"status": "published",
"author": "TrafficNinja AI",
"subscription_quota_consumed": 1,
"updated_at": "2026-04-09T12:30:00+00:00"
},
{
"type": "single-language",
"id": 104,
"article_plan_id": 58,
"locale": "ru-RU",
"title": "10 способов улучшить локальное SEO",
"content": "<h2>...</h2><p>...</p>",
"slug": "10-sposobov-uluchshit-lokalnoe-seo",
"excerpt": "Практическое руководство по локальному поиску...",
"seo_score": 91,
"meta_description": "Узнайте, как доминировать в локальном поиске...",
"focus_keyword": "локальное SEO",
"scheduled_for": null,
"json_ld": null,
"keywords": [],
"categories": ["SEO"],
"tags": [],
"featured_image": null,
"status": "published",
"author": "TrafficNinja AI",
"subscription_quota_consumed": 1,
"updated_at": "2026-04-10T09:15:00+00:00"
}
],
"meta": {
"count": 2,
"limit": 10,
"since": "2026-04-01T00:00:00+00:00"
}
}Note: focus_keyword is null when the post has no target keyword, and scheduled_for is null when the post is unscheduled.
GET /api/v1/wordpress/content/{id}
Request
curl https://trafficninja.io/api/v1/wordpress/content/99 \
-H "X-API-Key: tn_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Accept: application/json"Response
{
"success": true,
"data": {
"type": "single-language",
"id": 99,
"article_plan_id": 55,
"locale": "en-US",
"title": "10 Ways to Boost Local SEO",
"content": "<h2>Why local SEO matters</h2><p>Local search is how nearby customers find you...</p>",
"slug": "10-ways-boost-local-seo",
"excerpt": "A practical guide to local search...",
"seo_score": 94,
"meta_description": "Learn how to dominate local search...",
"focus_keyword": "local SEO tips",
"scheduled_for": "2026-04-11T08:00:00+00:00",
"json_ld": "{\"@context\":\"https://schema.org\",\"@type\":\"Article\"}",
"seo_report": null,
"validation_links": null,
"keywords": ["local SEO", "google business profile"],
"categories": ["SEO"],
"tags": ["seo", "local"],
"featured_image": {
"url": "https://trafficninja.io/storage/images/local-seo.jpg",
"alt": "Local SEO",
"title": "Local SEO"
},
"status": "published"
}
}Returns a single content item. focus_keyword is null when no target keyword is set, and scheduled_for is null when the post is unscheduled.
Outbound Webhooks
TrafficNinja can POST a signed JSON payload to a URL of your choice whenever a post changes state. Configure one webhook URL per website - no polling required.
Configuring a Webhook
The webhook URL and signing secret are configured in the dashboard under Settings → Webhooks. When you save a URL for the first time a signing secret is generated - store it securely, as you need it to verify incoming requests.
Events
post.publishedA post status changed to published (auto-publish or manual).post.archivedA post status changed to archived.post.content_editedThe title or body of a published, draft, or archived post was edited.Payload Shape
Every delivery is an HTTP POST with Content-Type: application/json. The post object mirrors the GET /api/v1/posts/{id} response.
{
"event": "post.published",
"timestamp": "2026-04-18T10:30:00Z",
"website_uuid": "8a4d3737-a965-4867-ada7-40b95c02e48d",
"post": {
"id": 99,
"title": "10 Ways to Boost Local SEO",
"slug": "10-ways-boost-local-seo",
"status": "published",
"language": "en-US",
"meta_description": "Learn how to dominate local search...",
"seo_score": 94,
"word_count": 2300,
"published_at": "2026-04-18T10:29:58Z",
...
}
}Verifying Signatures
Each request includes an X-Webhook-Signature header containing sha256= followed by an HMAC-SHA256 hex digest of the raw request body, signed with your webhook secret. Always verify this before processing the payload.
// Node.js verification example
const crypto = require('crypto');
function verifySignature(rawBody, secret, signatureHeader) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody) // use the raw request body bytes
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signatureHeader)
);
}// PHP verification example
function verifySignature(string $rawBody, string $secret, string $signatureHeader): bool {
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
return hash_equals($expected, $signatureHeader);
}Delivery & Retries
Error Handling
All errors return JSON with a consistent shape:
{
"message": "The given data was invalid.",
"errors": {
"email": ["The email field is required."]
}
}400Bad Request - validation failed401Unauthorized - missing or invalid token403Forbidden - insufficient permissions404Not Found - resource does not exist422Unprocessable Entity - business logic error429Too Many Requests - rate limited500Server Error - try again later