CitationBenchTalk to Sales
API referenceProduction

Publish API — Push Content to WordPress, Ghost, Webflow, and Custom CMS Hooks

REST API for publishing blog posts and landing pages to connected CMS platforms. Supports WordPress, Wisp, Ghost, Webflow, and custom REST hooks with approval gating and auto-indexing on success.

Publish a blog post or landing page to a connected CMS — WordPress, Wisp, Ghost, Webflow, or a custom REST hook. Auto-fires indexing submissions (GSC + IndexNow) on success.

Conceptual overview

Publishing is the last mile. Once content is drafted, refined, and evaluated, you push it to a platform. CitationBench maintains workspace-level platform configs — connected accounts that publishing references. One workspace can have many platforms (a WordPress blog + a Wisp landing-page CMS, for example) and one publish call targets one of them.

By default, publishing is approval-gated in agency workspaces. The agent pauses before sending; you review the rendered preview, approve, and the actual push happens.

Endpoints

MethodPathPurpose
POST/v1/produce/publishPublish content
POST/v1/produce/publish/{contentId}/unpublishTake it down
GET/v1/produce/publish/historyList publishing events
GET/v1/produce/publish/platformsList platform configs
POST/v1/produce/publish/platformsConnect a platform
GET/v1/produce/publish/platforms/{id}Get one
PATCH/v1/produce/publish/platforms/{id}Update credentials / settings
DELETE/v1/produce/publish/platforms/{id}Disconnect
POST/v1/produce/publish/platforms/{id}/testTest a platform connection

POST /v1/produce/publish

{
  "contentType": "blog_post",
  "contentId": "bp_***",
  "platformConfigId": "pfm_wordpress-main",
  "isDraft": false,
  "scheduledAt": "2026-05-25T09:00:00-04:00",
  "alsoIndex": true
}
FieldTypeRequiredDefaultNotes
contentType"blog_post" | "landing_page"yes
contentIdstringyes
platformConfigIdstringyesTarget platform
isDraftbooleannofalsePublish as draft instead of live
scheduledAtISO timestampnonowSchedule for later
alsoIndexbooleannoworkspace autoQueueOnPublishAuto-fire indexing submissions after publish
categoryIds / tagIdsstring[]noPlatform-specific
featuredImageIdstringnoIf IMAGE_REFINER produced one, reference it here

Response (approval-gated, default)

{
  "invocationId": "inv_***",
  "agentId": "agt_***",
  "skill": "produce.publish",
  "status": "WAITING_APPROVAL",
  "approvalId": "appr_***",
  "preview": {
    "platform": "wordpress",
    "title": "How engineering teams track capacity",
    "slug": "engineering-team-capacity-tracking",
    "url": "https://acme.com/blog/engineering-team-capacity-tracking",
    "categories": ["Engineering"],
    "tags": ["capacity", "engineering"],
    "featuredImageUrl": "https://cdn.citationbench.com/images/img_***.png",
    "scheduledAt": "2026-05-25T09:00:00-04:00"
  }
}

Approve via Agent · approval. On approval the publish actually fires.

Response (auto-publish, workspace policy allows)

{
  "invocationId": "inv_***",
  "agentId": "agt_***",
  "skill": "produce.publish",
  "status": "SUCCEEDED",
  "creditsUsed": 1,
  "result": {
    "publishedUrl": "https://acme.com/blog/engineering-team-capacity-tracking",
    "platformResponse": {
      "id": "wp_post_12345",
      "publishedAt": "2026-05-24T08:30:00Z",
      "permalink": "https://acme.com/blog/engineering-team-capacity-tracking"
    },
    "indexingTriggered": [
      { "platform": "gsc", "submissionId": "sub_***" },
      { "platform": "indexnow", "submissionId": "sub_***" }
    ]
  },
  "raw": "Pushed to WordPress (post id wp_post_12345). Indexing fired ...",
  "files": ["agent-output/publish-receipt.json"]
}

POST /v1/produce/publish/{contentId}/unpublish

curl -X POST .../v1/produce/publish/bp_***/unpublish -d '{
  "platformConfigId": "pfm_wordpress-main",
  "redirectTo":       "https://acme.com/blog/superseded-by-foo"
}'

If supported by the platform, sets the post to private/draft and optionally configures a redirect.


GET /v1/produce/publish/history

curl -G .../v1/produce/publish/history \
  --data-urlencode "platformConfigId=pfm_wordpress-main" \
  --data-urlencode "since=2026-05-01T00:00:00Z"
{
  "data": [
    {
      "id": "ph_***",
      "contentType": "blog_post",
      "contentId": "bp_***",
      "platform": "wordpress",
      "publishedUrl": "https://acme.com/blog/...",
      "publishedAt": "2026-05-24T08:30:00Z",
      "status": "SUCCESS",
      "invocationId": "inv_***"
    }
  ]
}

CRUD: /v1/produce/publish/platforms

List

curl .../v1/produce/publish/platforms
{
  "data": [
    {
      "id": "pfm_wordpress-main",
      "type": "wordpress",
      "name": "Acme blog",
      "baseUrl": "https://acme.com",
      "connected": true,
      "lastTestedAt": "2026-05-20T...",
      "settings": { "defaultCategoryId": 5, "defaultStatus": "publish" }
    },
    {
      "id": "pfm_wisp-pricing",
      "type": "wisp",
      "name": "Acme pricing pages",
      "connected": true
    }
  ]
}

Connect a new platform

curl -X POST .../v1/produce/publish/platforms -d '{
  "type":      "wordpress",
  "name":      "Acme blog",
  "baseUrl":   "https://acme.com",
  "credentials": {
    "username":         "admin",
    "applicationPassword": "***"
  },
  "settings": {
    "defaultCategoryId": 5,
    "defaultStatus":     "publish"
  }
}'

Supported types: wordpress, wisp, ghost, webflow, custom_rest.

Test

curl -X POST .../v1/produce/publish/platforms/pfm_***/test

Returns success/failure of the auth + a sample API call.


MCP

> Publish bp_*** to our WordPress.

Claude calls produce.publish.send. If workspace policy requires approval, surfaces the preview for you to approve first.

> Publish all DRAFT blog posts in the performance pillar to WordPress, scheduled one per day at 9am Eastern.

Claude lists drafts, then calls produce.publish.send for each with staggered scheduledAt.


Errors

StatusCodeCause
400validation_errorMissing fields
403platform_not_configuredplatformConfigId not in this workspace
422platform_publish_failedPlatform rejected the post; see details.platformError
503platform_unavailableConnection timed out

Cost

ActionCredits
Per publish1 (covers downstream indexing as well, if enabled)
Unpublishfree
Platform CRUDfree
Platform testfree

Use cases (string things together)

A. Auto-publish on draft completion

The content_factory skill's last step is produce.publish (approval-gated). Once you approve, it ships.

B. Schedule a content calendar

DAY=0
for BP in $(curl -sf -G .../v1/produce/blog-post --data-urlencode "status=DRAFT" --data-urlencode "tag=q2-2026" | jq -r '.data[].id'); do
  SCHED=$(date -u -d "+$DAY day 09:00" +"%Y-%m-%dT%H:%M:%SZ")
  curl -X POST .../v1/produce/publish -d "{
    \"contentType\":      \"blog_post\",
    \"contentId\":        \"$BP\",
    \"platformConfigId\": \"pfm_wordpress-main\",
    \"scheduledAt\":      \"$SCHED\"
  }"
  DAY=$((DAY+1))
done

C. Multi-platform publish

Different content types go to different platforms.

curl -X POST .../v1/produce/publish -d '{
  "contentType":      "blog_post",
  "contentId":        "bp_***",
  "platformConfigId": "pfm_wordpress-main"
}'

curl -X POST .../v1/produce/publish -d '{
  "contentType":      "landing_page",
  "contentId":        "lp_***",
  "platformConfigId": "pfm_webflow-pricing"
}'

D. Gate publishing on quality

Use an eval gate that blocks publish until evaluation score ≥ 75:

- applies_to: produce.publish
  when: { field_lt: { evaluationScore: 75 } }
  then: { action: escalate_to_approval, reason: "Quality below threshold" }

On this page