CitationBenchTalk to Sales
API referenceProduction

Refine API — Apply Brand Voice, SEO, and CTA Transforms to Content

REST API for applying reusable refiners to blog posts and landing pages. Chain brand voice, SEO hygiene, image insertion, and CTA refiners to keep hundreds of articles consistent without rewriting by hand.

Apply a refiner — a reusable post-generation transform — to a piece of content. Refiners are how brand voice, SEO hygiene, image generation, and CTA insertion stay consistent across hundreds of articles without rewriting them by hand. The conceptual model lives in Content Refiners; this page is the API.

Endpoints

MethodPathPurpose
POST/v1/produce/refineApply one refiner to one content
POST/v1/produce/refine/bulkApply across many content items
GET/v1/produce/refine/refinersList refiners
GET/v1/produce/refine/refiners/{id}Get one refiner
POST/v1/produce/refine/refinersCreate a refiner
PATCH/v1/produce/refine/refiners/{id}Update
DELETE/v1/produce/refine/refiners/{id}Delete
POST/v1/produce/refine/refiners/{slug}/forkFork a system refiner into the workspace

POST /v1/produce/refine

{
  "contentType": "blog_post",
  "contentId": "bp_***",
  "refinerId": "rfn_brand-voice"
}
FieldTypeRequiredDefaultNotes
contentType"blog_post" | "landing_page" | "content"yesWhat's being refined
contentIdstringyes
refinerIdstringyes (one of)Single refiner
refinerIdsstring[]yes (one of)Chain of refiners; applied in order
customPromptstringnoAd-hoc refinement without saving as a refiner

Response

{
  "invocationId": "inv_***",
  "agentId": "agt_***",
  "skill": "produce.refine",
  "contentType": "blog_post",
  "contentId": "bp_***",
  "originalRevision": "rev_***",
  "status": "RUNNING",
  "estimatedCost": { "credits": 5, "durationSeconds": 30 }
}

Final result

{
  "invocationId": "inv_***",
  "agentId": "agt_***",
  "skill": "produce.refine",
  "status": "SUCCEEDED",
  "creditsUsed": 5,
  "result": {
    "contentId": "bp_***",
    "refinerId": "rfn_brand-voice",
    "previousRevision": "rev_***A",
    "newRevision": "rev_***B",
    "diff": {
      "wordsAdded": 142,
      "wordsRemoved": 87,
      "sectionsModified": ["intro", "section_3"]
    },
    "imagesGenerated": [],
    "errors": []
  },
  "raw": "Applied the brand-voice refiner. Tightened the intro from 220 to 145 words. Section 3 rewritten in second person ...",
  "files": ["agent-workspace/diff.md", "agent-output/refined-draft.md"]
}

For IMAGE_REFINER / MULTI_IMAGE_REFINER, imagesGenerated is populated:

"imagesGenerated": [
  { "templateId": "acme-hero",   "imageId": "img_***", "url": "https://cdn.citationbench.com/.../hero.png" },
  { "templateId": "acme-inline", "imageId": "img_***", "url": "..." }
]

POST /v1/produce/refine/bulk

Apply one refiner across many content items.

curl -X POST .../v1/produce/refine/bulk -d '{
  "refinerId": "rfn_brand-voice",
  "scope": {
    "contentType":  "blog_post",
    "tags":         ["q2-2026"],
    "publishStatus":"DRAFT"
  },
  "concurrency": 5
}'

Returns one invocation per matched content + a batch handle.


CRUD: /v1/produce/refine/refiners

List

curl -G .../v1/produce/refine/refiners \
  --data-urlencode "type=SIMPLE_REFINER,IMAGE_REFINER"
{
  "data": [
    {
      "id": "rfn_brand-voice",
      "type": "SIMPLE_REFINER",
      "title": "Acme brand voice",
      "description": "Second person; cut corporate cliches; tighten",
      "createdAt": "2026-02-15T...",
      "updatedAt": "2026-05-20T...",
      "useCount": 142
    },
    {
      "id": "rfn_og-image",
      "type": "MULTI_IMAGE_REFINER",
      "title": "Standard image set",
      "description": "Hero + 2 inline + CTA",
      "templates": [
        { "templateId": "acme-hero" },
        { "templateId": "acme-inline" },
        { "templateId": "acme-cta" }
      ],
      "createdAt": "...",
      "updatedAt": "...",
      "useCount": 88
    }
  ]
}

Create

curl -X POST .../v1/produce/refine/refiners -d '{
  "type":        "SIMPLE_REFINER",
  "title":       "SEO hygiene pass",
  "description": "Fix heading hierarchy, add internal links, ensure primary keyword in H1",
  "prompt":      "Apply the following SEO hygiene rules to this article: ..."
}'

Update

curl -X PATCH .../v1/produce/refine/refiners/rfn_*** -d '{
  "prompt": "Updated prompt ..."
}'

Delete

curl -X DELETE .../v1/produce/refine/refiners/rfn_***

Fork (from a system refiner)

curl -X POST .../v1/produce/refine/refiners/system.seo-hygiene/fork -d '{
  "title": "Acme SEO hygiene (customized)"
}'

MCP

> Apply the brand-voice refiner to bp_***.

Claude calls produce.refine.apply.

> Re-apply brand-voice across every q2-2026 draft.

Claude calls produce.refine.bulk.

> Show me my refiner library.

Claude calls produce.refine.list_refiners.


Errors

StatusCodeCause
404refiner_not_found
404content_not_found
409concurrent_refinementAnother refinement on the same content is in flight
422incompatible_refinerE.g. applying IMAGE_REFINER to a content type without images

Cost

Refiner typeCredits
SIMPLE_REFINER5
IMAGE_REFINER1 per image
MULTI_IMAGE_REFINER1 per image (so 3 images = 3)
customPrompt ad-hoc5

Use cases (string things together)

A. Standard publish pipeline

PIPELINE="rfn_brand-voice rfn_seo-cleanup rfn_og-image"
for R in $PIPELINE; do
  curl -X POST .../v1/produce/refine -d "{
    \"contentType\": \"blog_post\",
    \"contentId\":   \"bp_***\",
    \"refinerId\":   \"$R\"
  }"
done

B. Workspace-wide refiner refresh

You tweak rfn_brand-voice. Re-apply across the live library:

curl -X POST .../v1/produce/refine/bulk -d '{
  "refinerId": "rfn_brand-voice",
  "scope":     { "contentType": "blog_post", "publishStatus": "PUBLISHED" }
}'

C. Chain in one call

curl -X POST .../v1/produce/refine -d '{
  "contentType":"blog_post",
  "contentId":  "bp_***",
  "refinerIds": ["rfn_brand-voice", "rfn_seo-cleanup", "rfn_og-image"]
}'

Refiners applied in array order; each creates a tracked revision.

D. Custom one-off

curl -X POST .../v1/produce/refine -d '{
  "contentType":  "blog_post",
  "contentId":    "bp_***",
  "customPrompt": "Add a TL;DR at the top, max 3 sentences."
}'

On this page