HomeDocumentation

Documentation

Everything you need to integrate TrafficNinja into your stack - REST API reference and WordPress plugin guide.

REST API

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/v1

Authentication

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:

bash
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

GET/api/v1/websitesList all websites Auth

Request

bash
curl https://trafficninja.io/api/v1/websites \
  -H "Authorization: Bearer {your-api-key}" \
  -H "Accept: application/json"

Response

json
{
  "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

GET/api/v1/postsList posts Auth
GET/api/v1/posts/{id}Get post Auth

Optional 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

bash
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

json
{
  "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}

bash
curl https://trafficninja.io/api/v1/posts/99 \
  -H "Authorization: Bearer {your-api-key}" \
  -H "Accept: application/json"

Response

json
{
  "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:

http
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.

GET/api/v1/wordpress/connection-testVerify API key + connectivity
GET/api/v1/wordpress/content-queueFetch articles ready to publish
GET/api/v1/wordpress/content/{id}Get full article content

GET /api/v1/wordpress/connection-test

Request

bash
curl https://trafficninja.io/api/v1/wordpress/connection-test \
  -H "X-API-Key: tn_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Accept: application/json"

Response

json
{
  "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

bash
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

json
{
  "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

bash
curl https://trafficninja.io/api/v1/wordpress/content/99 \
  -H "X-API-Key: tn_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Accept: application/json"

Response

json
{
  "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.

json
{
  "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.

javascript
// 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
// 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

1Your endpoint must return a 2xx status within 30 seconds.
2Failed deliveries are retried up to 3 times with exponential backoff: 30 s → 60 s → 120 s.
3After 3 failures the event is dropped and logged. Check your server logs if events go missing.
4Respond quickly - offload any heavy processing to a background queue on your end.

Error Handling

All errors return JSON with a consistent shape:

json
{
  "message": "The given data was invalid.",
  "errors": {
    "email": ["The email field is required."]
  }
}
400Bad Request - validation failed
401Unauthorized - missing or invalid token
403Forbidden - insufficient permissions
404Not Found - resource does not exist
422Unprocessable Entity - business logic error
429Too Many Requests - rate limited
500Server Error - try again later
TrafficNinja API Documentation & WordPress Plugin Guide