# IGNY8 Phase 2: External Linker & Backlinks (02E) ## SAG-Based External Backlink Campaign Engine **Document Version:** 1.0 **Date:** 2026-03-23 **Phase:** IGNY8 Phase 2 — Feature Expansion **Status:** Build Ready **Source of Truth:** Codebase at `/data/app/igny8/` **Audience:** Claude Code, Backend Developers, Architects --- ## 1. CURRENT STATE ### External Linking Today There is **no** backlink management in IGNY8. No external API integrations exist for link building platforms. No campaign generation, tracking, or KPI monitoring. Backlink building is entirely manual and external to the platform. ### What Exists - `SAGBlueprint` and `SAGCluster` models (01A) — provide the hierarchy and cluster assignments for target page identification - `Keywords` model (planner app) — provides search volume data for tier assignment - `Content` model with `content_type` and `content_structure` — classifies pages for tiering - `GSCMetricsCache` (02C) — provides organic traffic and impression data for KPI tracking - `SAGLink` and `LinkMap` models (02D) — internal link data complements external strategy - The `linker` app (02D) — provides app namespace for related models ### What Does Not Exist - No SAGCampaign model, SAGBacklink model, or CampaignKPISnapshot model - No page tier assignment system - No country-specific strategy profiles - No marketplace API integrations (FatGrid, PRNews.io, etc.) - No campaign generation algorithm - No anchor text planning - No quality scoring for backlink opportunities - No tipping point detection - No dead link monitoring for placed backlinks --- ## 2. WHAT TO BUILD ### Overview Build a SAG-based backlink campaign engine that generates country-specific link-building campaigns targeting hub pages. The system leverages the SAG hierarchy to focus backlinks on high-value pages (T1-T3) and lets internal linking (02D) distribute authority to supporting content. ### 2.1 Hub-Only Strategy (Core Principle) Backlinks target ONLY T1-T3 pages (homepage + cluster hubs + key service/product pages). SAG internal linking (02D) distributes authority from hubs downstream to 70+ supporting pages per cluster. **Budget Allocation:** - 70-85% to T1-T3 (homepage, top hubs, products/services) - 15-30% to T4-T5 (authority magnets: guides, tools, supporting articles) - 0% to term/taxonomy pages (get authority via internal links) Typically 20-30 target pages per site. ### 2.2 Page Tier Assignment | Tier | Pages | Links/Page Target | Description | |------|-------|-------------------|-------------| | **T1** | Homepage (1 page) | 10-30 | Brand authority anchor | | **T2** | Top 40% hubs by search volume | 5-15 | Primary money pages | | **T3** | Remaining hubs + products/services | 3-10 | Supporting money pages | | **T4** | Supporting blog articles | 1-4 | Content authority | | **T5** | Authority magnets (guides, tools) | 2-6 | Link bait pages | **Tier Assignment Algorithm:** 1. Load SAGBlueprint → identify all published hub pages 2. Sort hubs by total cluster keyword search volume (from Keywords model) 3. Top 40% of hubs = T2, remaining hubs = T3 4. Products/services pages = T3 5. Supporting blog articles = T4 6. Content with `content_structure` in (`guide`, `comparison`, `listicle`) and high word count = T5 ### 2.3 Country-Specific Strategy Profiles Four pre-built profiles with different timelines, budgets, and quality thresholds: | Parameter | Pakistan (PK) | Canada (CA) | UK | USA | |-----------|--------------|-------------|-----|------| | Timeline | 8 months | 12 months | 14 months | 18 months | | Budget range | $2-5K | $3-7K | $3-9K | $5-13K | | Target DR | 25-30 | 35-40 | 35-40 | 40-45 | | Quality threshold | ≥5/11 | ≥6/11 | ≥6/11 | ≥7/11 | | Exact match anchor | 5-10% | 3-7% | 3-7% | 2-5% | | Velocity phases | ramp→peak→cruise→maintenance | Same 4 phases | Same | Same | **Anchor Text Mix by Country:** | Anchor Type | PK | CA | UK | USA | |-------------|-----|-----|-----|------| | Branded | 30-35% | 35-40% | 35-40% | 35-45% | | Naked URL | 15-20% | 15-20% | 15-20% | 15-20% | | Generic | 15-20% | 15-20% | 15-20% | 15-20% | | Partial Match | 15-20% | 12-18% | 12-18% | 10-15% | | Exact Match | 5-10% | 3-7% | 3-7% | 2-5% | | LSI/Topical | 5-10% | 5-10% | 5-10% | 5-8% | | Brand+Keyword | — | 3-5% | 3-5% | 3-5% | ### 2.4 Campaign Generation Algorithm 1. Load SAGBlueprint → identify all published hub pages 2. Assign tiers based on search volume data (from Keywords model) 3. Select country profile → calculate `referring_domains_needed` 4. `links_per_tier = referring_domains_needed × tier_allocation_%` 5. `budget_estimate = links × cost_per_link × link_mix_%` 6. Distribute across monthly velocity curve (ramp → peak → cruise → maintenance) 7. Assign pages to months by priority (keyword difficulty, search volume, commercial value) 8. Pre-generate 3 anchor text variants per page per anchor type 9. Set quality requirements per country threshold ### 2.5 Marketplace Integrations #### FatGrid API - **Base URL:** `https://api.fatgrid.com/api/public` - **Auth:** API key in request header - **Endpoints:** - Domain Lookup — DR, DA, traffic, niche for a domain - Marketplace Browse — filterable by DR, traffic, price, niche - Bulk Domain Lookup — up to 1,000 domains per request - **15+ Aggregated Marketplaces:** Collaborator.pro, PRNews.io, Adsy.com, WhitePress.com, Bazoom.com, MeUp.com, etc. - **Usage:** IGNY8 proxies FatGrid API calls to find and filter link placement opportunities #### PR Distribution (3 Tiers) | Tier | Provider | Price Range | Reach | |------|----------|-------------|-------| | PR Basic | EIN Presswire | $99-499/release | AP News, Bloomberg, 115+ US TV | | PR Premium | PRNews.io | $500-5K/placement | Yahoo Finance, Forbes-tier publications | | PR Enterprise | Linking News (white-label) | $500-2K/distribution | ABC, NBC, FOX, Yahoo, Bloomberg | ### 2.6 Quality Scoring (Per Backlink Opportunity) **Auto-Checkable Factors (7 points):** 1. Organic traffic >500/month 2. Domain Rating / Domain Authority > country threshold 3. Indexed in Google 4. Not on known PBN/spam farm blocklist 5. Traffic trend stable or growing 6. Niche relevance to content topic 7. Dofollow link confirmed **Manual Review Factors (4 points):** 8. Outbound links <100 on linking page 9. Niche relevance (editorial check) 10. Editorial quality of surrounding content 11. Dofollow confirmed (manual verification) **Total: 0-11 points. Country thresholds:** PK ≥5, CA ≥6, UK ≥6, USA ≥7 ### 2.7 Authority Tipping Point Detection Monitor for 3+ simultaneous indicators: - Domain Rating reached country target - Pages with GSC impressions >100 but 0 SAGBacklinks start getting organic clicks - Un-linked pages rank on page 2-3 (positions 11-30) - New content ranks passively without dedicated backlinks - Keywords in top 10 exceed threshold: PK 10+, UK/CA 15+, USA 20+ **When triggered:** Recommend: reduce link-building velocity, shift budget to content creation, enter maintenance mode. ### 2.8 Dead Link Monitoring - Periodic HTTP checks on all placed backlinks (status=`live`) - Status tracking: `live` → `dead` (404/403/removed) → `replaced` - Impact scoring: estimate authority loss based on source DR and link type - Auto-generate replacement recommendations - Reserve 10-15% monthly budget for replacements --- ## 3. DATA MODELS & APIS ### 3.1 New Models All models in the `linker` app (same app as 02D). #### SAGCampaign (linker app) ```python class SAGCampaign(SiteSectorBaseModel): """ Backlink campaign generated from SAG data + country profile. """ blueprint = models.ForeignKey( 'planner.SAGBlueprint', on_delete=models.SET_NULL, null=True, blank=True, related_name='backlink_campaigns' ) country_code = models.CharField( max_length=3, help_text='PK, CA, UK, or USA' ) status = models.CharField( max_length=15, choices=[ ('draft', 'Draft'), ('active', 'Active'), ('paused', 'Paused'), ('completed', 'Completed'), ], default='draft' ) tier_assignments = models.JSONField( default=dict, help_text='{content_id: tier_level (T1/T2/T3/T4/T5)}' ) total_links_target = models.IntegerField(default=0) budget_estimate_min = models.DecimalField( max_digits=10, decimal_places=2, default=0 ) budget_estimate_max = models.DecimalField( max_digits=10, decimal_places=2, default=0 ) timeline_months = models.IntegerField(default=12) monthly_plan = models.JSONField( default=list, help_text='[{month, links_target, pages[], budget}]' ) anchor_text_plan = models.JSONField( default=dict, help_text='{content_id: [{text, type, allocated}]}' ) country_profile = models.JSONField( default=dict, help_text='Full profile snapshot at campaign creation' ) kpi_data = models.JSONField( default=dict, help_text='Monthly KPI snapshots summary' ) started_at = models.DateTimeField(null=True, blank=True) class Meta: app_label = 'linker' db_table = 'igny8_sag_campaigns' ``` **PK:** BigAutoField (integer) — inherits from SiteSectorBaseModel #### SAGBacklink (linker app) ```python class SAGBacklink(SiteSectorBaseModel): """ Individual backlink record within a campaign. Tracks from planning through placement to ongoing monitoring. """ campaign = models.ForeignKey( 'linker.SAGCampaign', on_delete=models.CASCADE, related_name='backlinks' ) blueprint = models.ForeignKey( 'planner.SAGBlueprint', on_delete=models.SET_NULL, null=True, blank=True ) target_content = models.ForeignKey( 'writer.Content', on_delete=models.CASCADE, related_name='backlinks' ) target_url = models.URLField() target_tier = models.CharField( max_length=3, choices=[ ('T1', 'Tier 1 — Homepage'), ('T2', 'Tier 2 — Top Hubs'), ('T3', 'Tier 3 — Other Hubs/Products'), ('T4', 'Tier 4 — Supporting Articles'), ('T5', 'Tier 5 — Authority Magnets'), ] ) source_url = models.URLField( blank=True, default='', help_text='May not be known at planning stage' ) source_domain = models.CharField(max_length=255, blank=True, default='') source_dr = models.IntegerField(null=True, blank=True) source_traffic = models.IntegerField(null=True, blank=True) anchor_text = models.CharField(max_length=200) anchor_type = models.CharField( max_length=20, choices=[ ('branded', 'Branded'), ('naked_url', 'Naked URL'), ('generic', 'Generic'), ('partial_match', 'Partial Match'), ('exact_match', 'Exact Match'), ('lsi', 'LSI/Topical'), ('brand_keyword', 'Brand + Keyword'), ] ) link_type = models.CharField( max_length=20, choices=[ ('guest_post', 'Guest Post'), ('niche_edit', 'Niche Edit'), ('pr_distribution', 'PR Distribution'), ('directory', 'Directory'), ('resource_page', 'Resource Page'), ('broken_link', 'Broken Link Building'), ('haro', 'HARO/Journalist Query'), ] ) marketplace = models.CharField( max_length=20, blank=True, default='', help_text='fatgrid, prnews, ein, linking_news, manual' ) cost = models.DecimalField( max_digits=10, decimal_places=2, null=True, blank=True ) quality_score = models.FloatField( null=True, blank=True, help_text='0-11 quality score' ) country_relevant = models.BooleanField(default=True) date_ordered = models.DateField(null=True, blank=True) date_live = models.DateField(null=True, blank=True) date_last_checked = models.DateField(null=True, blank=True) status = models.CharField( max_length=15, choices=[ ('planned', 'Planned'), ('ordered', 'Ordered'), ('live', 'Live'), ('dead', 'Dead'), ('replaced', 'Replaced'), ('rejected', 'Rejected'), ], default='planned' ) notes = models.TextField(blank=True, default='') class Meta: app_label = 'linker' db_table = 'igny8_sag_backlinks' ``` **PK:** BigAutoField (integer) — inherits from SiteSectorBaseModel #### CampaignKPISnapshot (linker app) ```python class CampaignKPISnapshot(SiteSectorBaseModel): """ Monthly KPI snapshot for a backlink campaign. Tracks domain metrics, link counts, keyword rankings, and tipping point indicators. """ campaign = models.ForeignKey( 'linker.SAGCampaign', on_delete=models.CASCADE, related_name='kpi_snapshots' ) snapshot_date = models.DateField() dr = models.FloatField(null=True, blank=True, help_text='Domain Rating') da = models.FloatField(null=True, blank=True, help_text='Domain Authority') referring_domains = models.IntegerField(default=0) new_links_this_month = models.IntegerField(default=0) links_by_tier = models.JSONField(default=dict, help_text='{T1: count, T2: count, ...}') cost_this_month = models.DecimalField(max_digits=10, decimal_places=2, default=0) cost_per_link_avg = models.DecimalField(max_digits=10, decimal_places=2, default=0) keywords_top_10 = models.IntegerField(default=0) keywords_top_20 = models.IntegerField(default=0) keywords_top_50 = models.IntegerField(default=0) organic_traffic = models.IntegerField( null=True, blank=True, help_text='From GSC via 02C GSCMetricsCache' ) impressions = models.IntegerField( null=True, blank=True, help_text='From GSC via 02C GSCMetricsCache' ) pages_ranking_without_backlinks = models.IntegerField(default=0) tipping_point_indicators = models.JSONField( default=dict, help_text='{indicator_name: True/False}' ) tipping_point_triggered = models.BooleanField(default=False) class Meta: app_label = 'linker' db_table = 'igny8_campaign_kpi_snapshots' ``` **PK:** BigAutoField (integer) — inherits from SiteSectorBaseModel ### 3.2 Migration ``` igny8_core/migrations/XXXX_add_backlink_models.py ``` **Operations:** 1. `CreateModel('SAGCampaign', ...)` — with index on country_code, status 2. `CreateModel('SAGBacklink', ...)` — with indexes on campaign, status, target_content 3. `CreateModel('CampaignKPISnapshot', ...)` — with index on campaign, snapshot_date ### 3.3 API Endpoints All endpoints under `/api/v1/linker/` (extending the linker URL namespace from 02D): #### Campaign Management | Method | Path | Description | |--------|------|-------------| | GET | `/api/v1/linker/campaigns/?site_id=X` | List backlink campaigns | | POST | `/api/v1/linker/campaigns/generate/` | AI-generate campaign from blueprint + country. Body: `{site_id, blueprint_id, country_code}`. | | GET | `/api/v1/linker/campaigns/{id}/` | Campaign detail with monthly plan | | PUT | `/api/v1/linker/campaigns/{id}/` | Update campaign (adjust plan, budget) | | POST | `/api/v1/linker/campaigns/{id}/activate/` | Start campaign (set status=active, started_at) | | POST | `/api/v1/linker/campaigns/{id}/pause/` | Pause active campaign | #### KPI Tracking | Method | Path | Description | |--------|------|-------------| | GET | `/api/v1/linker/campaigns/{id}/kpi/` | KPI snapshot timeline for campaign | | POST | `/api/v1/linker/campaigns/{id}/kpi/snapshot/` | Record monthly KPI. Body: `{dr, da, referring_domains, ...}`. | | GET | `/api/v1/linker/tipping-point/?campaign_id=X` | Tipping point analysis — current indicator state | #### Backlink Records | Method | Path | Description | |--------|------|-------------| | GET | `/api/v1/linker/backlinks/?campaign_id=X` | List backlinks with filters (status, tier, anchor_type) | | POST | `/api/v1/linker/backlinks/` | Add backlink record. Body: `{campaign_id, target_content_id, ...}`. | | PUT | `/api/v1/linker/backlinks/{id}/` | Update backlink status/details | | POST | `/api/v1/linker/backlinks/check/` | Trigger dead link check for campaign. Body: `{campaign_id}`. | #### Marketplace Proxy | Method | Path | Description | |--------|------|-------------| | GET | `/api/v1/linker/marketplace/search/` | FatGrid marketplace search. Query params: `dr_min, traffic_min, price_max, niche`. | | GET | `/api/v1/linker/marketplace/domain/{domain}/` | FatGrid domain lookup — DR, DA, traffic, niche. | **Permissions:** All endpoints use `SiteSectorModelViewSet` permission patterns. ### 3.4 Campaign Generation Service **Location:** `igny8_core/business/campaign_generation.py` ```python class CampaignGenerationService: """ Generates a backlink campaign from SAG data + country profile. """ COUNTRY_PROFILES = { 'PK': { 'timeline_months': 8, 'budget_min': 2000, 'budget_max': 5000, 'target_dr': 25, 'quality_threshold': 5, 'exact_match_max': 0.10, 'tier_allocation': {'T1': 0.25, 'T2': 0.35, 'T3': 0.25, 'T4': 0.10, 'T5': 0.05}, # ... full profile }, 'CA': { ... }, 'UK': { ... }, 'USA': { ... }, } def generate(self, site_id, blueprint_id, country_code): """ 1. Load blueprint → published hub pages 2. Assign tiers by search volume 3. Select country profile 4. Calculate total links needed + budget 5. Build monthly plan with velocity curve 6. Pre-generate anchor text variants 7. Create SAGCampaign record Returns SAGCampaign instance. """ pass def _assign_tiers(self, blueprint, published_content): """Sort hubs by search volume, assign T1-T5.""" pass def _build_monthly_plan(self, tier_assignments, country_profile): """Distribute links across months using velocity curve.""" pass def _generate_anchor_plans(self, tier_assignments, country_profile): """Pre-generate 3 anchor text variants per page per type.""" pass ``` ### 3.5 Quality Scoring Service **Location:** `igny8_core/business/backlink_quality.py` ```python class BacklinkQualityService: """ Scores backlink opportunities on 0-11 scale. """ def score(self, domain_data, country_code): """ Auto-check 7 factors: 1. Organic traffic >500/mo 2. DR/DA > country threshold 3. Indexed in Google 4. Not on blocklist 5. Traffic trend stable/growing 6. Niche relevance 7. Dofollow confirmed Returns (score, breakdown). """ pass def meets_threshold(self, score, country_code): """Check if score meets country minimum.""" pass ``` ### 3.6 Tipping Point Detector **Location:** `igny8_core/business/tipping_point.py` ```python class TippingPointDetector: """ Monitors campaign KPI snapshots for authority tipping point indicators. """ def evaluate(self, campaign_id): """ Check 5 indicators against latest KPI data + GSC data. If 3+ triggered, set tipping_point_triggered=True. Returns {indicators: {name: bool}, triggered: bool, recommendation: str} """ pass ``` ### 3.7 FatGrid API Client **Location:** `igny8_core/integration/fatgrid_client.py` ```python class FatGridClient: """ Client for FatGrid marketplace API. API key stored in SiteIntegration or account-level settings. """ BASE_URL = 'https://api.fatgrid.com/api/public' def __init__(self, api_key): self.api_key = api_key def search_marketplace(self, dr_min=None, traffic_min=None, price_max=None, niche=None, limit=50): """Browse marketplace with filters.""" pass def lookup_domain(self, domain): """Get DR, DA, traffic, niche for a domain.""" pass def bulk_lookup(self, domains): """Lookup up to 1,000 domains.""" pass ``` ### 3.8 Celery Tasks **Location:** `igny8_core/tasks/backlink_tasks.py` ```python @shared_task(name='check_backlink_status') def check_backlink_status(campaign_id): """Weekly HTTP check on all 'live' backlinks. Updates status to 'dead' if 4xx/5xx.""" pass @shared_task(name='record_kpi_snapshot') def record_kpi_snapshot(campaign_id): """Monthly KPI recording: pull GSC data, calculate keyword rankings.""" pass @shared_task(name='evaluate_tipping_point') def evaluate_tipping_point(campaign_id): """Monthly, after KPI snapshot. Check tipping point indicators.""" pass @shared_task(name='generate_replacement_recommendations') def generate_replacement_recommendations(campaign_id): """Triggered when dead links detected. Generate replacement suggestions.""" pass ``` **Beat Schedule Additions:** | Task | Schedule | Notes | |------|----------|-------| | `check_backlink_status` | Weekly (Thursday 3:00 AM) | HTTP check all live backlinks | | `record_kpi_snapshot` | Monthly (1st of month, 5:00 AM) | Record KPI for all active campaigns | | `evaluate_tipping_point` | Monthly (1st of month, 6:00 AM) | After KPI snapshot | --- ## 4. IMPLEMENTATION STEPS ### Step 1: Models 1. Create SAGCampaign, SAGBacklink, CampaignKPISnapshot in linker app (extends 02D) 2. Run migration ### Step 2: Country Profiles 1. Define 4 country profiles (PK, CA, UK, USA) as configuration data 2. Store as constants in `CampaignGenerationService` or as a seed data migration ### Step 3: Services 1. Implement `CampaignGenerationService` in `igny8_core/business/campaign_generation.py` 2. Implement `BacklinkQualityService` in `igny8_core/business/backlink_quality.py` 3. Implement `TippingPointDetector` in `igny8_core/business/tipping_point.py` 4. Implement `FatGridClient` in `igny8_core/integration/fatgrid_client.py` ### Step 4: API Endpoints 1. Add campaign, backlink, KPI, and marketplace endpoints to `igny8_core/urls/linker.py` 2. Create views: `CampaignViewSet`, `BacklinkViewSet`, `KPISnapshotView`, `TippingPointView` 3. Create `MarketplaceSearchView`, `MarketplaceDomainLookupView` (FatGrid proxy) ### Step 5: Celery Tasks 1. Implement 4 tasks in `igny8_core/tasks/backlink_tasks.py` 2. Add to Celery beat schedule (weekly backlink check, monthly KPI + tipping point) ### Step 6: Serializers & Admin 1. Create DRF serializers for SAGCampaign, SAGBacklink, CampaignKPISnapshot 2. Register models in Django admin ### Step 7: Credit Cost Configuration Add to `CreditCostConfig` (billing app): | operation_type | default_cost | description | |---------------|-------------|-------------| | `campaign_generation` | 2 | AI-generate campaign from blueprint + country | | `backlink_audit` | 1 | Dead link check for campaign | | `kpi_snapshot` | 0.5 | Monthly KPI recording | | `dead_link_detection` | 1 | Dead link detection + replacement recommendations | **Note:** FatGrid API calls consume FatGrid credits (separate billing — not IGNY8 credits). Store FatGrid API key in account-level settings or SiteIntegration. --- ## 5. ACCEPTANCE CRITERIA ### Campaign Generation - [ ] Campaign generated from SAGBlueprint + country code - [ ] Page tiers assigned based on search volume from Keywords model - [ ] Budget estimates calculated from country profile + tier allocation - [ ] Monthly plan distributed across velocity curve (ramp → peak → cruise → maintenance) - [ ] Anchor text plan pre-generated with 3 variants per page per anchor type - [ ] Country profile snapshot stored in campaign record ### Country Profiles - [ ] All 4 profiles (PK, CA, UK, USA) defined with correct parameters - [ ] Anchor text mix enforced per country (branded %, exact match %, etc.) - [ ] Quality thresholds enforced per country (PK ≥5, CA ≥6, UK ≥6, USA ≥7) - [ ] Timeline and budget ranges match specification ### Quality Scoring - [ ] 7 auto-checkable factors scored per backlink opportunity - [ ] Country-specific threshold enforcement - [ ] Quality score stored on SAGBacklink record ### Tipping Point - [ ] 5 indicators monitored from KPI data + GSC data - [ ] Triggered when 3+ indicators simultaneous - [ ] Recommendation generated when triggered (reduce velocity, shift to content) - [ ] tipping_point_triggered flag set on KPI snapshot ### Dead Link Monitoring - [ ] Weekly HTTP checks on all live backlinks - [ ] Status transitions: live → dead, with date tracking - [ ] Replacement recommendations generated for dead links - [ ] 10-15% budget reserve recommended in campaign plan ### Marketplace Integration - [ ] FatGrid marketplace search proxied through IGNY8 API - [ ] FatGrid domain lookup returns DR, DA, traffic, niche - [ ] API key stored securely (not exposed to frontend) --- ## 6. CLAUDE CODE INSTRUCTIONS ### File Locations ``` igny8_core/ ├── modules/ │ └── linker/ │ └── models.py # Add SAGCampaign, SAGBacklink, CampaignKPISnapshot ├── business/ │ ├── campaign_generation.py # CampaignGenerationService │ ├── backlink_quality.py # BacklinkQualityService │ └── tipping_point.py # TippingPointDetector ├── integration/ │ └── fatgrid_client.py # FatGridClient ├── tasks/ │ └── backlink_tasks.py # Celery tasks ├── urls/ │ └── linker.py # Extend with campaign/backlink endpoints └── migrations/ └── XXXX_add_backlink_models.py ``` ### Conventions - **PKs:** BigAutoField (integer) — do NOT use UUIDs - **Table prefix:** `igny8_` on all new tables - **App label:** `linker` (same app as 02D) - **Celery app name:** `igny8_core` - **URL pattern:** `/api/v1/linker/campaigns/...`, `/api/v1/linker/backlinks/...`, `/api/v1/linker/marketplace/...` - **Permissions:** Use `SiteSectorModelViewSet` permission pattern - **Model inheritance:** All new models extend `SiteSectorBaseModel` - **Frontend:** `.tsx` files with Zustand stores ### Cross-References | Doc | Relationship | |-----|-------------| | **02D** | Internal linking distributes authority from hub pages (backlink targets) to supporting content | | **02C** | GSC data feeds KPIs (organic traffic, impressions, keyword positions) | | **01A** | SAGBlueprint provides cluster hierarchy for tier assignment | | **04A** | Managed services package includes backlink campaign management for clients | | **01G** | SAG health monitoring can incorporate backlink campaign progress | ### Key Decisions 1. **Same `linker` app as 02D** — Internal and external linking share the same app since they're conceptually related; campaigns reference the same Content and Blueprint models 2. **Country profiles as code constants** — Stored in CampaignGenerationService class, not database, to prevent accidental modification; versioned with code 3. **FatGrid proxy** — Never expose FatGrid API key to frontend; all marketplace calls routed through IGNY8 backend 4. **KPI snapshots are manual + automatic** — Monthly auto-recording via Celery, but manual recording also supported for ad-hoc updates 5. **Separate billing for marketplace** — FatGrid credits are external; IGNY8 credits cover campaign generation, audits, and AI-powered anchor text generation