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:
- Go to Settings
- Scroll to the External API section
- Click Create API Key and give it a name
- Copy the key immediately — it won't be shown again
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 Status | Code | Description |
|---|---|---|
401 | UNAUTHORIZED | Missing or invalid API key |
403 | TIER_REQUIRED | The requested feature or action requires a higher plan tier |
429 | RATE_LIMITED | Too many requests (see Rate Limits) |
400 | INVALID_REQUEST | Missing or invalid parameters |
404 | NOT_FOUND | Workflow or resource not found |
502 | CMS_ERROR | CMS publishing failed (returned by POST /jobs/publish) |
500 | INTERNAL_ERROR | Unexpected 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"
} | Field | Type | Description |
|---|---|---|
name | string | Required. Name of the style guide. |
content | string | Required. The style guide content (markdown, plain text, etc.). |
description | string | Optional. 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.
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
| Field | Type | Description |
|---|---|---|
agentId | string | List all jobs for this workflow. Required when jobId is not provided. |
jobId | string | Optional. Fetch a single job with full article content (HTML, workflow outputs, etc.). |
status | string | Optional. 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}
}'