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. Agentic Mode uses Distribb's keyword data. Legacy Free Agentic accounts use their 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 |
The Free Agentic plan is deprecated and no longer offered to new users. Current plans are Agentic Mode at $49/month and Pro at $97/month. If the calling user is on a legacy Free Agentic account 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.
A custom Webhook integration receives initial articles and later edits at the same URL, using the same access token. Handle publish_articles and update_articles separately. Your endpoint must accept this JSON shape, validate the bearer token, and respond within 30 seconds. Use 202 if you queue the work.
| 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 |
| Idempotency-Key | A unique delivery key, reused for retries of that delivery. Later edits have a new key even when the article ID stays the same. |
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 | Stable Distribb article ID. Store it as a unique post identifier. Do not discard all later deliveries with the same ID: they can contain edits. |
| data.articles[].title | string | Article title |
| data.articles[].slug | string | URL slug, e.g. lawn-care-toronto. A fallback to match older posts without a stored Distribb ID. Preserve the existing permalink on content updates. |
| data.articles[].url, published_url | string, optional | The existing stored public URL. An update can identify the article by ID even if its URL is unknown. |
| data.articles[].external_id | string, optional | The remote post ID, when known. |
| 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, published_at | string, optional | Original dates (ISO 8601, UTC). Unknown dates are omitted on updates; retain your stored dates when a field is absent. |
| data.articles[].updated_at | string | Saved modification time, or delivery time when no modification date is available. |
| 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". Sync preserves the article’s current publication status. |
| 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. |
| data.articles[].update_only | boolean | true on updates. If the existing post is missing, return an error and create nothing. |
Return the existing published_url so Distribb can show the article link. A 2xx response confirms receipt; it does not prove your public page has changed. Distribb checks the body for explicit failures, ignored updates, zero processed articles, and unexpected create responses. Initial publication can retry on server errors and timeouts; updates are sent once so an uncertain result is not replayed automatically.
Users can click Send changes to Webhook on a published article or ask the Distribb Agent to send edits. Your existing route receives event_type: "update_articles", is_update: true, and update_only: true. Find the existing post by its stored Distribb ID, update its content, and preserve its permalink and original dates. Match by slug only for older posts without that ID. If no post matches, return 404.
Never fall back to creating a post from an update event. If your route only handles publication today, add this update branch to the same route; you do not need another integration or webhook URL.
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."update_articles" by finding and updating the existing post. Return an error if the post is missing.Instead of writing curl commands, connect Distribb to Claude and call every endpoint as a native tool. The agent calls list_projects, create_article, publish_article directly, with no CLI flags and no shell scripts.
The hosted server is the recommended path: nothing to install, nothing to run, and you sign in with your Distribb account instead of pasting an API key.
In Claude, open Settings → Connectors → Add custom connector and paste:
You are redirected to Distribb, where you sign in and approve the connection. Distribb uses OAuth 2.0 with PKCE, so Claude never sees your password and you never paste an API key. Revoke access at any time from your Distribb settings.
All 18 tools appear immediately and run against your own projects only. Ask for what you want in plain language, for example “find keywords for my project and plan four articles for next month”.
Transport is streamable HTTP. Every tool enforces the same plan limits, rate limits and per-account scoping as the REST API documented above, because each tool call is dispatched to these very same endpoints.
These change data. publish_article pushes a post to your connected CMS, and gbp_reply_review posts a reply that is publicly visible on Google. Nothing in this server deletes projects, articles or reviews.
Prefer to run it yourself, or using an editor without remote MCP support? A local stdio server is available in the distribb-skill repo. It authenticates with a DISTRIBB_API_KEY from your dashboard rather than OAuth, and exposes its own tool set.
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 a legacy Free Agentic account). 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 |