Files
igny8/docs/wp/04-WORDPRESS-BIDIRECTIONAL-SYNC-REFERENCE.md
2025-12-03 18:12:01 +05:00

54 KiB

IGNY8 WordPress Integration Reference

Version: 1.0.0
Last Updated: November 29, 2025
Purpose: Complete reference for the bidirectional WordPress ↔ IGNY8 integration, covering architecture, data flows, sync mechanisms, and troubleshooting.


Table of Contents

  1. Integration Overview
  2. Architecture
  3. WordPress Plugin Structure
  4. IGNY8 Backend Integration Points
  5. Authentication & Security
  6. Bidirectional Sync: Data Elements
  7. Publishing Flow (IGNY8 → WordPress)
  8. Sync Flow (WordPress → IGNY8)
  9. REST API Endpoints
  10. Data Models & Meta Fields
  11. Status Mapping
  12. Troubleshooting Guide
  13. Developer Quick Reference

Integration Overview

The IGNY8 WordPress Bridge enables bidirectional synchronization between IGNY8 (SaaS platform) and WordPress sites, allowing content created in IGNY8 to be published to WordPress and WordPress content/metadata to be synced back to IGNY8.

Core Capabilities

Feature Direction Status
Content Publishing IGNY8 → WP Live
Post Status Sync WP → IGNY8 Live
Taxonomy Sync WP ↔ IGNY8 Live
SEO Metadata IGNY8 → WP Live
Featured Images IGNY8 → WP Live
Site Discovery WP → IGNY8 Live
Keyword Tracking IGNY8 → WP Live
Internal Linking IGNY8 → WP 🔄 Pending
Content Optimization WP ↔ IGNY8 🔄 Pending

Integration Model

┌─────────────────────────────────────────────────────────────┐
│                    IGNY8 SaaS Platform                       │
│  ┌──────────────────────────────────────────────────────┐   │
│  │  Django Backend (api.igny8.com)                      │   │
│  │  - Publisher Service                                 │   │
│  │  - WordPress Adapter                                 │   │
│  │  - Content Models                                    │   │
│  │  - Integration Models                                │   │
│  │  - Celery Background Tasks                           │   │
│  └──────────────────────────────────────────────────────┘   │
└───────────────────────┬─────────────────────────────────────┘
                        │
                        │ REST API (JSON)
                        │ - JWT Auth + API Key
                        │ - Unified Response Format
                        │
┌───────────────────────▼─────────────────────────────────────┐
│               WordPress Site (client.com)                    │
│  ┌──────────────────────────────────────────────────────┐   │
│  │  IGNY8 Bridge Plugin                                 │   │
│  │  - API Client (class-igny8-api.php)                  │   │
│  │  - REST Endpoints (/wp-json/igny8/v1/)              │   │
│  │  - Sync Handlers (sync/*.php)                        │   │
│  │  - Post Meta Storage (_igny8_* fields)              │   │
│  │  - Custom Taxonomies (igny8_clusters, igny8_sectors)│   │
│  └──────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

Architecture

High-Level Data Flow

IGNY8 → WordPress Publishing Flow:
┌─────────────┐    ┌──────────────┐    ┌─────────────────┐    ┌──────────────┐    ┌──────────────┐
│   Frontend  │ -> │  Publisher   │ -> │    Publisher    │ -> │  WordPress   │ -> │  WordPress   │
│ (Review.tsx)│    │   ViewSet    │    │    Service      │    │   Adapter    │    │  REST API    │
│  Publish    │    │  /publish/   │    │publish_content()│    │  publish()   │    │ /igny8/v1/   │
│   Button    │    │              │    │                 │    │              │    │publish-content│
└─────────────┘    └──────────────┘    └─────────────────┘    └──────────────┘    └──────────────┘
                                                                                            │
                                                                                            ▼
                                                                                   ┌──────────────┐
                                                                                   │  WordPress   │
                                                                                   │     Post     │
                                                                                   │   Created    │
                                                                                   └──────────────┘

WordPress → IGNY8 Sync Flow:
┌─────────────┐    ┌──────────────┐    ┌─────────────────┐    ┌──────────────┐
│  WordPress  │ -> │   WP Hook    │ -> │   Igny8API      │ -> │   IGNY8 API  │
│  save_post  │    │ post-sync.php│    │     PUT/POST    │    │ /writer/tasks│
│    Event    │    │              │    │                 │    │              │
└─────────────┘    └──────────────┘    └─────────────────┘    └──────────────┘
                                                                        │
                                                                        ▼
                                                                ┌──────────────┐
                                                                │    Content   │
                                                                │    Updated   │
                                                                └──────────────┘

Component Interaction

IGNY8 Backend Components:

  • PublisherViewSet (igny8_core/modules/publisher/views.py) - REST API endpoint
  • PublisherService (igny8_core/business/publishing/services/publisher_service.py) - Business logic
  • WordPressAdapter (igny8_core/business/publishing/services/adapters/wordpress_adapter.py) - WP integration
  • Celery Tasks (igny8_core/tasks/wordpress_publishing.py) - Async background publishing
  • Content Model (igny8_core/business/content/models.py) - Content data
  • SiteIntegration Model (igny8_core/business/integration/models.py) - WordPress connection config

WordPress Plugin Components:

  • Igny8API (includes/class-igny8-api.php) - API client for IGNY8
  • Igny8RestAPI (includes/class-igny8-rest-api.php) - REST endpoints for IGNY8 to call
  • Post Sync (sync/post-sync.php) - WP → IGNY8 synchronization
  • IGNY8 to WP (sync/igny8-to-wp.php) - IGNY8 → WP content creation
  • Hooks (sync/hooks.php) - WordPress action/filter hooks
  • Admin (admin/settings.php) - Plugin settings UI

WordPress Plugin Structure

igny8-wp-integration/
├── igny8-bridge.php                # Main plugin file
│   - Plugin bootstrap
│   - Registers all classes
│   - Defines constants
│   - Version: 1.0.0
│
├── includes/                       # Core functionality
│   ├── class-igny8-api.php         # API Client
│   │   - login() - JWT authentication
│   │   - refresh_token() - Token refresh
│   │   - get/post/put/delete() - HTTP methods
│   │   - parse_response() - Unified response handling
│   │
│   ├── class-igny8-rest-api.php    # WordPress REST Endpoints
│   │   Endpoints:
│   │   - GET  /wp-json/igny8/v1/post-by-content-id/{id}
│   │   - GET  /wp-json/igny8/v1/post-by-task-id/{id}
│   │   - GET  /wp-json/igny8/v1/post-status/{content_id}
│   │   - GET  /wp-json/igny8/v1/site-metadata/
│   │   - GET  /wp-json/igny8/v1/status
│   │   - POST /wp-json/igny8/v1/publish-content/
│   │
│   ├── class-igny8-site.php        # Site data collection
│   ├── class-igny8-webhooks.php    # Webhook handlers
│   ├── class-igny8-link-queue.php  # Link processing
│   └── functions.php               # Helper functions
│
├── sync/                           # Synchronization
│   ├── hooks.php                   # WP hooks registration
│   │   - save_post
│   │   - publish_post
│   │   - transition_post_status
│   │
│   ├── post-sync.php               # WP → IGNY8 sync
│   │   - igny8_sync_post_status_to_igny8()
│   │   - igny8_update_keywords_on_post_publish()
│   │   - igny8_map_wp_status_to_igny8()
│   │
│   ├── taxonomy-sync.php           # Taxonomy sync
│   │   - Clusters (igny8_clusters)
│   │   - Sectors (igny8_sectors)
│   │   - Categories/Tags mapping
│   │
│   └── igny8-to-wp.php             # IGNY8 → WP content
│       - igny8_create_wordpress_post_from_task()
│       - igny8_import_seo_metadata()
│       - igny8_import_featured_image()
│       - igny8_import_taxonomies()
│
├── admin/                          # Admin interface
│   ├── settings.php                # Settings page UI
│   │   - API credentials
│   │   - Connection status
│   │   - Post type toggles
│   │
│   ├── class-admin.php             # Settings controller
│   ├── class-admin-columns.php     # Custom post columns
│   └── class-post-meta-boxes.php   # Post editor meta boxes
│
├── data/                           # Data collection
│   ├── site-collection.php         # Full site scan
│   ├── semantic-mapping.php        # Semantic metadata
│   ├── link-graph.php              # Internal link graph
│   └── woocommerce.php             # WooCommerce integration
│
└── docs/                           # Documentation
    ├── WORDPRESS-PLUGIN-INTEGRATION.md
    ├── AUTHENTICATION-AUDIT.md
    └── SYNC-DATA-FLOW-DIAGRAM.md

Key Plugin Classes

Igny8API

  • Purpose: Communication with IGNY8 REST API
  • Location: includes/class-igny8-api.php
  • Key Methods:
    • login($email, $password) - Authenticate and get JWT tokens
    • refresh_token() - Refresh expired access token
    • get($endpoint) - GET request with auto-retry on 401
    • post($endpoint, $data) - POST request
    • put($endpoint, $data) - PUT request (for updates)
    • parse_response($response) - Parse unified response format

Igny8RestAPI

  • Purpose: Provide REST endpoints for IGNY8 to call
  • Location: includes/class-igny8-rest-api.php
  • Authentication: API key via X-IGNY8-API-KEY header or Bearer token
  • Response Format: Unified {success, data, message, request_id}

IGNY8 Backend Integration Points

Database Models

Content Model (igny8_core/business/content/models.py)

class Content(SiteSectorBaseModel):
    """Main content storage in IGNY8"""
    
    # Core fields
    title = CharField(max_length=255)
    content_html = TextField()  # Final HTML content
    word_count = IntegerField()
    
    # SEO fields
    meta_title = CharField(max_length=255)
    meta_description = TextField()
    primary_keyword = CharField(max_length=255)
    secondary_keywords = JSONField(default=list)
    
    # Content classification
    cluster = ForeignKey('planner.Clusters')  # Required
    content_type = CharField(choices=['post', 'page', 'product', 'taxonomy'])
    content_structure = CharField(choices=['article', 'guide', 'comparison', ...])
    
    # WordPress sync fields
    external_id = CharField()        # WordPress post ID
    external_url = URLField()        # WordPress post URL
    external_type = CharField()      # WordPress post type
    sync_status = CharField()        # Sync status
    
    # Status
    status = CharField(choices=['draft', 'review', 'published'])
    source = CharField(choices=['igny8', 'wordpress'])

SiteIntegration Model (igny8_core/business/integration/models.py)

class SiteIntegration(AccountBaseModel):
    """WordPress connection configuration"""
    
    site = ForeignKey('Site')
    platform = CharField(choices=['wordpress', 'shopify', 'custom'])
    
    # Configuration
    config_json = JSONField()  # {site_url, endpoints, ...}
    credentials_json = JSONField()  # {api_key, username, password}
    
    # Sync tracking
    is_active = BooleanField(default=True)
    sync_enabled = BooleanField(default=False)
    last_sync_at = DateTimeField()
    sync_status = CharField(choices=['success', 'failed', 'pending', 'syncing'])

PublishingRecord Model (igny8_core/business/publishing/models.py)

class PublishingRecord(SiteSectorBaseModel):
    """Track content publishing to destinations"""
    
    content = ForeignKey('writer.Content')
    destination = CharField()  # 'wordpress', 'sites', 'shopify'
    destination_id = CharField()  # External platform ID
    destination_url = URLField()  # Published URL
    
    status = CharField(choices=['pending', 'publishing', 'published', 'failed'])
    published_at = DateTimeField()
    error_message = TextField()
    metadata = JSONField()

Backend Services

PublisherService (igny8_core/business/publishing/services/publisher_service.py)

  • Purpose: Main publishing orchestrator
  • Key Method: publish_content(content_id, destinations, account)
  • Flow:
    1. Lookup Content by ID
    2. For each destination → call _publish_to_destination()
    3. Create PublishingRecord
    4. Get adapter (WordPressAdapter)
    5. Get SiteIntegration config
    6. Call adapter.publish()
    7. Update PublishingRecord status

WordPressAdapter (igny8_core/business/publishing/services/adapters/wordpress_adapter.py)

  • Purpose: WordPress-specific publishing logic
  • Authentication: Auto-detects API key vs username/password
  • Key Methods:
    • publish(content, destination_config) - Main publish method
    • _publish_via_api_key() - Uses /wp-json/igny8/v1/publish-content/
    • _publish_via_username_password() - Uses standard WP REST API
  • Payload:
    {
        'content_id': content.id,
        'title': content.title,
        'content_html': content.content_html,
        'content_type': content.content_type,
        'content_structure': content.content_structure,
        'meta_title': content.meta_title,
        'meta_description': content.meta_description,
        'status': content.status,
        'cluster_id': content.cluster.id,
        'sector_id': content.sector.id,
        'featured_image': {...},
        'categories': [...],
        'tags': [...]
    }
    

Celery Tasks (igny8_core/tasks/wordpress_publishing.py)

  • Purpose: Async background publishing
  • Task: publish_content_to_wordpress(content_id, site_integration_id)
  • Features:
    • Retry logic (3 retries, exponential backoff)
    • Duplicate check (prevents re-publishing)
    • Updates Content.external_id and external_url
    • Comprehensive logging

Authentication & Security

IGNY8 → WordPress Authentication

Method 1: API Key (Preferred)

  • Storage: SiteIntegration.credentials_json['api_key']
  • WordPress Storage: igny8_api_key option (encrypted if available)
  • Header: X-IGNY8-API-KEY: {api_key} or Authorization: Bearer {api_key}
  • Endpoint: /wp-json/igny8/v1/publish-content/
  • Security: API key validated in Igny8RestAPI::check_permission()

Method 2: Username/Password (Legacy)

  • Storage: SiteIntegration.credentials_json['username'] and password
  • Flow: Uses standard WordPress REST API authentication
  • Limitation: Requires Application Password or JWT plugin

WordPress → IGNY8 Authentication

JWT Token Flow

  1. Login: POST /api/v1/auth/login/ with {email, password}
  2. Response: {access, refresh} tokens
  3. Storage: igny8_access_token and igny8_refresh_token WordPress options
  4. Usage: Authorization: Bearer {access_token} header
  5. Refresh: On 401, call POST /api/v1/auth/refresh/ with {refresh}
  6. Auto-retry: Igny8API class automatically refreshes and retries on 401

Credentials Required (WordPress Settings)

  • Email: igny8_email option
  • Password: Stored temporarily for token refresh
  • API Key: igny8_api_key option (for incoming requests from IGNY8)

Bidirectional Sync: Data Elements

IGNY8 → WordPress (Publishing)

Content Fields Synced:

IGNY8 Field WordPress Destination Notes
title post_title Sanitized
content_html post_content Passed through wp_kses_post()
meta_title _yoast_wpseo_title
_seopress_titles_title
_aioseo_title
_igny8_meta_title
Multi-SEO plugin support
meta_description _yoast_wpseo_metadesc
_seopress_titles_desc
_aioseo_description
_igny8_meta_description
Multi-SEO plugin support
status post_status Mapped via igny8_map_igny8_status_to_wp()
content_type post_type post, page, product
content_structure _igny8_content_structure Meta field for tracking
cluster.id _igny8_cluster_id
igny8_clusters taxonomy
Both meta and taxonomy term
sector.id _igny8_sector_id
igny8_sectors taxonomy
Both meta and taxonomy term
task_id _igny8_task_id Task reference
id (Content) _igny8_content_id Content reference
primary_keyword _igny8_primary_keyword SEO keyword
secondary_keywords _igny8_secondary_keywords JSON array
featured_image.url Featured Image (attachment) Downloaded and attached
gallery_images[] _igny8_gallery_images Image attachments
categories[] category taxonomy Standard WP categories
tags[] post_tag taxonomy Standard WP tags

Post Meta Fields Created:

// IGNY8 identification
_igny8_task_id          // Task ID from IGNY8
_igny8_content_id       // Content ID from IGNY8
_igny8_cluster_id       // Cluster ID
_igny8_sector_id        // Sector ID
_igny8_source           // 'igny8' or 'wordpress'

// Content metadata
_igny8_content_type     // 'post', 'page', 'product'
_igny8_content_structure // 'article', 'guide', etc.

// SEO fields (generic)
_igny8_meta_title       // SEO title
_igny8_meta_description // SEO description
_igny8_primary_keyword  // Primary keyword
_igny8_secondary_keywords // JSON array

// Sync tracking
_igny8_wordpress_status // WordPress post_status
_igny8_last_synced      // Last sync timestamp
_igny8_brief_cached_at  // Brief cache timestamp
_igny8_task_brief       // Cached task brief

WordPress → IGNY8 (Status Sync)

Fields Synced on Post Save:

WordPress Field IGNY8 Endpoint IGNY8 Field Trigger
post_status PUT /writer/tasks/{id}/ status (mapped)
wordpress_status (raw)
save_post hook
ID PUT /writer/tasks/{id}/ assigned_post_id save_post hook
permalink PUT /writer/tasks/{id}/ post_url save_post hook
post_type PUT /writer/tasks/{id}/ post_type save_post hook
Post updated PUT /writer/tasks/{id}/ synced_at save_post hook

Sync Conditions:

  • Only syncs if post has _igny8_task_id meta (IGNY8-managed posts)
  • Skips autosaves and revisions
  • Only runs if connection is enabled (igny8_is_connection_enabled())
  • Implemented in sync/post-sync.php

Site Metadata Discovery (WordPress → IGNY8)

Endpoint: GET /wp-json/igny8/v1/site-metadata/

Data Collected:

{
  "success": true,
  "data": {
    "site_info": {
      "name": "Site Name",
      "url": "https://example.com",
      "description": "Site description",
      "language": "en_US",
      "timezone": "America/New_York"
    },
    "post_types": [
      {
        "name": "post",
        "label": "Posts",
        "count": 150,
        "supports": ["title", "editor", "thumbnail", "excerpt"]
      },
      {
        "name": "page",
        "label": "Pages",
        "count": 25
      },
      {
        "name": "product",
        "label": "Products",
        "count": 80
      }
    ],
    "taxonomies": [
      {
        "name": "category",
        "label": "Categories",
        "count": 20,
        "terms": [...]
      },
      {
        "name": "post_tag",
        "label": "Tags",
        "count": 150
      }
    ],
    "plugins": {
      "woocommerce": true,
      "yoast_seo": true,
      "advanced_custom_fields": false
    }
  }
}

Cache: Transient igny8_site_metadata_v1 (5 minutes)


Publishing Flow (IGNY8 → WordPress)

Complete Flow (Frontend to WordPress)

Step 1: User Clicks Publish Button

  • File: frontend/src/pages/Writer/Review.tsx
  • Method: handlePublishSingle()
  • Action: POST /api/v1/publisher/publish/
  • Payload: {content_id, destinations: ['wordpress']}

Step 2: Publisher API Endpoint

  • File: igny8_core/modules/publisher/views.py
  • Class: PublisherViewSet
  • Method: publish() action
  • Logging: 🚀 Publish request received
  • Action: Call PublisherService.publish_content()

Step 3: Publisher Service

  • File: igny8_core/business/publishing/services/publisher_service.py
  • Method: publish_content(content_id, destinations, account)
  • Steps:
    1. Load Content from database
    2. For each destination, call _publish_to_destination()
    3. Create PublishingRecord (status='pending')
    4. Get adapter for destination
    5. Lookup SiteIntegration for site
    6. Merge config: {**config_json, **credentials_json, 'site_url': ...}
    7. Call adapter.publish(content, config)
    8. Update PublishingRecord (status='published' or 'failed')

Step 4: WordPress Adapter

  • File: igny8_core/business/publishing/services/adapters/wordpress_adapter.py
  • Method: publish(content, destination_config)
  • Auto-detection:
    if config.get('api_key'):
        return _publish_via_api_key(...)
    else:
        return _publish_via_username_password(...)
    

Step 4a: API Key Method (Preferred)

  • Method: _publish_via_api_key()
  • Endpoint: POST {site_url}/wp-json/igny8/v1/publish-content/
  • Headers: X-IGNY8-API-KEY: {api_key}
  • Payload:
    {
      "content_id": 123,
      "title": "Post Title",
      "content_html": "<h2>Introduction</h2><p>...</p>",
      "content_type": "post",
      "content_structure": "article",
      "meta_title": "SEO Title",
      "meta_description": "SEO Description",
      "status": "published",
      "cluster_id": 45,
      "sector_id": 12,
      "featured_image": {...},
      "categories": [...],
      "tags": [...]
    }
    
  • Response: {success, data: {post_id, url}}

Step 5: WordPress REST Endpoint

  • File: includes/class-igny8-rest-api.php
  • Method: publish_content_to_wordpress()
  • Endpoint: /wp-json/igny8/v1/publish-content/
  • Permission: check_permission() validates API key
  • Action: Call igny8_create_wordpress_post_from_task($content_data)

Step 6: WordPress Post Creation

  • File: sync/igny8-to-wp.php
  • Function: igny8_create_wordpress_post_from_task()
  • Steps:
    1. Resolve post type from content_type
    2. Map author (via igny8_map_content_author())
    3. Prepare post data
    4. wp_insert_post() - Create WordPress post
    5. Import SEO metadata (igny8_import_seo_metadata())
    6. Import featured image (igny8_import_featured_image())
    7. Import taxonomies (igny8_import_taxonomies())
    8. Set cluster/sector taxonomy terms
    9. Set categories/tags
    10. Store IGNY8 meta fields
    11. Update IGNY8 task via API (PUT /writer/tasks/{id}/)
  • Return: WordPress post ID or WP_Error

Step 7: Response Propagation

  • WordPress → WordPress Adapter → Publisher Service → Publisher ViewSet → Frontend
  • Frontend Success:
    console.log('[Review.handlePublishSingle] ✅ Publish successful:', response)
    toast.success('Published to WordPress!')
    

Alternative: Celery Background Task

When: If using async publishing (currently available but not default)

Trigger: publish_content_to_wordpress.delay(content_id, site_integration_id)

File: igny8_core/tasks/wordpress_publishing.py

Benefits:

  • Non-blocking (user doesn't wait)
  • Retry logic (3 attempts with exponential backoff)
  • Duplicate check (prevents re-publishing same content)

Flow:

  1. Load Content and SiteIntegration from DB
  2. Check if already published (has external_id)
  3. Prepare payload
  4. POST to WordPress API
  5. Update Content.external_id and external_url
  6. Log success/failure

Sync Flow (WordPress → IGNY8)

Post Status Sync

Trigger: WordPress save_post, publish_post, transition_post_status hooks

File: sync/post-sync.php

Function: igny8_sync_post_status_to_igny8($post_id, $post, $update)

Flow:

  1. Check if connection enabled
  2. Skip autosaves and revisions
  3. Check if IGNY8-managed (has _igny8_task_id meta)
  4. Get $task_id from post meta
  5. Get WordPress post_status
  6. Map to IGNY8 status: igny8_map_wp_status_to_igny8($post_status)
  7. Prepare update payload:
    [
        'status' => $task_status,  // IGNY8 mapped status
        'assigned_post_id' => $post_id,
        'post_url' => get_permalink($post_id),
        'wordpress_status' => $post_status,  // Raw WP status
        'synced_at' => current_time('mysql'),
        'content_id' => $content_id  // If available
    ]
    
  8. PUT /writer/tasks/{task_id}/ via Igny8API
  9. Update _igny8_wordpress_status and _igny8_last_synced meta

Logged in WordPress:

IGNY8: Synced post 123 status (publish) to task 456

Keyword Status Update on Publish

Trigger: Post published

Function: igny8_update_keywords_on_post_publish($post_id)

Flow:

  1. Get task_id from post meta
  2. Get task details: GET /writer/tasks/{task_id}/
  3. Extract cluster_id and keyword_ids
  4. Update keyword statuses: PUT /planner/keywords/bulk-update/
  5. Mark keywords as "published" or "in_content"

REST API Endpoints

IGNY8 Backend Endpoints (Called by WordPress)

Method Endpoint Purpose Request Response
POST /api/v1/auth/login/ Get JWT tokens {email, password} {access, refresh}
POST /api/v1/auth/refresh/ Refresh access token {refresh} {access, refresh}
GET /api/v1/writer/tasks/{id}/ Get task details - Task object
PUT /api/v1/writer/tasks/{id}/ Update task status {status, assigned_post_id, post_url, wordpress_status} Updated task
GET /api/v1/writer/tasks/{id}/brief/ Get task brief - Brief content
PUT /api/v1/planner/keywords/bulk-update/ Update keyword statuses {keyword_ids, status} Updated keywords
POST /api/v1/system/sites/{id}/import/ Import site data Site metadata payload Import result

WordPress REST Endpoints (Called by IGNY8)

Method Endpoint Purpose Auth Response
GET /wp-json/igny8/v1/post-by-content-id/{id} Get post by Content ID API key {success, data: {post_id, title, status, url}}
GET /wp-json/igny8/v1/post-by-task-id/{id} Get post by Task ID API key {success, data: {post_id, title, status, url}}
GET /wp-json/igny8/v1/post-status/{content_id} Get post status API key {success, data: {wordpress_status, igny8_status}}
GET /wp-json/igny8/v1/site-metadata/ Get site structure API key {success, data: {site_info, post_types, taxonomies}}
GET /wp-json/igny8/v1/status Plugin health check Public {success, data: {connected, api_key_present}}
POST /wp-json/igny8/v1/publish-content/ Publish content from IGNY8 API key {success, data: {post_id, url}}

Authentication:

  • API Key: X-IGNY8-API-KEY header or Authorization: Bearer {api_key}
  • Validation: Igny8RestAPI::check_permission() compares with igny8_api_key option

Data Models & Meta Fields

WordPress Post Meta Taxonomy

All IGNY8 meta fields use _igny8_ prefix

// IGNY8 References
_igny8_task_id              // INT - IGNY8 Task ID
_igny8_content_id           // INT - IGNY8 Content ID
_igny8_cluster_id           // INT - IGNY8 Cluster ID
_igny8_sector_id            // INT - IGNY8 Sector ID

// Content Classification
_igny8_content_type         // STRING - 'post', 'page', 'product', 'taxonomy'
_igny8_content_structure    // STRING - 'article', 'guide', 'comparison', 'review', etc.
_igny8_source               // STRING - 'igny8' or 'wordpress'

// SEO Fields (Generic - not plugin-specific)
_igny8_meta_title           // STRING - SEO meta title
_igny8_meta_description     // TEXT - SEO meta description
_igny8_primary_keyword      // STRING - Primary SEO keyword
_igny8_secondary_keywords   // JSON - ["keyword1", "keyword2"]

// Keywords
_igny8_keyword_ids          // JSON - [1, 2, 3]

// Sync Tracking
_igny8_wordpress_status     // STRING - WordPress post_status at last sync
_igny8_last_synced          // DATETIME - Last sync timestamp
_igny8_brief_cached_at      // DATETIME - Brief cache timestamp
_igny8_task_brief           // JSON - Cached task brief data

// Images
_igny8_gallery_images       // JSON - Gallery image IDs

WordPress Custom Taxonomies

Created by Plugin:

// IGNY8 Clusters
register_taxonomy('igny8_clusters', ['post', 'page'], [
    'label' => 'IGNY8 Clusters',
    'hierarchical' => true,
    'show_in_rest' => true
]);

// IGNY8 Sectors
register_taxonomy('igny8_sectors', ['post', 'page'], [
    'label' => 'IGNY8 Sectors',
    'hierarchical' => true,
    'show_in_rest' => true
]);

Term Meta:

  • _igny8_cluster_id - IGNY8 cluster ID
  • _igny8_sector_id - IGNY8 sector ID

WordPress Options

// Authentication
igny8_email                 // User email for IGNY8 account
igny8_access_token          // JWT access token
igny8_refresh_token         // JWT refresh token
igny8_api_key               // API key (encrypted if available)

// Connection
igny8_connection_enabled    // BOOL - Enable/disable sync
igny8_connection_status     // STRING - 'connected', 'disconnected', 'error'

// Settings
igny8_allowed_post_types    // JSON - ['post', 'page', 'product']
igny8_auto_import_enabled   // BOOL - Auto-import content from IGNY8
igny8_sync_mode             // STRING - 'manual', 'auto'

// Cache
igny8_site_metadata_v1      // Transient (5 min) - Cached site metadata

IGNY8 Database Tables

Content Table (igny8_content)

  • Primary table for all content
  • Fields: title, content_html, meta_title, meta_description, cluster_id, sector_id, status, external_id, external_url

Tasks Table (igny8_tasks)

  • Content generation tasks
  • Fields: title, cluster_id, status, assigned_post_id, post_url

SiteIntegration Table (igny8_site_integrations)

  • WordPress connection configs
  • Fields: site_id, platform, config_json, credentials_json, is_active, sync_status

PublishingRecord Table (igny8_publishing_records)

  • Track all publish operations
  • Fields: content_id, destination, destination_id, destination_url, status, published_at

Status Mapping

IGNY8 Status → WordPress Status

Function: igny8_map_igny8_status_to_wp($igny8_status) (WordPress)

IGNY8 Status WordPress Status Notes
completed publish Published and live
draft draft Draft/unpublished
pending pending Pending review
scheduled future Scheduled for future
archived trash Trashed
(other) draft Default fallback

WordPress Status → IGNY8 Status

Function: igny8_map_wp_status_to_igny8($wp_status) (WordPress)

WordPress Status IGNY8 Status Notes
publish completed Published
future completed Scheduled (counts as completed)
private completed Private but complete
pending pending Pending review
draft draft Draft
auto-draft draft Auto-draft
trash archived Trashed
(other) draft Default fallback

Important: WordPress sends raw wordpress_status AND mapped IGNY8 status to backend


Troubleshooting Guide

Common Issues

1. "Failed to publish: undefined"

Symptoms:

  • Error message in frontend when clicking publish
  • No post created in WordPress

Diagnosis Steps:

  1. Check browser console:

    [Review.handlePublishSingle] Starting publish for content: {id, title}
    [Review.handlePublishSingle] Calling API endpoint /v1/publisher/publish/
    
  2. Check Django logs:

    [PublisherViewSet.publish] 🚀 Publish request received: content_id=X
    [PublisherService.publish_content] 📄 Content found: title='...'
    [WordPressAdapter.publish] 🎬 Starting WordPress publish
    
  3. Check WordPress logs:

    IGNY8: Creating WordPress post from IGNY8 content
    IGNY8: Post created with ID 123
    

Common Causes:

  • SiteIntegration missing API key → Check credentials_json['api_key']
  • WordPress plugin not active → Verify plugin activated
  • API key mismatch → Compare IGNY8 and WordPress api_key
  • site_url not set → Check SiteIntegration.config_json['site_url']
  • Content missing content_html → Content.content_html is empty

Fixes:

  • Ensure SiteIntegration has both config_json['site_url'] and credentials_json['api_key']
  • Verify WordPress endpoint accessible: GET {site_url}/wp-json/igny8/v1/status
  • Check Content model has content_html field populated

2. Posts Not Syncing Back to IGNY8

Symptoms:

  • Edit post in WordPress, status not updated in IGNY8
  • Post published in WordPress, keyword status not changing in IGNY8

Diagnosis:

  1. Check if post has _igny8_task_id: get_post_meta($post_id, '_igny8_task_id', true)
  2. Check connection enabled: get_option('igny8_connection_enabled')
  3. Check WordPress error logs for API failures

Common Causes:

  • Post not IGNY8-managed (missing _igny8_task_id)
  • Connection disabled in settings
  • Access token expired and refresh failed
  • Network/firewall blocking outbound requests

Fixes:

  • Only IGNY8-created posts sync (have _igny8_task_id)
  • Enable connection in Settings → IGNY8 API
  • Re-authenticate: delete igny8_access_token and igny8_refresh_token, save settings again
  • Check server can reach api.igny8.com

3. SEO Metadata Not Appearing

Symptoms:

  • Meta title/description not in Yoast/RankMath/SEOPress

Diagnosis:

  1. Check if generic meta exists: get_post_meta($post_id, '_igny8_meta_title')
  2. Check SEO plugin-specific meta: get_post_meta($post_id, '_yoast_wpseo_title')

Common Causes:

  • Content model missing meta_title or meta_description
  • SEO plugin not detected during import

Fixes:

  • Ensure Content has meta_title and meta_description populated
  • Function igny8_import_seo_metadata() writes to all major SEO plugins
  • Manually copy from _igny8_meta_title to SEO plugin field

4. Images Not Importing

Symptoms:

  • Featured image not set
  • Images in content not downloaded

Diagnosis:

  1. Check if featured_image in payload
  2. Check WordPress upload permissions
  3. Check igny8_import_featured_image() logs

Common Causes:

  • Image URL not accessible (403, 404)
  • WordPress upload directory not writable
  • allow_url_fopen disabled in PHP

Fixes:

  • Verify image URLs publicly accessible
  • Check wp-content/uploads/ permissions (775 or 755)
  • Enable allow_url_fopen in php.ini

5. Duplicate Posts Created

Symptoms:

  • Same content published multiple times in WordPress
  • Multiple posts with same _igny8_content_id

Diagnosis:

  1. Check if Celery task has duplicate check
  2. Check if external_id set on Content after first publish

Common Causes:

  • Celery task retrying after network error
  • Content.external_id not being saved
  • Multiple publish button clicks

Fixes:

  • Celery task has duplicate check: if content.external_id: return
  • Ensure WordPressAdapter saves external_id after successful publish
  • Disable publish button after first click (frontend)

Debug Mode

Enable WordPress Debug Logging:

// wp-config.php
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
define('IGNY8_DEBUG', true);  // Plugin-specific

Enable Django Logging:

# settings.py
LOGGING = {
    'version': 1,
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
        },
    },
    'loggers': {
        'igny8_core.business.publishing': {
            'handlers': ['console'],
            'level': 'DEBUG',
        },
    },
}

Check Logs:

  • WordPress: wp-content/debug.log
  • Django: Console output or configured log file
  • Browser: Console (F12 → Console tab)

Health Check Endpoints

WordPress Plugin Status:

GET https://yoursite.com/wp-json/igny8/v1/status

Response:

{
  "success": true,
  "data": {
    "plugin_version": "1.0.0",
    "connected": true,
    "api_key_present": true,
    "token_present": true,
    "connection_enabled": true
  }
}

IGNY8 API Health:

GET https://api.igny8.com/api/v1/health/

Common WordPress Errors

Error: IGNY8 API key not authenticated

  • Cause: API key missing or invalid
  • Fix: Settings → IGNY8 API → Enter API key

Error: IGNY8 connection is disabled

  • Cause: Connection toggle disabled in settings
  • Fix: Settings → IGNY8 API → Enable "Enable Connection"

Error: Post not found for this content ID

  • Cause: WordPress post with _igny8_content_id doesn't exist
  • Fix: Content not yet published to WordPress or meta field missing

Developer Quick Reference

Quick Start: New Developer Onboarding

1. Understand the Architecture

  • Read this document (WORDPRESS_INTEGRATION_REFERENCE.md)
  • Read MASTER_REFERENCE.md for overall IGNY8 architecture
  • Review docs/WORDPRESS-PLUGIN-INTEGRATION.md for plugin details

2. Setup Local Environment

WordPress Plugin:

# Clone plugin
cd wp-content/plugins/
git clone <repo> igny8-bridge

# Activate plugin in WordPress admin
# Go to Settings → IGNY8 API
# Enter credentials: Email, Password, API Key

IGNY8 Backend:

cd igny8/backend
pip install -r requirements.txt
python manage.py migrate
python manage.py runserver

# Start Celery (for async tasks)
celery -A igny8_core worker -l info

3. Test Publishing Flow

# 1. Create Content in IGNY8
# 2. Click Publish in Review page
# 3. Check browser console for logs
# 4. Check Django logs for backend flow
# 5. Check WordPress for created post
# 6. Edit post in WordPress
# 7. Check IGNY8 for updated status

Key Files to Modify

To Change Publishing Payload:

  • igny8_core/business/publishing/services/adapters/wordpress_adapter.py (IGNY8)
  • sync/igny8-to-wp.phpigny8_create_wordpress_post_from_task() (WordPress)

To Add New Meta Field:

  • wordpress_adapter.py → Add to payload
  • igny8-to-wp.php → Add to meta_input array
  • Update this document's meta fields table

To Add New REST Endpoint:

  • includes/class-igny8-rest-api.phpregister_routes()
  • Add permission callback
  • Add handler method

To Change Status Mapping:

  • sync/igny8-to-wp.phpigny8_map_igny8_status_to_wp()
  • sync/post-sync.phpigny8_map_wp_status_to_igny8()

Testing Checklist

IGNY8 → WordPress Publishing

  • Create content in IGNY8 Writer
  • Content has title, content_html, cluster
  • Click Publish button
  • Verify post created in WordPress
  • Check post has correct title and content
  • Verify SEO meta fields populated
  • Check featured image attached
  • Verify categories/tags assigned
  • Confirm cluster/sector taxonomies set
  • Check all _igny8_* meta fields present

WordPress → IGNY8 sync

  • Create IGNY8 content and publish to WP
  • Edit post in WordPress (change status to draft)
  • Check IGNY8 task status updated to 'draft'
  • Publish post in WordPress
  • Check IGNY8 task status updated to 'completed'
  • Verify wordpress_status field shows 'publish'

Site Metadata Discovery

  • Call GET /wp-json/igny8/v1/site-metadata/
  • Verify post types listed
  • Check taxonomies included
  • Confirm counts accurate
  • Check WooCommerce detected (if installed)

Code Snippets

WordPress: Get IGNY8 Content ID from Post

$content_id = get_post_meta($post_id, '_igny8_content_id', true);
if ($content_id) {
    echo "This post is IGNY8 Content ID: " . $content_id;
}

WordPress: Check if Post is IGNY8-Managed

function is_igny8_post($post_id) {
    $task_id = get_post_meta($post_id, '_igny8_task_id', true);
    return !empty($task_id);
}

IGNY8: Get WordPress Post ID from Content

from igny8_core.business.content.models import Content

content = Content.objects.get(id=123)
if content.external_id:
    print(f"WordPress Post ID: {content.external_id}")
    print(f"WordPress URL: {content.external_url}")

IGNY8: Manually Trigger Publish

from igny8_core.tasks.wordpress_publishing import publish_content_to_wordpress

# Queue Celery task
publish_content_to_wordpress.delay(
    content_id=123,
    site_integration_id=1
)

WordPress: Call IGNY8 API

$api = new Igny8API();
$response = $api->get('/writer/tasks/123/');

if ($response['success']) {
    $task = $response['data'];
    echo $task['title'];
}

Useful Queries

WordPress: Find all IGNY8-managed posts

SELECT p.ID, p.post_title, pm.meta_value as content_id
FROM wp_posts p
INNER JOIN wp_postmeta pm ON p.ID = pm.post_id
WHERE pm.meta_key = '_igny8_content_id'
ORDER BY p.ID DESC;

IGNY8: Find all content published to WordPress

SELECT id, title, external_id, external_url, sync_status
FROM igny8_content
WHERE external_id IS NOT NULL
ORDER BY updated_at DESC;

WordPress: Find posts missing IGNY8 sync

SELECT p.ID, p.post_title
FROM wp_posts p
LEFT JOIN wp_postmeta pm ON p.ID = pm.post_id AND pm.meta_key = '_igny8_content_id'
WHERE p.post_type = 'post'
  AND p.post_status = 'publish'
  AND pm.meta_value IS NULL;

Integration Roadmap

Phase 1: Core Publishing ( Complete)

  • IGNY8 → WordPress content publishing
  • WordPress → IGNY8 status sync
  • Authentication (JWT + API Key)
  • SEO metadata sync
  • Featured images
  • Taxonomies (categories, tags, clusters, sectors)

Phase 2: Enhanced Sync (🔄 In Progress)

  • Site metadata discovery
  • Automated content import from WordPress
  • Bulk publishing
  • Scheduled publishing
  • Content update sync (detect WordPress edits)

Phase 3: Advanced Features (📋 Planned)

  • Internal linking recommendations
  • Content optimization scoring
  • Link graph sync
  • Automated interlinking
  • Content analytics sync

Phase 4: Automation (📋 Planned)

  • Webhooks (IGNY8 → WordPress events)
  • WP-CLI commands
  • Automated content refresh
  • SEO score monitoring
  • Broken link detection

Appendix: Data Flow Diagrams

Complete Publishing Architecture

┌────────────────────────────────────────────────────────────────┐
│                    IGNY8 SaaS Platform                          │
├────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  Frontend (React)                                         │  │
│  │  ────────────────                                         │  │
│  │  Review.tsx → Publish Button                             │  │
│  │  POST /api/v1/publisher/publish/                         │  │
│  │  {content_id: 123, destinations: ['wordpress']}          │  │
│  └────────────────────┬─────────────────────────────────────┘  │
│                       │                                         │
│                       ▼                                         │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  API Layer (Django REST Framework)                       │  │
│  │  ──────────────────────────────────                       │  │
│  │  PublisherViewSet.publish()                              │  │
│  │  - Validate request                                      │  │
│  │  - Call PublisherService                                 │  │
│  └────────────────────┬─────────────────────────────────────┘  │
│                       │                                         │
│                       ▼                                         │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  Business Logic (Services)                               │  │
│  │  ─────────────────────────────                            │  │
│  │  PublisherService.publish_content()                      │  │
│  │  1. Load Content from DB                                 │  │
│  │  2. Create PublishingRecord                              │  │
│  │  3. Get SiteIntegration config                           │  │
│  │  4. Call WordPressAdapter                                │  │
│  └────────────────────┬─────────────────────────────────────┘  │
│                       │                                         │
│                       ▼                                         │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  Adapter Layer (Platform-Specific)                       │  │
│  │  ──────────────────────────────────                       │  │
│  │  WordPressAdapter.publish()                              │  │
│  │  - Detect auth method (API key vs user/pass)            │  │
│  │  - Prepare payload                                       │  │
│  │  - POST to WordPress REST API                            │  │
│  │  - Handle response                                       │  │
│  └────────────────────┬─────────────────────────────────────┘  │
│                       │                                         │
│                       │ HTTPS Request                           │
│                       │ POST /wp-json/igny8/v1/publish-content/ │
│                       │ X-IGNY8-API-KEY: ***                    │
└───────────────────────┼─────────────────────────────────────────┘
                        │
                        ▼
┌────────────────────────────────────────────────────────────────┐
│               WordPress Site (yoursite.com)                     │
├────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  WordPress REST API                                       │  │
│  │  ──────────────────                                       │  │
│  │  /wp-json/igny8/v1/publish-content/                      │  │
│  │  - Validate API key                                      │  │
│  │  - Parse request                                         │  │
│  │  - Call content creation handler                         │  │
│  └────────────────────┬─────────────────────────────────────┘  │
│                       │                                         │
│                       ▼                                         │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  Content Handler (PHP)                                   │  │
│  │  ─────────────────────────                                │  │
│  │  igny8_create_wordpress_post_from_task()                 │  │
│  │  1. Resolve post type                                    │  │
│  │  2. Prepare post data                                    │  │
│  │  3. wp_insert_post()                                     │  │
│  │  4. Import SEO metadata                                  │  │
│  │  5. Import featured image                                │  │
│  │  6. Set taxonomies                                       │  │
│  │  7. Store IGNY8 meta fields                              │  │
│  └────────────────────┬─────────────────────────────────────┘  │
│                       │                                         │
│                       ▼                                         │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  WordPress Database                                       │  │
│  │  ──────────────────                                       │  │
│  │  wp_posts                                                 │  │
│  │  - ID, post_title, post_content, post_status             │  │
│  │                                                           │  │
│  │  wp_postmeta                                              │  │
│  │  - _igny8_content_id, _igny8_task_id, _igny8_cluster_id │  │
│  │  - _igny8_meta_title, _igny8_meta_description           │  │
│  │                                                           │  │
│  │  wp_term_relationships                                    │  │
│  │  - Categories, Tags, Clusters, Sectors                   │  │
│  └──────────────────────────────────────────────────────────┘  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

End of Document

For questions or issues, consult:

  • Main documentation: /docs/MASTER_REFERENCE.md
  • Plugin docs: /igny8-wp-integration/docs/WORDPRESS-PLUGIN-INTEGRATION.md
  • Publishing fixes: /igny8/FIXES-PUBLISH-FAILURE.md
  • GitHub Issues: Repository Issues