API Integration Guide

Written By Founder@SEOProAI

Last updated 6 months ago

SEOPro AI API Integration Guide

This guide shows how to programmatically generate and publish blog posts using SEOPro AI's REST API. Unlike webhooks (which are reactive), this API integration allows you to proactively create content for specific dates and manage your blog publishing workflow.

Overview

The SEOPro AI API provides endpoints to:

  • βœ… Generate blog posts for specific dates (async, returns immediately)

  • βœ… Poll for blog generation status

  • βœ… Fetch draft blogs with complete HTML content

  • βœ… Publish blog drafts to your platform

  • βœ… Check blog status by date

  • βœ… Manage scheduled content

  • βœ… Get blog generation limits and usage

Async Workflow (Recommended)

Blog generation takes 5-10 minutes, so the API uses an asynchronous workflow:

  1. Initiate Generation β†’ POST /generate-blog-for-date β†’ Returns 202 ACCEPTED immediately

  2. Poll for Status β†’ GET /blog-status-by-date β†’ Check if status is "draft" (ready)

  3. Fetch Draft Content β†’ GET /blog-drafts/{blog_id} β†’ Get complete HTML content

  4. Publish to Your Site β†’ POST /blog-drafts/{blog_id}/publish β†’ Publish to CMS

This pattern lets you:

  • Start generation without blocking your application

  • Poll periodically (every 30-60 seconds) until ready

  • Fetch the complete HTML content when draft is ready

  • Publish directly to your third-party website

Authentication

All API requests require authentication using API keys. API keys provide secure, programmatic access to the SEOPro AI API without exposing user credentials.

API Key Format

API keys follow this format:

sk_live_1234567_xxxxxxxxxxxxxxxxxxxxx

Using API Keys

Include your API key in the Authorization header of all requests:

Authorization: Bearer sk_live_1234567_xxxxxxxxxxxxxxxxxxxxx

Generating API Keys

  1. Navigate to Settings: Go to Settings > Integrations in your SEOPro AI dashboard

  2. Developer API Tab: Open the "Developer API" card and select "API Keys" tab

  3. Generate New Key: Click "Generate New Key"

  4. Name Your Key: Give it a descriptive name (e.g., "Production API" or "Node.js Integration")

  5. Copy and Save: Copy the full API key immediately - you won't be able to see it again!

⚠️ Important: Store your API key securely. Never commit it to version control or share it publicly.

Base URL

https://api.seoproai.co/api

Finding Your Website ID

Every API request requires your website_id. Here's how to find it:

From the Dashboard UI (Easiest)

  1. Go to Settings > Integrations > Developer API in your SEOPro AI dashboard

  2. Your Website ID is displayed at the top of the API Keys tab

  3. Click the copy button to copy it to your clipboard

Core API Endpoints

1. Generate Blog for Specific Date (Async)

Important: This endpoint returns immediately with 202 ACCEPTED. Blog generation happens in the background and takes 5-10 minutes.

POST /websites/123/generate-blog-for-date
Content-Type: application/json
Authorization: Bearer sk_live_1234567_xxxxxxxxxxxxxxxxxxxxx

{
  "scheduled_date": "2024-01-15",
  "keywords": ["your target keyword"],
  "language_code": "en"
}

Path Parameters:

  • website_id (required): Your website ID (e.g., 123) - See above on how to get it

Response (Immediate - 202 ACCEPTED):

{
  "status": "processing",
  "keyword_id": 789,
  "scheduled_date": "2024-01-15",
  "message": "Blog generation started. Use /blog-status-by-date to check progress."
}

Next Steps:

  1. Poll /blog-status-by-date every 30-60 seconds to check if blog is ready

  2. When status is "draft", fetch the content using /blog-drafts/{blog_id}

  3. Publish to your site when ready

2. Publish Blog for Specific Date

Publish an existing blog draft for a specific date:

POST /websites/123/blogs/publish-for-date
Content-Type: application/json
Authorization: Bearer sk_live_1234567_xxxxxxxxxxxxxxxxxxxxx

{
  "date": "2024-01-15"
}

Path Parameters:

  • website_id (required): Your website ID (e.g., 123)

Response:

{
  "status": "success",
  "blog_id": 12345,
  "published_url": "https://yoursite.com/blog/post-slug",
  "message": "Blog published successfully"
}

3. Check Blog Status by Date (Polling Endpoint)

Use this endpoint to poll for blog generation progress. Check every 30-60 seconds until status is "draft".

GET /websites/123/blog-status-by-date?start_date=2024-01-01&end_date=2024-01-31
Authorization: Bearer sk_live_1234567_xxxxxxxxxxxxxxxxxxxxx

Path Parameters:

  • website_id (required): Your website ID (e.g., 123)

Query Parameters:

  • start_date (required): Start date in YYYY-MM-DD format

  • end_date (required): End date in YYYY-MM-DD format

Response:

{
  "date_statuses": {
    "2024-01-15": {
      "has_blog": true,
      "blog_id": 12345,
      "status": "draft",
      "title": "Generated Blog Title",
      "scheduled_date": "2024-01-15"
    },
    "2024-01-16": {
      "has_blog": false
    }
  }
}

Blog Status Values:

  • "processing" - Blog is still being generated (keep polling)

  • "draft" - Blog is ready! Fetch content with /blog-drafts/{blog_id}

  • "published" - Blog has been published to your CMS

4. Fetch Draft Blog Content (For Third-Party Publishing)

Once status is "draft", fetch the complete HTML content to publish on your third-party website.

GET /blog-drafts/{blog_id}
Authorization: Bearer sk_live_1234567_xxxxxxxxxxxxxxxxxxxxx

Response:

{
  "id": 12345,
  "title": "Your Blog Post Title",
  "slug": "your-blog-post-title",
  "content": "<h1>Your Blog Post Title</h1><p>Full HTML content here...</p>",
  "meta_description": "SEO-optimized meta description",
  "focus_keyword": "target keyword",
  "status": "draft",
  "scheduled_date": "2024-01-15T00:00:00Z",
  "created_at": "2024-01-15T10:30:00Z",
  "featured_image_url": "https://...",
  "word_count": 1500
}

Use the content field to publish the HTML to your website. This is the complete, SEO-optimized article.

5. Mark Blog as Published (Required for API Websites)

When SEOPro publishes directly to a connected CMS (WordPress, HubSpot, Webflow, etc.), we automatically capture the live URL, update the blogs table, create the vector embedding, and refresh backlinks.

If you're using the Developer API to publish content to your own CMS, we don't know the live URL yetβ€”you have to send it to us so we can store it and use it for creating Backlinks for your new blog post.

Follow these two calls after you publish the HTML on your side:

  1. Send us the published URL

    PATCH /blog-drafts/{blog_id}
    Authorization: Bearer sk_live_1234567_xxxxxxxxxxxxxxxxxxxxx
    Content-Type: application/json
    
    {
      "published_url": "https://yoursite.com/blog/post-slug"
    }
    
    • Stores the final URL in the blogs table.

    • You can clear the URL later by sending an empty string.

  2. Mark the blog as published

    POST /blog-drafts/{blog_id}/publish
    Authorization: Bearer sk_live_1234567_xxxxxxxxxxxxxxxxxxxxx
    

    Response:

    {
      "status": "success",
      "blog_id": 12345,
      "message": "Blog marked as published"
    }
    
    • If you prefer to publish-by-date instead of by blog_id, you can include the URL in the same way and then call POST /websites/{website_id}/blogs/publish-for-date.

Tip: Always send the URL before calling the publish endpoint. Without it, SEOPro cannot track the post, generate embeddings, or include it in the backlink network.

Note: Generating a Developer API key (or webhook token) automatically switches the selected website’s integration to API, so SEOPro knows to skip CMS publishing. Any new websites you add while an API key exists will default to the same API integration, though you can override this anytime under Settings β†’ Integrations.

6. Generate Blog (General)

Generate a blog post with custom parameters:

POST /blogs/generate
Content-Type: application/json
Authorization: Bearer sk_live_1234567_xxxxxxxxxxxxxxxxxxxxx

{
  "keywords": ["target keyword", "secondary keyword"],
  "website_id": 123,
  "language_code": "en",
  "keyword_id": 456
}

JavaScript/Node.js Integration Examples

Basic Setup with Async Polling

class SEOProAPI {
  constructor(apiKey, baseUrl = 'https://api.seoproai.co/api') {
    this.apiKey = apiKey;
    this.baseUrl = baseUrl;
  }

  async request(endpoint, options = {}) {
    const url = `${this.baseUrl}${endpoint}`;
    const config = {
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json',
        ...options.headers
      },
      ...options
    };

    const response = await fetch(url, config);

    if (!response.ok) {
      throw new Error(`API Error: ${response.status} ${response.statusText}`);
    }

    return await response.json();
  }

  // Generate blog for specific date (async - returns immediately)
  async generateBlogForDate(websiteId, date, keywords, languageCode = 'en') {
    return await this.request(`/websites/${websiteId}/generate-blog-for-date`, {
      method: 'POST',
      body: JSON.stringify({
        scheduled_date: date,
        keywords: keywords,
        language_code: languageCode
      })
    });
  }

  // Check blog status for date range (for polling)
  async getBlogStatusByDate(websiteId, startDate, endDate) {
    const params = new URLSearchParams({
      start_date: startDate,
      end_date: endDate
    });

    return await this.request(`/websites/${websiteId}/blog-status-by-date?${params}`);
  }

  // Fetch draft blog content (complete HTML)
  async getBlogDraft(blogId) {
    return await this.request(`/blog-drafts/${blogId}`);
  }

  // Mark blog as published (optional)
  async markBlogAsPublished(blogId) {
    return await this.request(`/blog-drafts/${blogId}/publish`, {
      method: 'POST'
    });
  }

  // Poll for blog completion with timeout
  async waitForBlogGeneration(websiteId, date, maxWaitMinutes = 15, pollIntervalSeconds = 30) {
    const maxAttempts = (maxWaitMinutes * 60) / pollIntervalSeconds;
    let attempts = 0;

    while (attempts < maxAttempts) {
      attempts++;

      // Check blog status
      const status = await this.getBlogStatusByDate(websiteId, date, date);
      const dateStatus = status.date_statuses?.[date];

      if (dateStatus?.has_blog && dateStatus?.status === 'draft') {
        console.log(`Blog ready after ${attempts * pollIntervalSeconds} seconds`);
        return {
          ready: true,
          blog_id: dateStatus.blog_id,
          title: dateStatus.title
        };
      }

      console.log(`Blog still processing... (attempt ${attempts}/${maxAttempts})`);

      // Wait before next poll
      await new Promise(resolve => setTimeout(resolve, pollIntervalSeconds * 1000));
    }

    throw new Error(`Blog generation timeout after ${maxWaitMinutes} minutes`);
  }
}

Usage Examples

1. Complete Async Workflow - Generate and Fetch Blog

This example shows the recommended async workflow with polling:

const seoProAPI = new SEOProAPI('sk_live_1234567_xxxxxxxxxxxxxxxxxxxxx');

async function generateAndFetchBlog() {
  const websiteId = 123;
  const targetDate = '2024-01-15'; // YYYY-MM-DD format

  try {
    // Step 1: Initiate blog generation (returns immediately)
    console.log(`Initiating blog generation for ${targetDate}...`);
    const initResult = await seoProAPI.generateBlogForDate(
      websiteId,
      targetDate,
      ['your target keyword', 'secondary keyword'],
      'en'
    );

    console.log('Generation started:', initResult);
    // Response: { status: "processing", keyword_id: 789, scheduled_date: "2024-01-15" }

    // Step 2: Poll for completion (waits up to 15 minutes)
    console.log('Waiting for blog generation to complete...');
    const result = await seoProAPI.waitForBlogGeneration(
      websiteId,
      targetDate,
      15,  // max wait 15 minutes
      30   // poll every 30 seconds
    );

    console.log('Blog is ready!', result);
    // Result: { ready: true, blog_id: 12345, title: "Your Generated Title" }

    // Step 3: Fetch the complete HTML content
    console.log(`Fetching blog content (ID: ${result.blog_id})...`);
    const blogDraft = await seoProAPI.getBlogDraft(result.blog_id);

    console.log('Blog fetched successfully:');
    console.log('- Title:', blogDraft.title);
    console.log('- Word Count:', blogDraft.word_count);
    console.log('- Meta Description:', blogDraft.meta_description);
    console.log('- HTML Content Length:', blogDraft.content.length, 'chars');

    // Step 4: Publish the HTML to your third-party website
    // (Your custom publishing logic here)
    await publishToYourWebsite(blogDraft);

    // Step 5: Optionally mark as published in SEOPro
    await seoProAPI.markBlogAsPublished(result.blog_id);

    return blogDraft;

  } catch (error) {
    console.error('Error:', error.message);
    throw error;
  }
}

// Helper function to publish to your website
async function publishToYourWebsite(blogDraft) {
  // Example: Publish to your custom CMS API
  const response = await fetch('https://your-site.com/api/posts', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer your-cms-token'
    },
    body: JSON.stringify({
      title: blogDraft.title,
      content: blogDraft.content,
      slug: blogDraft.slug,
      meta_description: blogDraft.meta_description,
      featured_image: blogDraft.featured_image_url
    })
  });

  if (!response.ok) {
    throw new Error('Failed to publish to website');
  }

  console.log('Published to your website successfully!');
}

// Run it
generateAndFetchBlog();

2. Bulk Generate Blogs (Async, No Waiting)

Generate multiple blogs and track them separately (they generate in parallel):

async function bulkGenerateBlogs() {
  const websiteId = 123;
  const dates = [
    { date: '2024-01-15', keywords: ['monday keyword', 'productivity'] },
    { date: '2024-01-16', keywords: ['tuesday keyword', 'efficiency'] },
    { date: '2024-01-17', keywords: ['wednesday keyword', 'workflow'] },
    { date: '2024-01-18', keywords: ['thursday keyword', 'automation'] },
    { date: '2024-01-19', keywords: ['friday keyword', 'results'] }
  ];

  const results = [];

  // Initiate all generations (they run in parallel in the background)
  for (const { date, keywords } of dates) {
    try {
      console.log(`Initiating blog generation for ${date}...`);

      const result = await seoProAPI.generateBlogForDate(
        websiteId,
        date,
        keywords
      );

      results.push({
        date: date,
        status: 'initiated',
        keyword_id: result.keyword_id
      });

      // Small delay to avoid rate limiting
      await new Promise(resolve => setTimeout(resolve, 1000));

    } catch (error) {
      console.error(`Failed to initiate blog for ${date}:`, error.message);
      results.push({
        date: date,
        status: 'error',
        error: error.message
      });
    }
  }

  console.log(`Initiated ${results.length} blog generations. They will complete in 5-10 minutes.`);
  return results;
}

// Later, check which ones are ready
async function checkBulkBlogStatus(websiteId, startDate, endDate) {
  const status = await seoProAPI.getBlogStatusByDate(websiteId, startDate, endDate);

  const summary = {
    ready: [],
    processing: [],
    failed: []
  };

  for (const [date, info] of Object.entries(status.date_statuses)) {
    if (info.has_blog) {
      if (info.status === 'draft') {
        summary.ready.push({ date, blog_id: info.blog_id, title: info.title });
      } else if (info.status === 'processing') {
        summary.processing.push({ date });
      }
    } else {
      summary.failed.push({ date });
    }
  }

  console.log(`Ready: ${summary.ready.length}, Processing: ${summary.processing.length}, Failed: ${summary.failed.length}`);
  return summary;
}

3. Check and Fill Missing Blogs

async function fillMissingBlogs(websiteId, startDate, endDate) {
  try {
    // Check current blog status
    const status = await seoProAPI.getBlogStatusByDate(websiteId, startDate, endDate);
    
    const missingDates = [];
    
    // Find dates without blogs
    for (const [date, info] of Object.entries(status.date_statuses)) {
      if (!info.has_blog) {
        missingDates.push(date);
      }
    }
    
    console.log(`Found ${missingDates.length} dates without blogs:`, missingDates);
    
    // Generate blogs for missing dates
    const results = [];
    for (const date of missingDates) {
      try {
        const result = await seoProAPI.generateBlogForDate(
          websiteId,
          date,
          ['auto-generated content', 'blog post']
        );
        
        results.push({ date, status: 'generated', blog_id: result.blog_id });
        
        // Wait between requests
        await new Promise(resolve => setTimeout(resolve, 3000));
        
      } catch (error) {
        results.push({ date, status: 'error', error: error.message });
      }
    }
    
    return results;
    
  } catch (error) {
    console.error('Error filling missing blogs:', error);
    throw error;
  }
}

// Usage
fillMissingBlogs(123, '2024-01-01', '2024-01-31');

4. Scheduled Publishing Workflow

async function scheduledPublishingWorkflow() {
  const websiteId = 123;
  const today = new Date().toISOString().split('T')[0];
  
  try {
    // Check if today has a blog ready to publish
    const status = await seoProAPI.getBlogStatusByDate(websiteId, today, today);
    const todayStatus = status.date_statuses[today];
    
    if (todayStatus && todayStatus.has_blog && todayStatus.status === 'draft') {
      console.log('Publishing today\'s blog...');
      
      const publishResult = await seoProAPI.publishBlogForDate(websiteId, today);
      console.log('Published:', publishResult);
      
      return publishResult;
    } else {
      console.log('No draft blog available for today');
      return { status: 'no_blog_to_publish' };
    }
    
  } catch (error) {
    console.error('Publishing workflow error:', error);
    throw error;
  }
}

// Run this daily via cron job or scheduler
scheduledPublishingWorkflow();

Python Integration Example

Complete Async Workflow with Polling

import requests
import time
from datetime import datetime, timedelta
from typing import Dict, Optional

class SEOProAPI:
    def __init__(self, api_key: str, base_url: str = "https://api.seoproai.co/api"):
        self.api_key = api_key
        self.base_url = base_url
        self.headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        }

    def request(self, endpoint: str, method: str = "GET", data: Optional[Dict] = None):
        url = f"{self.base_url}{endpoint}"
        response = requests.request(method, url, headers=self.headers, json=data)
        response.raise_for_status()
        return response.json()

    def generate_blog_for_date(self, website_id: int, date: str, keywords: list, language_code: str = "en"):
        """Initiate blog generation (returns immediately)"""
        return self.request(
            f"/websites/{website_id}/generate-blog-for-date",
            method="POST",
            data={
                "scheduled_date": date,
                "keywords": keywords,
                "language_code": language_code
            }
        )

    def get_blog_status_by_date(self, website_id: int, start_date: str, end_date: str):
        """Check blog status for polling"""
        return self.request(
            f"/websites/{website_id}/blog-status-by-date?start_date={start_date}&end_date={end_date}"
        )

    def get_blog_draft(self, blog_id: int):
        """Fetch complete HTML content of draft blog"""
        return self.request(f"/blog-drafts/{blog_id}")

    def mark_blog_as_published(self, blog_id: int):
        """Mark blog as published (optional)"""
        return self.request(f"/blog-drafts/{blog_id}/publish", method="POST")

    def wait_for_blog_generation(
        self,
        website_id: int,
        date: str,
        max_wait_minutes: int = 15,
        poll_interval_seconds: int = 30
    ):
        """Poll for blog completion with timeout"""
        max_attempts = (max_wait_minutes * 60) // poll_interval_seconds
        attempts = 0

        while attempts < max_attempts:
            attempts += 1

            # Check blog status
            status = self.get_blog_status_by_date(website_id, date, date)
            date_status = status.get("date_statuses", {}).get(date)

            if date_status and date_status.get("has_blog") and date_status.get("status") == "draft":
                print(f"Blog ready after {attempts * poll_interval_seconds} seconds")
                return {
                    "ready": True,
                    "blog_id": date_status["blog_id"],
                    "title": date_status["title"]
                }

            print(f"Blog still processing... (attempt {attempts}/{max_attempts})")

            # Wait before next poll
            time.sleep(poll_interval_seconds)

        raise TimeoutError(f"Blog generation timeout after {max_wait_minutes} minutes")

# Complete usage example
def generate_and_fetch_blog():
    api = SEOProAPI("sk_live_1234567_xxxxxxxxxxxxxxxxxxxxx")
    website_id = 123
    target_date = "2024-01-15"

    try:
        # Step 1: Initiate blog generation (returns immediately)
        print(f"Initiating blog generation for {target_date}...")
        init_result = api.generate_blog_for_date(
            website_id,
            target_date,
            ["python", "automation", "async workflow"],
            "en"
        )
        print(f"Generation started: {init_result}")

        # Step 2: Poll for completion
        print("Waiting for blog generation to complete...")
        result = api.wait_for_blog_generation(
            website_id,
            target_date,
            max_wait_minutes=15,
            poll_interval_seconds=30
        )
        print(f"Blog is ready! {result}")

        # Step 3: Fetch the complete HTML content
        print(f"Fetching blog content (ID: {result['blog_id']})...")
        blog_draft = api.get_blog_draft(result["blog_id"])

        print("Blog fetched successfully:")
        print(f"- Title: {blog_draft['title']}")
        print(f"- Word Count: {blog_draft['word_count']}")
        print(f"- Meta Description: {blog_draft['meta_description']}")
        print(f"- HTML Content Length: {len(blog_draft['content'])} chars")

        # Step 4: Publish to your third-party website
        # (Your custom publishing logic here)
        publish_to_your_website(blog_draft)

        # Step 5: Optionally mark as published in SEOPro
        api.mark_blog_as_published(result["blog_id"])

        return blog_draft

    except Exception as e:
        print(f"Error: {e}")
        raise

def publish_to_your_website(blog_draft: Dict):
    """Example: Publish to your custom CMS"""
    response = requests.post(
        "https://your-site.com/api/posts",
        headers={
            "Content-Type": "application/json",
            "Authorization": "Bearer your-cms-token"
        },
        json={
            "title": blog_draft["title"],
            "content": blog_draft["content"],
            "slug": blog_draft["slug"],
            "meta_description": blog_draft["meta_description"],
            "featured_image": blog_draft.get("featured_image_url")
        }
    )
    response.raise_for_status()
    print("Published to your website successfully!")

# Run it
if __name__ == "__main__":
    generate_and_fetch_blog()

Rate Limits and Best Practices

Rate Limits

  • Blog Generation: Limited by your plan (5-60 blogs per month)

  • API Requests: No strict rate limits (API keys have their own limits - see Authentication section)

  • Polling: Poll every 30-60 seconds (not more frequently)

  • Publishing: No limits on publishing existing drafts

Best Practices for Async Workflow

  1. Use Async Polling Pattern: Never wait synchronously for blog generation - always use the polling pattern

  2. Poll Interval: Poll every 30-60 seconds (not more frequently to avoid unnecessary API calls)

  3. Timeout: Set a reasonable timeout (15 minutes recommended)

  4. Check Before Generating: Use /blog-status-by-date to check if blog already exists

  5. Handle Errors Gracefully: Implement proper error handling and retries

  6. Bulk Operations: When generating multiple blogs, initiate them all first (they process in parallel), then poll for all

  7. Validate Dates: Ensure dates are in YYYY-MM-DD format

  8. Store Blog IDs: Track blog IDs returned by status checks for later retrieval

  9. Test First: Use a test website/API key for development

Recommended Workflow Summary

For Single Blog:

1. POST /generate-blog-for-date β†’ Get keyword_id
2. Loop: GET /blog-status-by-date every 30s β†’ Check if status = "draft"
3. GET /blog-drafts/{blog_id} β†’ Fetch HTML content
4. Publish to your website
5. (Optional) POST /blog-drafts/{blog_id}/publish β†’ Mark as published

For Multiple Blogs:

1. POST /generate-blog-for-date for each date (1 second apart)
2. Wait 5-10 minutes
3. GET /blog-status-by-date with date range β†’ Get all statuses at once
4. For each ready blog: GET /blog-drafts/{blog_id} β†’ Fetch HTML
5. Publish all to your website

Error Handling

Common error responses:

{
  "detail": "Monthly blog generation limit reached",
  "status_code": 429
}
{
  "detail": "Website not found",
  "status_code": 404
}
{
  "detail": "Blog already exists for this date",
  "status_code": 400
}

Integration Patterns

1. Content Calendar Automation

  • Generate blogs for planned content calendar dates

  • Automatically publish on scheduled dates

  • Fill gaps in content calendar

2. Keyword-Driven Content

  • Generate blogs based on keyword research

  • Target specific keywords for specific dates

  • Optimize for seasonal content

3. Bulk Content Creation

  • Generate multiple blogs for content sprints

  • Prepare content in advance for busy periods

  • Create evergreen content libraries

Webhook + API Combined Workflow

You can combine both webhook and API integrations:

  1. API: Generate and schedule blogs programmatically

  2. Webhook: Receive notifications when blogs are published

  3. Your System: Process published content and update your CMS

This gives you full control over both content creation and content delivery.

Quick Start Guide

For Third-Party Website Publishing (Recommended Use Case)

If you want to use SEOPro AI to generate blogs and publish them to your own website (not using SEOPro's built-in CMS integrations):

Setup:

  1. Generate your API key in Settings > Integrations > Developer API

  2. Copy your Website ID - It's displayed at the top of the API Keys tab (see Finding Your Website ID)

  3. Install the code examples above (JavaScript or Python)

Daily Workflow:

# Morning: Generate blog for today
POST /generate-blog-for-date (returns immediately)

# 10 minutes later: Check if ready
GET /blog-status-by-date (poll until status = "draft")

# Fetch HTML content
GET /blog-drafts/{blog_id}

# Publish to your WordPress/custom CMS
[Your publishing logic using the HTML content]

# Mark as published (optional)
POST /blog-drafts/{blog_id}/publish

Complete Setup Steps

  1. Generate Your API Key:

    • Go to Settings > Integrations > Developer API in your SEOPro AI dashboard

    • Click "Generate New Key" in the API Keys tab

    • Give it a descriptive name and copy the key immediately (you won't see it again!)

    • Store it securely as an environment variable

  2. Copy Your Website ID:

    • Easiest: Displayed at the top of Settings > Integrations > Developer API (API Keys tab)

    • Or from dashboard URL: https://app.seoproai.co/dashboard/websites/{website_id}

    • Or via API: GET /api/websites endpoint

    • See full details: Finding Your Website ID

  3. Test with Simple Requests:

    • Copy one of the complete code examples above (JavaScript or Python)

    • Replace API key and website_id with your values

    • Run the generateAndFetchBlog() example

    • Verify you receive the HTML content

  4. Build Your Integration:

    • Implement the async polling pattern (30-60 second intervals)

    • Add error handling and timeout logic (15 minutes recommended)

    • Create your custom publishing function to send HTML to your website

    • Store API keys securely in environment variables

  5. Monitor and Maintain:

    • Track your API usage in the dashboard (Settings > Integrations > Developer API > Usage)

    • Monitor monthly blog generation limits for your plan

    • Rotate API keys regularly for security

    • Set up alerts for failed generations

API Key Security Best Practices

  1. Store Securely: Use environment variables or secure secret management

  2. Rotate Regularly: Generate new keys periodically and revoke old ones

  3. Use Test Keys: Create separate keys for development and production

  4. Monitor Usage: Check usage statistics in the dashboard

  5. Revoke Compromised Keys: Delete any keys that may have been exposed

Rate Limits

API keys are subject to the following rate limits:

If you exceed rate limits, you'll receive a 429 Too Many Requests response with a Retry-After header.

Why Use the API?

Perfect for:

  • Third-party Publishing: Generate SEO blogs and publish to your custom website/CMS

  • Bulk Generation: Generate multiple blogs efficiently in parallel

  • Custom Workflows: Integrate blog generation into your existing automation

  • Agency Use: Generate content for multiple client websites programmatically

  • Scheduled Publishing: Build your own scheduling system with full control

The API integration gives you powerful programmatic control over your content generation and publishing workflow, perfect for agencies, developers, and businesses with complex content needs.