The Harbor API & MCP
Generate SEO articles, landing pages and rewrites — and research topics and manage sites — from your own apps, n8n, or Claude Code. A curl-first REST API, an MCP server, and hand-written Python & TypeScript SDKs. Every call uses your existing Harbor article credits.
Overview
Harbor's API lets you run everything Harbor does programmatically. It's in beta — endpoints may change, and we'll give notice before any breaking change.
- Base URL —
https://outgoing-oyster-428.convex.site/v1 - MCP endpoint —
https://outgoing-oyster-428.convex.site/mcp - Auth — every request needs the header
Authorization: Bearer hrb_live_… - Billing — 1 article credit per generation (research is free). Failed jobs refund automatically.
Mint a key from the Devs page in your dashboard — it's shown once, so store it safely.
Quick start
List your sites, start an article, then poll it to done:
# 1. Find a site_id
curl "https://outgoing-oyster-428.convex.site/v1/sites" \
-H "Authorization: Bearer hrb_live_..."
# 2. Start an article
curl -X POST "https://outgoing-oyster-428.convex.site/v1/articles" \
-H "Authorization: Bearer hrb_live_..." \
-H "Content-Type: application/json" \
-d '{"site_id":"SITE_ID","keywords":"best running shoes"}'
# → { "id": "JOB_ID", "type": "article", "status": "queued" }
# 3. Poll until done (?wait=true blocks up to ~45s)
curl "https://outgoing-oyster-428.convex.site/v1/articles/JOB_ID?wait=true" \
-H "Authorization: Bearer hrb_live_..."
# → { "status": "completed", "content": "<html>...", ... }Endpoints
All endpoints are under /v1 and require the Bearer header.
Generate — POST, 1 credit each
POST /v1/articles—site_id, keywords, content_type?, tone?, instructions?, words_to_avoid?, webhook_url?POST /v1/articles/bulk—site_id, keywords[] (max 25), …same optionsPOST /v1/landing-pages—site_id, title, topic, page_type?, notes?, webhook_url?POST /v1/reworks—url, mode? (rewrite|reforge), instructions?, webhook_url?
Articles inherit the site's saved settings — tone, brand voice, instructions, words-to-avoid — exactly like the dashboard. Body fields override them per request.
Research — POST, free
POST /v1/research—site_id→ poll for atopicsarray of ideas
Read — GET
GET /v1/articles/{id}— one job (also landing-pages, reworks, research)GET /v1/articles?limit=&cursor=— paginated listGET /v1/sites·GET /v1/sites/{id}— your sites with full settingsGET /v1/account— credits remaining + plan
Manage sites
POST /v1/sites— create a site (name, sitemap_urlrequired)PATCH /v1/sites/{id}— update any site setting
Jobs & polling
Generations are async. A POST returns a job id instantly; poll it until status is completed or failed. A full article takes ~3-6 minutes.
Add ?wait=true to a GET and it long-polls server-side for up to ~45s — returning the moment the job finishes — so you make a handful of requests instead of dozens.
{
"id": "...",
"type": "article | landing_page | rework | research",
"status": "queued | generating | completed | failed",
"progress": { "step": 2, "total": 3, "message": "Writing..." },
"content": "<html>...</html>", // when completed
"title": "...", "word_count": 1840,
"topics": [ ... ], // research jobs
"error": "...", // when failed
"credit_refunded": true
}Webhooks
Pass a webhook_url on any POST. When the job reaches a terminal state, Harbor sends POST <webhook_url> with the full job JSON and a header X-Harbor-Signature: sha256=<hmac> — HMAC-SHA256 of the raw body, secret = sha256_hex(your_api_key). Delivery retries ~3× on non-2xx.
MCP — Harbor inside Claude Code
Harbor ships a remote MCP server, so you can drop it into Claude Code, Claude Desktop, Cursor, or any MCP client. Install it with one command:
claude mcp add --transport http harbor \ https://outgoing-oyster-428.convex.site/mcp \ --header "Authorization: Bearer hrb_live_..."
Then just ask Claude — "research topics for my site and write the best 5" — and it runs the whole workflow. 11 tools: research, generate (single + bulk), landing pages, reworks, job status, full site management, and account.
Python SDK
Dependency-free (standard library only).
pip install harbor
from harbor import Harbor h = Harbor(api_key="hrb_live_...") # Generate an article and wait for it job = h.articles.create(site_id="SITE_ID", keywords="best running shoes") article = h.articles.wait(job.id) print(article.content) # Research, then bulk-generate the best ideas research = h.research.wait(h.research.create(site_id="SITE_ID").id) topics = [t["title"] for t in research.topics[:5]] batch = h.articles.create_bulk(site_id="SITE_ID", keywords=topics)
Namespaces: articles, landing_pages, reworks, research, sites, account. .wait() polls until the job is done.
TypeScript SDK
Zero dependencies, fetch-based, fully typed.
npm install @harbor/sdk
import { Harbor } from "@harbor/sdk";
const h = new Harbor({ apiKey: "hrb_live_..." });
const job = await h.articles.create({
siteId: "SITE_ID",
keywords: "best running shoes",
});
const article = await h.articles.wait(job.id);
console.log(article.content);n8n
There's no dedicated Harbor node — use n8n's built-in HTTP Request node. Method POST, URL https://outgoing-oyster-428.convex.site/v1/articles, a header Authorization: Bearer hrb_live_…, and a JSON body:
{
"site_id": "SITE_ID",
"keywords": "best running shoes"
}The Devs page has copy-paste n8n recipes for every task, with your site pre-filled.
Errors
Errors use a consistent envelope: { "error": { "code": "...", "message": "..." } }
400— invalid request401— missing / invalid / revoked key402— out of article credits404— not found500— internal error
Ready to build? Mint a key and try every endpoint in the live playground.
Open the Devs page →