# 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](#integration-overview)
2. [Architecture](#architecture)
3. [WordPress Plugin Structure](#wordpress-plugin-structure)
4. [IGNY8 Backend Integration Points](#igny8-backend-integration-points)
5. [Authentication & Security](#authentication--security)
6. [Bidirectional Sync: Data Elements](#bidirectional-sync-data-elements)
7. [Publishing Flow (IGNY8 → WordPress)](#publishing-flow-igny8--wordpress)
8. [Sync Flow (WordPress → IGNY8)](#sync-flow-wordpress--igny8)
9. [REST API Endpoints](#rest-api-endpoints)
10. [Data Models & Meta Fields](#data-models--meta-fields)
11. [Status Mapping](#status-mapping)
12. [Troubleshooting Guide](#troubleshooting-guide)
13. [Developer Quick Reference](#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`)
```python
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`)
```python
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`)
```python
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:**
  ```python
  {
      '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`<br>`_seopress_titles_title`<br>`_aioseo_title`<br>`_igny8_meta_title` | Multi-SEO plugin support |
| `meta_description` | `_yoast_wpseo_metadesc`<br>`_seopress_titles_desc`<br>`_aioseo_description`<br>`_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`<br>`igny8_clusters` taxonomy | Both meta and taxonomy term |
| `sector.id` | `_igny8_sector_id`<br>`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:**

```php
// 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)<br>`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:**

```json
{
  "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:**
  ```python
  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:**
  ```json
  {
    "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:**
  ```javascript
  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:
   ```php
   [
       '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**

```php
// 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:**

```php
// 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

```php
// 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:**
   ```javascript
   [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:**
```php
// 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:**
```python
# 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:**
```bash
GET https://yoursite.com/wp-json/igny8/v1/status
```
Response:
```json
{
  "success": true,
  "data": {
    "plugin_version": "1.0.0",
    "connected": true,
    "api_key_present": true,
    "token_present": true,
    "connection_enabled": true
  }
}
```

**IGNY8 API Health:**
```bash
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:**
```bash
# 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:**
```bash
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**
```bash
# 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.php` → `igny8_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.php` → `register_routes()`
- Add permission callback
- Add handler method

**To Change Status Mapping:**
- `sync/igny8-to-wp.php` → `igny8_map_igny8_status_to_wp()`
- `sync/post-sync.php` → `igny8_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**
```php
$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**
```php
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**
```python
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**
```python
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**
```php
$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**
```sql
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**
```sql
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**
```sql
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)**
- [x] IGNY8 → WordPress content publishing
- [x] WordPress → IGNY8 status sync
- [x] Authentication (JWT + API Key)
- [x] SEO metadata sync
- [x] Featured images
- [x] Taxonomies (categories, tags, clusters, sectors)

**Phase 2: Enhanced Sync (🔄 In Progress)**
- [x] 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](https://github.com/alorig/igny8-wp-integration/issues)
