Connect your AI agent to Distribb. You write the content — we handle SEO optimization, backlinks, publishing, and analytics.
Fastest setup (Cursor, Claude Code, Codex, and 45+ agents)
npx skills add Bomx/distribb-skill
export DISTRIBB_API_KEY=your_keynpx skills add Bomx/distribb-skillcurl -s -H "Authorization: Bearer $DISTRIBB_API_KEY" https://distribb.io/api/v1/projects | jq .All API requests require a Bearer token in the Authorization header. Your API key is available in your account settings.
Authorization: Bearer your_api_key_here
Returns all active projects linked to your account. Use the project ID in subsequent API calls.
Spinning up a client from scratch is a deliberate 3-call sequence. Creating the project does NOT spend keyword-research credits, so you can create and configure freely, then ask the user before starting research.
POST /projects with the client's website_url (plus any settings) to create and configure the project.POST /projects/:id/onboarding to start keyword research and the first articles. Ask the user first; this one spends credits.POST /projects/:id/wordpress to connect the client's CMS so articles can publish.Create a new project for the authenticated account. Only website_url is required; you can pass any writable field from PUT /projects/:id in the same body to configure the project on create. Creating a project does NOT start keyword research (that spends credits), so after creating, ask the user, then call POST /projects/:id/onboarding.
| Field | Type | Required | Description |
|---|---|---|---|
| website_url | string | Yes | Client site URL (normalized and validated) |
| business_name | string | No | Business name |
| business_description | string | No | What the business does |
| target_audience | string[] | No | Audience segments |
| (any writable field) | any | No | Any key from the PUT writable set (same validation) |
Posting the same website_url again returns the existing active project instead of creating a duplicate.
Project slots are gated by the account's paid quantity. At the limit, this returns a machine-readable body with a purchase_url the human can click to buy another slot; retry the same body once they confirm.
Returns the project plus a settings object that mirrors the writable keys of PUT /projects/:id. GET it, tweak keys, and PUT the same shape back (read-modify-write).
Accepts the same field set as the Settings UI. Send only the fields you want to change; article-quality and image preferences are MERGED, so a partial update never resets the others. GET this project to see the settings object; every key there is writable here.
Watch the exact key names: the tone key is tone (not content_style), the duplicate-content toggle is duplicate_content_protection (not a bare duplicate_content_guard), and articles_per_day is plan-controlled and NOT settable (it is echoed back under ignored, never written).
| Field | Type | Notes |
|---|---|---|
| ai_instructions | string | Custom writer instructions (null to clear) |
| business_name | string | Business name |
| business_description | string | What the business does |
| target_audience | string[] | Audience segments |
| sitemap_url | string | Sitemap URL |
| blog_root_url | string | Blog root URL |
| content_pillars | string[] | Pillar page URLs (list or CSV) |
| internal_links_per_article | int | 1 to 5 (alias: internal_links) |
| tone | enum | Informative, Conversational, Persuasive |
| language | string | Supported label, e.g. "English (US)", "French" |
| keyword_region | string | e.g. "United States", "United Kingdom", "Worldwide" |
| writing_profile | enum | Experienced practitioner, Simple educational, Balanced SEO |
| product_positioning | enum | Neutral operational, Soft mention, Promotional |
| custom_author_name | string | Byline author name |
| social_media_ai_instructions | string | Social repurposing instructions |
| publish_time | string | 24-hour "HH:MM", e.g. "09:00" |
| timezone | string | IANA name, e.g. "America/New_York" |
| publishing_status | enum | Publish Immediately, Save as Drafts, Send as Drafts |
| social_media_publishing_status | enum | Publish Immediately, Save as Drafts |
| image_hosting | enum | Distribb, CMS |
| image_style | string | Free-form style description (null to clear) |
| brand_color | string | Hex, e.g. "#e11d2a" |
| image_prompt_instructions | string | Extra image-prompt guidance |
| title_based_featured_image | bool | Render the title onto the featured image |
| cta_intensity | enum | None, Soft, Direct |
| first_person_writing | bool | Write in first person |
| table_of_contents | bool | Add a table of contents |
| avoid_formulaic_section_endings | bool | Suppress formulaic section closers |
| require_operational_examples | bool | Require concrete operational examples |
| strict_banned_phrase_guard | bool | Enforce banned phrases strictly |
| banned_phrases | string[] | Phrases to avoid (list or newline text) |
| brand_intelligence | bool | Brand intelligence on/off |
| duplicate_content_protection | bool | Duplicate-content guard on/off |
| videos_enabled | bool | Embed YouTube videos on/off |
| backlinks_network | bool | Participate in the backlink network |
| competitors | string[] | Competitor domains (list or CSV) |
Not settable via API: articles_per_day (plan-controlled) and optimization thresholds (applied at scan time). Passing them is accepted but echoed under ignored. Connect WordPress via POST /projects/:id/wordpress.
Runs the same discovery pipeline the dashboard runs when onboarding finishes: keyword research, then the first planned articles. Ask the user before calling this; it spends keyword and LLM credits. Idempotent: if the project already has articles it will not re-run. Free and Agentic plans bring their own keywords, so research is skipped and the response says so.
Connect (or reconnect) a project's WordPress site via the Distribb plugin. Install the Distribb WordPress plugin on the site, copy its Integration Key, and send it here. Credentials are validated before saving (format check plus live probe), exactly like the Settings UI.
| Field | Type | Required | Description |
|---|---|---|---|
| wordpress_url | string | Yes | The WordPress site URL |
| integration_key | string | Yes | The Distribb plugin Integration Key |
| wp_username | string | No | Optional WordPress username |
| Name | Type | Required | Description |
|---|---|---|---|
| project_id | int | No | Filter by project |
| status | string | No | Draft, Planned, Published, Generating |
| limit | int | No | Max results (default 50, max 200) |
| offset | int | No | Pagination offset |
Submit AI-generated content. Distribb handles backlink credit processing, stores the article, and prepares it for publishing.
| Field | Type | Required | Description |
|---|---|---|---|
| project_id | int | Yes | Target project ID |
| keyword | string | Yes | Main keyword / topic |
| title | string | No | Article title (defaults to keyword) |
| content | string | No | Full HTML content |
| meta_description | string | No | SEO meta description |
| scheduled_date | string | No | ISO 8601 date (e.g. 2026-03-25T09:00:00Z) |
| article_style | string | No | Informative, Listicle, How-To, etc. |
| status | string | No | Draft (default) or Planned |
Update an article's content, title, meta description, status, or scheduled date. Useful for revising articles to add backlink targets after receiving a backlinks_warning. Cannot update published articles.
content | string | Updated HTML content |
title | string | Updated title |
meta_description | string | Updated meta description |
status | string | "Draft" or "Planned" |
scheduled_date | string | ISO 8601 date (e.g. 2026-04-01T09:00:00Z) |
Send only the fields you want to update. If content is updated and the project participates in the backlink network, backlinks are re-scanned.
Retrieve full article details including content. The article must belong to your account.
Delete an article that belongs to your account. Draft and Planned articles are removed. Published articles are blocked (400); unschedule them, or unpublish or hide them from the dashboard or your CMS first.
Search for keyword ideas with search volume and difficulty data. Paid Agentic Mode uses Distribb's keyword data; the Free Agentic plan uses your own DataForSEO or Ahrefs keys (see BYO Keys below). Also available at the alias POST /keywords/research (identical behavior).
| Field | Type | Required | Description |
|---|---|---|---|
| keyword | string | Yes | Seed keyword to research |
| project_id | int | No | Project for context and ownership |
If the calling user is on the Free Agentic plan and has not yet saved a DataForSEO or Ahrefs API key, this endpoint returns HTTP 402 Payment Required with a machine-readable body so your agent knows what to do. Paid plans never see this response.
Agent contract: on receiving 402 with error = "byo_keys_required", halt the keyword-research step and surface instructions_for_agent verbatim to the human user. Do not retry until setup_url has been visited and credentials saved.
Provider precedence when both keys are saved: DataForSEO is used first (full keyword expansion); if only Ahrefs is saved, the response is sourced from Ahrefs Keywords Explorer ("source": "byo_ahrefs"). All other Distribb endpoints (articles, integrations, backlinks) work normally without BYO keys.
| Name | Type | Required | Description |
|---|---|---|---|
| project_id | int | No | Filter to a specific project |
The link-level detail behind the aggregate /backlinks/status: earned links (source domain, DR, business, target URL, status, date), upcoming scheduled links, and a velocity summary. Per-link anchor text is not captured yet, so an anchor-mix breakdown is intentionally omitted rather than guessed.
| Name | Type | Required | Description |
|---|---|---|---|
| project_id | int | Yes | Your project ID |
| earned_limit | int | No | Max earned links (default 100, max 500) |
| scheduled_limit | int | No | Max scheduled links (default 50, max 200) |
Lists active CMS and social integrations. Each row returns Platform (the platform/CMS type, e.g. WordPress, Shopify, Google Search Console, a social type), a friendly IntegrationName label, and Status.
| Name | Type | Required | Description |
|---|---|---|---|
| project_id | int | No | Filter to a specific project |
Returns published articles from your site that are relevant to a keyword. Use these as internal links in your new article for better SEO.
| Name | Type | Required | Description |
|---|---|---|---|
| project_id | int | Yes | Your project ID |
| keyword | string | Yes | Target keyword for the article |
Returns URLs from other Distribb network members. Include 1-2 of these as natural references in your article to earn backlink credits. When you submit the article, Distribb detects these links and credits your project automatically.
| Name | Type | Required | Description |
|---|---|---|---|
| project_id | int | Yes | Your project ID |
| keyword | string | Yes | Target keyword for the article |
Submit your own content (notes, drafts, talking points) and Distribb's AI will expand it into a full SEO-optimized article with YouTube videos, images, quotes, backlinks, and internal links. Requires the Pro plan and costs 1 article credit. Not available on the Agentic plan (use POST /articles instead to submit your own AI-generated content).
| Field | Type | Required | Description |
|---|---|---|---|
| project_id | int | Yes | Target project ID |
| keyword | string | Yes | Target keyword / topic for SEO |
| source_content | string | Yes | Your content to expand (notes, draft, talking points, etc.) |
| instructions | string | No | Additional guidance (e.g. "add YouTube videos", "focus on beginners") |
| title | string | No | Article title (defaults to keyword) |
| article_style | string | No | Informative, Listicle, How-To, etc. (default: Informative) |
Triggers CMS publishing for an article. Distribb handles the integration (WordPress, Webflow, Shopify, Ghost, Wix, Notion, Framer, or Webhook) based on the project's connected platform.
Returns project-specific context needed for high-quality content: business name, description, competitors, custom AI instructions, and language. Use this to ground your AI writer in the user's brand voice.
| Name | Type | Required | Description |
|---|---|---|---|
| project_id | int | Yes | Your project ID |
Google Search Console performance for a project: top queries, top pages, and site totals (clicks, impressions, CTR, average position) over the last N days. Requires the user to have connected GSC (Integrations, Google Search Console). If GSC is not connected, returns HTTP 200 with connected: false plus an instructions_for_agent string telling the user to connect it.
| Name | Type | Required | Description |
|---|---|---|---|
| project_id | int | Yes | Your project ID |
| days | int | No | Lookback window (default 28, max 90) |
| limit | int | No | Rows per dimension (default 25, max 1000) |
| start_row | int | No | Pagination offset; see the pagination block |
| compare | bool | No | Add period-over-period deltas (see below) |
Each query/page row carries is_brand (matches the business name) and striking_distance (impressions ≥ 10 and average position 4 to 20). Paginate with start_row; the response returns a pagination block whose next_start_row is null on the last page. With compare=true, each row adds delta_clicks, delta_impressions, delta_position, and is_new versus the immediately preceding window, and a top-level comparison block adds previous_totals and delta_totals.
Documented aliases of /search-console, for agents that look for /rankings or /analytics. Same handler, same parameters, same response. Note: this is Search Console search performance (clicks, impressions, CTR, position by query and page), NOT web-session analytics.
Read AI-visibility data for a project: how often it is cited across AI answer engines, its share of voice, and tracked prompts. API-key mirror of the dashboard AI-visibility pane.
| Name | Type | Required | Description |
|---|---|---|---|
| project_id | int | Yes | Your project ID |
| view | string | No | summary (default), prompts, competitors, cited_pages |
| page, per_page | int | No | Pagination for view=prompts |
Trigger an on-demand AI-visibility scan (the "Scan now" action). Enforces a per-project daily manual-scan cap that is SHARED with the dashboard button and the Distribb Agent, so heavy API scanning draws from the same budget. Returns 202 when queued, 429 when the cap is hit. project_id in the body or query.
Add (POST) or remove (DELETE) a tracked AI-visibility prompt. Added prompts are scanned on the next scan; removal is a soft-delete (past results are kept).
| Field | Type | Required | Description |
|---|---|---|---|
| project_id | int | Yes | Your project ID |
| prompt | string | Yes | The prompt to track (or remove) |
List a project's content-optimization suggestions (top queries and pages where a rewrite could lift rankings). Returns per-status counts, the project's suggestion settings, and whether GSC is connected. Suggestions are largely GSC-driven, so a disconnected project will usually have none.
| Name | Type | Required | Description |
|---|---|---|---|
| project_id | int | Yes | Your project ID |
| status | string | No | pending, approved, rewriting, ready, published, rejected, failed, superseded |
| type | string | No | Filter by opportunity_type: cannibalisation, declining_page, striking_distance, ctr_underperform, etc. |
| limit | int | No | Max results (default 100, max 500) |
Pull GSC, score articles, and insert new pending suggestions (deduped against existing open ones). Mirrors the weekly Monday cron. Returns the count created. project_id in the body or query.
The full review-and-publish loop for one suggestion. The typical path is approve (starts a background rewrite), poll the single-suggestion GET until status is ready, review the diff, then publish.
| Method + Path | Description |
|---|---|
| GET /suggestions/:id | Get one suggestion, including proposed_diff once a rewrite is ready |
| GET /suggestions/:id/diff | Before/after rewrite plus the GSC trigger_snapshot (null until ready) |
| POST /suggestions/:id/approve | Approve a pending suggestion; kicks off scrape + LLM rewrite. Poll until ready |
| POST /suggestions/:id/reject | Reject so it stops showing as actionable. Optional body { "reason": "..." } |
| POST /suggestions/:id/publish | Publish a ready rewrite to the connected CMS (409 if the article changed since staging) |
| POST /suggestions/:id/regenerate | Re-run the rewrite with optional { "feedback": "..." }. Paid plans only (402 on free) |
List project-scoped Microworkers campaigns registered through Distribb.
| Name | Type | Required | Description |
|---|---|---|---|
| project_id | int | No | Filter to a specific project |
| limit | int | No | Max results (default 50, max 100) |
| offset | int | No | Pagination offset |
Create a Microworkers Basic Campaign and register it to a project, or register/inspect an existing one. Creating a campaign also builds its Microworkers template.
| Method + Path | Description |
|---|---|
| POST /microworkers/campaigns | Create + register. Body: project_id, title, description, template_html (plus optional available_positions, payment_per_task ≥ 0.15, etc.) |
| POST /microworkers/campaigns/register | Register an existing campaign by campaign_id so this key can manage it |
| GET /microworkers/campaigns/:id | Get a registered campaign plus its live Microworkers status |
| GET /microworkers/campaigns/:id/slots | List slots/submissions (query: page, pageSize, status) |
| POST /microworkers/slots/:id/rate | Rate a slot. Body: campaign_id, rating (OK, NOK, REVISE), optional comment |
Returns whether the authenticated account is entitled to Outreach Pro (the flag is set and the account is active). Returns 403 when the Outreach Pro beta is closed for the account.
Idempotent upsert of the local Outreach skill database into MySQL mirror tables, keyed by the authenticated account. Body: { since, persons[], identities[], leads[], actions[], suppressions[] }. The client re-pushes boundary rows; the upsert makes it idempotent. Returns per-table counts. 403 if not entitled.
If you connect a custom Webhook integration (instead of WordPress / Webflow / Shopify / Ghost / Wix / Notion / Framer), Distribb posts each finished article to your URL. The payload is wire-compatible with Outrank’s webhook spec, so any receiver written for Outrank works against Distribb with zero changes. Your endpoint must accept this JSON shape, validate the bearer token, and respond with 200 within 30 seconds.
| Name | Value |
|---|---|
| Content-Type | application/json |
| Authorization | Bearer <your access token> — the token you set in Settings → Integrations → Webhook |
| X-API-Key | <your access token> — same value, sent for receivers that read this header (AWS API Gateway, generic SaaS) |
| x-make-apikey | <your access token> — same value, sent for Make.com Custom Webhook "API Key restriction" |
| User-Agent | Distribb-Publisher/1.0 |
Distribb sends your access token under three header names so the request authenticates against any common webhook receiver without extra configuration. Your endpoint only needs to validate one of them.
| Field | Type | Description |
|---|---|---|
| event_type | string | "publish_articles" on the first send for a given slug; "update_articles" when the user clicks Sync to CMS on an article that is already live. Route on this field to decide CREATE vs UPDATE on your side. |
| timestamp | string | When the webhook was sent (ISO 8601, UTC) |
| data.articles[].id | string | Distribb article ID — stable across publish and subsequent sync events. Safe to use as your idempotency key. |
| data.articles[].title | string | Article title |
| data.articles[].slug | string | URL slug, e.g. lawn-care-toronto. Treat as the canonical identifier on your CMS so subsequent update_articles events resolve to the same post. |
| data.articles[].content_html | string | Full article body as HTML (with <h2>, <p>, <ul>, <figure>, etc.). Render this if your CMS displays HTML directly. |
| data.articles[].content_markdown | string | Same body converted to real Markdown. Render this if your CMS expects Markdown. Pick one of the two — never both. |
| data.articles[].meta_description | string | SEO meta description (~155 chars) |
| data.articles[].created_at | string | When the article was created (ISO 8601, UTC) |
| data.articles[].image_url | string | Public URL of the feature image (already hosted on our CDN). Use as-is or re-upload to your storage. |
| data.articles[].alt_text | string | Alt text for the feature image |
| data.articles[].tags | string[] | SEO tags / keywords |
| data.articles[].author | string | Author display name |
| data.articles[].status | string | "Published" or "Draft" — based on the publishing preference set on your project |
| data.articles[].is_update | boolean | true on sync events, false on first publish. Redundant with event_type but handy if your router checks the article object directly. |
If you return a published_url in the response body, Distribb will store it and link to it from your dashboard. Any non-2xx response triggers up to 3 retries with exponential backoff.
When the user clicks Sync to CMS on an article that has already been published, Distribb sends the same payload shape but with event_type: "update_articles" and is_update: true. Implement upsert on your side — resolve the existing post by slug (or by storing Distribb’s id on first publish), then update its title, body, meta, image, and status in place rather than creating a new post.
If your receiver ignores event_type and always inserts, sync events will create duplicate posts. Always branch on event_type or store Distribb’s id on first publish.
content_html through a Markdown parser (or content_markdown through an HTML renderer). Pick the field that matches your renderer.<img> tags. Either allow our S3 domain or re-upload image_url to your own storage before saving.event_type. Branch on "update_articles" to upsert by slug.Instead of writing curl commands, connect the Distribb MCP server to Claude or Cursor and call every endpoint as a native tool — no CLI flags, no shell scripts. The agent calls list_projects, create_article, publish_article directly — same workflow, zero boilerplate.
Clone the distribb-skill repo or download distribb_mcp_server.py directly.
Cursor: ~/.cursor/mcp.json · Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json
Claude or Cursor will launch the server automatically. The tools appear immediately in the agent's tool list.
Here is how an AI agent typically uses the API to create and publish an SEO article:
All error responses follow the same format:
| Status Code | Meaning |
|---|---|
| 400 | Bad request — missing or invalid parameters |
| 401 | Unauthorized — invalid or missing API key |
| 402 | Payment required. project_limit_reached (POST /projects; body carries a purchase_url) or byo_keys_required (keyword search on the Free Agentic plan). Both return a machine-readable body with next-step instructions. |
| 403 | Forbidden. Resource does not belong to your account, or a beta feature is closed |
| 404 | Not found — resource does not exist |
| 409 | Conflict. The target changed since it was staged (e.g. publishing a suggestion whose article moved) |
| 429 | Rate limited. Too many requests, or a daily cap (e.g. AI-visibility scans) was hit; wait and retry |
| 500 | Server error — something went wrong on our end |