External API

Overview

The Publish Owl External API lets you fully manage workflows, style guides, keywords, and article generation programmatically. Create workflows, upload writing guidelines as style guides, add keywords, trigger runs, monitor results, and take actions on completed jobs (publish, refresh, redo, archive) — all via HTTP requests from tools like Zapier, Make, n8n, or your own orchestration platform.

The External API is available on every plan. All plan limits still apply when called via the API: article counts, site and workflow limits, concurrency, and feature gates are enforced the same way they are in the web app, so the API cannot be used to bypass your plan’s restrictions.

Authentication

All API requests require an API key sent as a Bearer token in the Authorization header:

Authorization: Bearer po_your_api_key_here

To generate an API key:

  1. Go to Settings
  2. Scroll to the External API section
  3. Click Create API Key and give it a name
  4. Copy the key immediately — it won't be shown again
Keep your API keys secret. Do not share them in public repositories, client-side code, or browser applications. If a key is compromised, revoke it immediately in Settings and generate a new one.

Base URL

All endpoints are relative to:

https://publishowl.com/api/v1

Response Format

All responses use a consistent JSON envelope:

Success

{
  "data": { ... }
}

Error

{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable description"
  }
}

Error Codes

HTTP StatusCodeDescription
401UNAUTHORIZEDMissing or invalid API key
403TIER_REQUIREDThe requested feature or action requires a higher plan tier
429RATE_LIMITEDToo many requests (see Rate Limits)
400INVALID_REQUESTMissing or invalid parameters
404NOT_FOUNDWorkflow or resource not found
502CMS_ERRORCMS publishing failed (returned by POST /jobs/publish)
500INTERNAL_ERRORUnexpected server error

Rate Limits

API requests are limited to 60 requests per minute per user, with a burst capacity of 20 requests. If you exceed the limit, the API returns a 429 status with a Retry-After header indicating how many seconds to wait.

Endpoints

POST /style-guides

Create a style guide. Style guides contain writing voice, tone, and brand strategy instructions that workflows reference when generating content. If a style guide with the same name already exists, its content will be updated (upsert behavior).

Request Body

{
  "name": "Tech Blog Voice",
  "content": "# Writing Guidelines\n\nTone: Professional but approachable...",
  "description": "Core brand voice for all blog content"
}
FieldTypeDescription
namestringRequired. Name of the style guide.
contentstringRequired. The style guide content (markdown, plain text, etc.).
descriptionstringOptional. A short description for your reference.

Response (201 Created / 200 Updated)

{
  "data": {
    "id": "k970r7h79wm61zbx0qs39s7zcd820tt7",
    "name": "Tech Blog Voice",
    "updated": false
  }
}

GET /style-guides

List all your style guides. Use the returned IDs when creating or updating workflows.

Response

{
  "data": [
    {
      "id": "k970r7h79wm61zbx0qs39s7zcd820tt7",
      "name": "Tech Blog Voice",
      "description": "Core brand voice for blog content",
      "contentLength": 2847,
      "createdAt": 1772303361973,
      "updatedAt": 1772555889879
    }
  ]
}

DELETE /style-guides

Delete a style guide. Automatically removes it from any workflows that reference it.

Request Body

{
  "styleGuideId": "k970r7h79wm61zbx0qs39s7zcd820tt7"
}

Response

{
  "data": {
    "id": "k970r7h79wm61zbx0qs39s7zcd820tt7",
    "deleted": true,
    "agentsAffected": 2
  }
}

GET /agents

List all your workflows with their full configuration. Use ?detail=summary for a compact response with just IDs, names, and statuses. Use ?id=agentId to fetch a single workflow.

Response (full, default)

{
  "data": [
    {
      "id": "j5771w97w4yvkztzgesxsqpn2x7tyxmb",
      "name": "Blog Writer",
      "siteId": "abc123",
      "siteName": "My WordPress Site",
      "keywordCount": 42,
      "keywordType": "topic",
      "isDraft": false,
      "isDisabled": false,
      "isFavorite": false,
      "status": "publish",
      "workflowAgents": [
        {
          "id": "abc123",
          "order": 0,
          "provider": "anthropic",
          "model": "claude-sonnet-4-5",
          "prompt": "Write an article about {{keyword}}...",
          "enabled": true,
          "useWebSearch": true
        }
      ],
      "styleGuideIds": ["k970r7h..."],
      "keywords": [
        { "keyword": "best productivity apps", "notes": "Pillar article" }
      ],
      "createdAt": 1772303361973,
      "updatedAt": 1772555889879
    }
  ]
}

The full response includes all configuration fields. Only a subset is shown above for brevity — the actual response includes every field listed in the POST/PATCH documentation below.

POST /agents

Create a new workflow. Only name and prompt are required — all other fields have sensible defaults. For advanced multi-step workflows, provide the workflowAgents array directly instead of prompt/provider/model.

Minimal Example

{
  "name": "Blog Writer",
  "prompt": "Write a comprehensive article about {{keyword}}..."
}

Full Example

{
  "name": "SEO Content Writer",
  "prompt": "Research and write a detailed article about {{keyword}}...",
  "provider": "anthropic",
  "model": "claude-sonnet-4-5",
  "useWebSearch": true,
  "siteId": "jd7atyym89ngzvnrh7azay56gn821980",
  "status": "publish",
  "styleGuideIds": ["k970r7h79wm61zbx0qs39s7zcd820tt7"],
  "keywords": [
    { "keyword": "best project management tools", "notes": "Pillar article" },
    { "keyword": "remote work tips", "notes": "Supporting article" }
  ],
  "titlePrompt": "Generate a compelling, SEO-friendly title...",
  "slugFormat": "from-title",
  "keywordType": "topic",
  "tableOfContentsConfig": {
    "enabled": true,
    "position": "before_content",
    "headingText": "Table of Contents",
    "includeH3": true
  },
  "internalLinkingConfig": {
    "enabled": true,
    "sitemapUrl": "https://example.com/sitemap.xml",
    "maxLinksPerArticle": 5,
    "minRelevanceScore": 0.7,
    "linkDistribution": "spread",
    "insertionMode": "inline",
    "anchorTextStyle": "contextual"
  },
  "externalLinkingConfig": {
    "enabled": true,
    "searchProvider": "openai",
    "searchModel": "gpt-5-mini",
    "maxLinksPerArticle": 3,
    "openInNewTab": true,
    "relAttribute": "nofollow",
    "linkPlacement": "natural",
    "preferAuthoritativeSources": true
  },
  "gutenbergBlocks": true
}

The POST /agents endpoint accepts a large number of optional configuration fields. See the source API documentation in-app for the complete list including: required fields (name, prompt), shorthand fields (provider, model, useWebSearch), article settings (status, titlePrompt, slugFormat, keywordType, authorName, categoryId), content & keywords (keywords, styleGuideIds, lengthMode, targetWordCount), workflow pipeline (workflowAgents array with support for providers: openai, anthropic, gemini, grok, perplexity, openrouter, scraper, youtube, news, topic_search, b0, humanize, dataforseo, gsc), WordPress custom fields, configuration objects (imageConfig, templateConfig, internalLinkingConfig, externalLinkingConfig, gutenbergBlocks, tableOfContentsConfig, metaDescriptionConfig, metaTitleConfig, schemaMarkupConfig, productsConfig, amazonProductConfig, pseoConfig, refreshConfig, newsConfig, webhookConfig), publishing schedule, auto-run settings, YouTube channel monitoring, and execution flags.

Response (201 Created)

{
  "data": {
    "id": "j570fdgqw41hak14esas01s9td821tpk",
    "name": "Blog Writer",
    "status": "draft",
    "keywordCount": 2,
    "workflowSteps": 1
  }
}

PATCH /agents

Update an existing workflow. Only fields included in the request body are changed — omitted fields are left unchanged. Accepts all the same fields as POST /agents plus the required agentId.

Request Body

{
  "agentId": "j570fdgqw41hak14esas01s9td821tpk",
  "prompt": "Updated writing instructions...",
  "styleGuideIds": ["k970r7h...", "k97150y..."],
  "status": "publish"
}

Response

{
  "data": {
    "id": "j570fdgqw41hak14esas01s9td821tpk",
    "name": "SEO Content Writer",
    "status": "publish",
    "keywordCount": 2,
    "workflowSteps": 1
  }
}

DELETE /agents

Permanently delete a workflow. This cannot be undone. Jobs created by the workflow are not deleted.

Request Body

{
  "agentId": "j570fdgqw41hak14esas01s9td821tpk"
}

Response

{
  "data": { "id": "j570fdgqw41hak14esas01s9td821tpk", "deleted": true }
}

POST /agents/duplicate

Duplicate an existing workflow. Copies all configuration (workflow steps, style guides, settings) but does not copy keywords — the new workflow starts with an empty keyword list.

Request Body

{
  "agentId": "j570fdgqw41hak14esas01s9td821tpk",
  "name": "Blog Writer v2",
  "siteId": "NEW_SITE_ID"
}

Response (201 Created)

{
  "data": { "id": "j571abc123def456ghi789jkl012mnop", "name": "Blog Writer v2", "status": "draft", "keywordCount": 0, "workflowSteps": 2 }
}

POST /keywords

Add keywords to a workflow. Supports plain strings or objects with notes and custom columns. Maximum 1,000 keywords per request.

Request Body

{
  "agentId": "j5771w97w4yvkztzgesxsqpn2x7tyxmb",
  "keywords": [
    "best project management software",
    {
      "keyword": "remote team collaboration tools",
      "notes": "focus on async-friendly options",
      "csvColumns": { "price_range": "free-$50/mo", "category": "productivity" }
    }
  ],
  "options": {
    "deduplicate": true,
    "triggerJobs": false,
    "mergeColumnNames": true
  }
}

Response

{
  "data": {
    "added": 2, "skipped": 0, "duplicates": [],
    "keywords": [
      { "keyword": "best project management software", "status": "added" },
      { "keyword": "remote team collaboration tools", "status": "added" }
    ],
    "jobsCreated": 0
  }
}

GET /keywords?agentId=...

List all keywords for a workflow, including job statuses and output URLs.

Response

{
  "data": {
    "agentId": "j5771w97w4yvkztzgesxsqpn2x7tyxmb",
    "keywordType": "keyword",
    "keywords": [
      { "keyword": "best project management software", "jobStatus": "succeeded", "outputUrl": "https://example.com/best-project-management-software", "jobCount": 1 },
      { "keyword": "remote team collaboration tools", "jobStatus": null, "outputUrl": null, "jobCount": 0 }
    ]
  }
}

DELETE /keywords

Remove specific keywords from a workflow by value.

Request Body

{ "agentId": "j5771w97w4yvkztzgesxsqpn2x7tyxmb", "keywords": ["best project management software"] }

Response

{ "data": { "removed": 1, "removedKeywords": ["best project management software"], "remaining": 1 } }

POST /keywords/clear

Remove all keywords from a workflow.

Request Body

{ "agentId": "j5771w97w4yvkztzgesxsqpn2x7tyxmb" }

Response

{ "data": { "cleared": 42 } }

POST /run

Trigger article generation for a workflow's pending keywords. Only keywords that don't already have a succeeded job will be processed.

This endpoint requires that your LLM API keys (OpenAI, Anthropic, etc.) are saved in Settings. The API uses your stored keys to generate content.

Request Body

{ "agentId": "j5771w97w4yvkztzgesxsqpn2x7tyxmb", "limit": 5 }

Response (202 Accepted)

{ "data": { "jobsCreated": 5, "message": "5 job(s) queued for processing." } }

GET /sites

List your connected CMS sites. Use the returned site IDs when publishing jobs via POST /jobs/publish.

Response

{
  "data": [
    { "id": "jd71jevf85ge149c...", "name": "My WordPress Blog", "cmsType": "wordpress", "cmsDisplayUrl": "https://myblog.com", "deployHookUrl": false, "createdAt": 1772303361973 }
  ]
}

GET /jobs

List jobs for a workflow, or get a single job with full article content. Use ?jobId= to retrieve a specific job including its full HTML output and workflow step outputs.

Query Parameters

FieldTypeDescription
agentIdstringList all jobs for this workflow. Required when jobId is not provided.
jobIdstringOptional. Fetch a single job with full article content (HTML, workflow outputs, etc.).
statusstringOptional. Filter by status: "queued", "running", "succeeded", "failed", "cancelled".

Response (list)

{
  "data": [
    { "id": "jd7abc123...", "keyword": "best productivity apps", "status": "succeeded", "title": "10 Best Productivity Apps for 2026", "slug": "best-productivity-apps", "outputUrl": "https://example.com/best-productivity-apps", "wordCount": 1850, "tokens": 3200, "costCents": 1.2, "createdAt": 1772654951313 }
  ]
}

Response (single job with ?jobId=)

{
  "data": {
    "id": "jd7abc123...",
    "agentId": "j5771w97...",
    "keyword": "best productivity apps",
    "status": "succeeded",
    "title": "10 Best Productivity Apps for 2026",
    "content": "<h1>10 Best Productivity Apps...</h1>\n<p>Finding the right...</p>",
    "metaDescription": "Discover the top productivity apps...",
    "featuredImage": "https://example.com/images/productivity.jpg",
    "wordCount": 1850,
    "workflowOutputs": [
      { "stepId": "step1", "output": "Research findings...", "provider": "perplexity", "model": "sonar-pro", "tokens": 1200, "costCents": 0.4 },
      { "stepId": "step2", "output": "<h1>10 Best Productivity Apps...</h1>...", "provider": "anthropic", "model": "claude-sonnet-4-5", "tokens": 2000, "costCents": 0.8 }
    ],
    "createdAt": 1772654951313
  }
}

POST /jobs/publish

Publish a completed job to a connected CMS site. The job must have a succeeded status.

Request Body

{ "jobId": "jd7abc123...", "siteId": "k572def456...", "scheduledDate": "2026-04-01T09:00:00Z" }

Response

{ "data": { "success": true, "url": "https://example.com/best-productivity-apps", "postId": "12345" } }

POST /jobs/refresh

Refresh an existing article by regenerating its content. Only works on succeeded jobs.

Request Body

{ "jobId": "jd7abc123...", "provider": "openai", "model": "gpt-4o-mini", "refreshPrompt": "Update the statistics and add a new section about AI tools.", "useWebSearch": true }

Response

{ "data": { "success": true, "jobId": "jd7abc123..." } }

POST /jobs/redo

Re-queue a job for regeneration from scratch. The original job is archived as superseded and a fresh queued job is created.

Request Body

{ "jobId": "jd7abc123...", "autoRun": true }

Response

{ "data": { "newJobId": "jd8xyz789...", "keyword": "best productivity apps", "status": "queued" } }

POST /jobs/archive

Archive one or more jobs. Archived jobs are hidden from the default job list but can be restored later.

Request Body

{ "jobIds": ["jd7abc123...", "jd8xyz789..."] }

Response

{ "data": { "archived": 2, "skipped": 0 } }

POST /jobs/restore

Restore previously archived jobs.

Request Body

{ "jobIds": ["jd7abc123...", "jd8xyz789..."] }

Response

{ "data": { "restored": 2, "skipped": 0 } }

POST /optimize/analyze

Start a keyword analysis for content optimization. Scrapes top-ranking pages, extracts NLP term frequencies, structure recommendations, and competitor data. Creates an optimization session. Works for any language DataForSEO supports — pass locationCode and languageCode together.

Request Body

{
  "keyword": "best project management tools",
  "locationCode": 2840,
  "languageCode": "en",
  "content": "<h1>Best Project Management Tools</h1><p>Here are the top picks...</p>"
}

Example Request

curl -X POST https://publishowl.com/api/v1/optimize/analyze \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "keyword": "best project management tools",
    "content": "<h1>Best Project Management Tools</h1><p>Here are the top picks...</p>"
  }'

Response

{
  "data": {
    "sessionId": "jd7atyym89ngzvnrh7azay56gn821980",
    "keyword": "best project management tools",
    "score": 42,
    "terms": [ { "term": "project management", "importance": "high", "recommendedCount": 8 } ],
    "structureRecommendations": { "wordCountMin": 2200, "wordCountMax": 3500, "recommendedH2Count": 8 },
    "competitors": [ { "url": "https://example.com/pm-tools", "title": "10 Best Project Management Tools in 2026", "wordCount": 3100, "score": 87 } ],
    "costCents": 12, "tokens": 15420
  }
}

POST /optimize/run

Run AI-powered content optimization on existing content. Uses analysis data from a previous /optimize/analyze call. Language is inherited from the session.

Request Body

{
  "sessionId": "jd7atyym89ngzvnrh7azay56gn821980",
  "content": "<h1>Best Project Management Tools</h1><p>Here are the top picks...</p>",
  "keyword": "best project management tools",
  "provider": "anthropic",
  "model": "claude-sonnet-4-5"
}

Example Request

curl -X POST https://publishowl.com/api/v1/optimize/run \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sessionId": "SESSION_ID",
    "content": "<h1>Best Project Management Tools</h1><p>Here are the top picks...</p>",
    "keyword": "best project management tools"
  }'

Response

{
  "data": {
    "sessionId": "jd7atyym89ngzvnrh7azay56gn821980",
    "content": "<h1>Best Project Management Tools for 2026</h1><p>Choosing the right...</p>",
    "score": 78,
    "metaTitle": "Best Project Management Tools (2026) — Expert Picks",
    "metaDescription": "Compare the top project management tools for teams of all sizes...",
    "costCents": 18, "tokens": 22540
  }
}

GET /optimize/sessions

List all your optimization sessions, newest first.

Response

{
  "data": [
    { "id": "jd7atyym89ngzvnrh7azay56gn821980", "keyword": "best project management tools", "score": 78, "createdAt": 1772303361973, "updatedAt": 1772555889879 }
  ]
}

GET /optimize/sessions/:id

Get full details for a single optimization session, including analysis data, current content, score, and optimization status.

Response

{
  "data": {
    "id": "jd7atyym89ngzvnrh7azay56gn821980",
    "keyword": "best project management tools",
    "score": 78,
    "content": "<h1>Best Project Management Tools for 2026</h1>...",
    "analysisData": { "terms": [...], "structureRecommendations": {...}, "competitors": [...] },
    "analyzeStatus": "done", "optimizeStatus": "done",
    "optimizeMetaTitle": "Best Project Management Tools (2026) — Expert Picks",
    "optimizeMetaDescription": "Compare the top project management tools...",
    "createdAt": 1772303361973, "updatedAt": 1772555889879
  }
}

POST /optimize/screenshot

Take a screenshot of a URL.

Request Body

{ "url": "https://example.com/best-project-management-tools" }

Example Request

curl -X POST https://publishowl.com/api/v1/optimize/screenshot \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://example.com/best-project-management-tools" }'

Response

{ "data": { "url": "https://cdn.publishowl.com/screenshots/abc123.png" } }

Examples

Common workflows using curl. Replace YOUR_API_KEY with your actual key.

Upload a style guide

curl -X POST https://publishowl.com/api/v1/style-guides \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Tech Blog Voice",
    "content": "# Brand Voice Guidelines\n\nTone: Professional yet approachable...",
    "description": "Voice and tone guidelines for blog content"
  }'

Create a workflow with a style guide and keywords

curl -X POST https://publishowl.com/api/v1/agents \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Blog Writer",
    "prompt": "Research and write a comprehensive article about {{keyword}}...",
    "provider": "anthropic",
    "model": "claude-sonnet-4-5",
    "useWebSearch": true,
    "siteId": "YOUR_SITE_ID",
    "styleGuideIds": ["STYLE_GUIDE_ID"],
    "keywords": [
      { "keyword": "best productivity apps", "notes": "Pillar article" },
      { "keyword": "time management techniques", "notes": "Supporting article" }
    ],
    "status": "publish"
  }'

Create a multi-step research + writing workflow

curl -X POST https://publishowl.com/api/v1/agents \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Research & Write Workflow",
    "workflowAgents": [
      { "id": "step1", "order": 0, "provider": "perplexity", "model": "sonar-pro", "prompt": "Research {{keyword}} thoroughly. Find latest data and sources.", "enabled": true, "useWebSearch": true },
      { "id": "step2", "order": 1, "provider": "anthropic", "model": "claude-sonnet-4-5", "prompt": "Using the research above, write a comprehensive article. Include external links to sources naturally.", "enabled": true }
    ]
  }'

Add keywords and immediately generate articles

curl -X POST https://publishowl.com/api/v1/keywords \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "YOUR_AGENT_ID",
    "keywords": ["best email marketing tools", "how to build a landing page"],
    "options": { "triggerJobs": true }
  }'

Trigger a run for pending keywords

curl -X POST https://publishowl.com/api/v1/run \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "agentId": "YOUR_AGENT_ID", "limit": 10 }'

Publish a job to a CMS site

curl -X POST https://publishowl.com/api/v1/jobs/publish \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jobId": "YOUR_JOB_ID",
    "siteId": "YOUR_SITE_ID",
    "scheduledDate": "2026-04-01T09:00:00Z"
  }'

Refresh an article with custom instructions

curl -X POST https://publishowl.com/api/v1/jobs/refresh \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jobId": "YOUR_JOB_ID",
    "refreshPrompt": "Update statistics and add a section about AI tools.",
    "useWebSearch": true
  }'

Archive and restore jobs

# Archive jobs
curl -X POST https://publishowl.com/api/v1/jobs/archive \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "jobIds": ["JOB_ID_1", "JOB_ID_2"] }'

# Restore archived jobs
curl -X POST https://publishowl.com/api/v1/jobs/restore \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "jobIds": ["JOB_ID_1", "JOB_ID_2"] }'

Full orchestrator workflow

# 1. Upload a style guide
GUIDE_ID=$(curl -s -X POST https://publishowl.com/api/v1/style-guides \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Blog Voice","content":"..."}' | jq -r '.data.id')

# 2. Create workflow with style guide
AGENT_ID=$(curl -s -X POST https://publishowl.com/api/v1/agents \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name":"Content Writer",
    "prompt":"Write about {{keyword}}...",
    "provider":"anthropic",
    "model":"claude-sonnet-4-5",
    "styleGuideIds":["'$GUIDE_ID'"],
    "status":"publish"
  }' | jq -r '.data.id')

# 3. Add keywords and trigger generation
curl -X POST https://publishowl.com/api/v1/keywords \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agentId":"'$AGENT_ID'",
    "keywords":["topic 1","topic 2","topic 3"],
    "options":{"triggerJobs":true}
  }'
Was this helpful?