Docs · API · Articles

Articles.

Creating an article enqueues an asynchronous job; reading one returns the complete generated package — body in two formats, citations, SEO metadata, structured data, quality scores, and derivatives.

Create an article#

POST/v1/articles

Validates the request, verifies the brand profile belongs to your org, debits credits, and returns 202 Accepted with the job. Generation runs the pipeline described in Jobs.

Request body#

FieldTypeDescription
topicstringrequiredWhat the article is about. 4–500 characters. Be specific — the planner builds the angle from this.
target_keywordstringoptionalPrimary SEO keyword (≤200 chars). Falls back to the topic when omitted.
brand_profile_idstringoptionalGrounds the article in that profile's knowledge base, fact sheet, and voice profile. Returns 404 not_foundif the ID doesn't belong to your org.
mode"standard" | "deep_research"default: standarddeep_research runs more research questions on a stronger model stack. 10 vs 25 credits — see the cost table below.
languagestringdefault: "en"Output language code (≤12 chars), e.g. ar, de. Non-English is culturally adapted, not literally translated, and adds 5 credits.
target_wordsintegeroptionalAccepted range 400–4000; the pipeline aims for 600–3000 and defaults to 1600.
tonestringoptionalFreeform tone instruction (≤300 chars). A brand profile voice usually makes this unnecessary.
audiencestringoptionalWho this is for (≤300 chars). Sharpens the outline and research questions.
extra_instructionsstringoptionalAnything else the planner should honor (≤2000 chars): sections to include, positions to take, products to mention.
imagebooleandefault: falseGenerate an editorial hero image (+3 credits). URL lands in hero_image_url.
derivatives("x_thread" | "linkedin" | "newsletter")[]default: []Platform-native repurposing, generated after the article. Any non-empty selection is a flat +3 credits.

Credit cost#

FieldTypeDescription
mode: standard10 creditsBase price for a standard article.
mode: deep_research25 creditsBase price for deep research mode (replaces the 10, not added to it).
image: true+3 creditsHero image generation.
derivatives: […]+3 creditsFlat bundle price whether you request one derivative or all three.
language ≠ "en"+5 creditsCulturally-adapted non-English output.

The example below is deep_research + image + derivatives = 25 + 3 + 3 = 31 credits, confirmed by credits_charged in the response. Credits are debited when the job is created and automatically refunded if it fails. With insufficient balance the call fails up front with 402 insufficient_credits and nothing is charged.

Idempotency#

Pass an Idempotency-Key header to make creation safe to retry. If a job with the same key already exists in your org, the API returns that original job with 200 (instead of 202) and "replayed": true — no new job, no second charge.

Request
curl -X POST https://scribe.whizztech.ai/v1/articles \
  -H "Authorization: Bearer $SCRIBE_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: post-2026-07-06-01" \
  -d '{
    "topic": "Contextual retrieval for production RAG systems",
    "target_keyword": "contextual retrieval",
    "brand_profile_id": "bfa6d2a7-4c1e-4f7a-9a2b-91d3f0a6c8e2",
    "mode": "deep_research",
    "language": "en",
    "target_words": 2200,
    "tone": "technical, direct, first-person plural sparingly",
    "audience": "ML engineers running RAG in production",
    "image": true,
    "derivatives": ["x_thread", "linkedin"]
  }'
Response · 202 Accepted
{
  "id": "4d1a8f36-9e02-47b8-b7c4-3a92d5e60f18",
  "object": "job",
  "type": "article",
  "status": "queued",
  "credits_charged": 31,
  "replayed": false,
  "links": { "self": "/v1/jobs/4d1a8f36-9e02-47b8-b7c4-3a92d5e60f18" }
}
FieldTypeDescription
idstringJob ID. Poll it at /v1/jobs/{id}.
object"job"Always job for this endpoint.
type"article"Job type.
statusstringqueued on a fresh job; a replayed job returns its current status.
credits_chargedintegerTotal credits debited for this job.
replayedbooleantrue when an Idempotency-Key matched an existing job.
links.selfstringRelative URL of the job resource.

List articles#

GET/v1/articles
Query paramTypeDescription
limitintegerdefault: 20Number of most-recent articles to return, capped at 50.
Response · 200 OK
{
  "object": "list",
  "data": [
    {
      "id": "9e72c4b0-51af-4c3d-8e6a-2d94b7f01c55",
      "title": "SaaS Churn Drops When You Instrument These 5 Moments",
      "status": "in_review",
      "mode": "standard",
      "language": "en",
      "word_count": 1584,
      "quality_score": 86,
      "created_at": "2026-07-06T09:45:02.000Z"
    }
  ]
}

List items are summaries — fetch the full object by ID for content and metadata. Article status is one of draft, in_review, approved, published; the pipeline finishes articles in in_review, sign-off moves them to approved, and publishing marks them published.

Retrieve an article#

GET/v1/articles/{id}
Query paramTypeDescription
format"markdown" | "html"optionalmarkdown omits content_html; html omits content_markdown. Omit the param to get both.

The article object#

FieldTypeDescription
idstringArticle ID (UUID).
object"article"Resource type.
statusstringdraft | in_review | approved | published.
modestringstandard | deep_research.
languagestringOutput language code.
topicstringThe topic you submitted.
target_keywordstring | nullThe keyword you submitted.
titlestringFinal SEO-selected title (≤65 chars).
slugstring | nullkebab-case URL slug.
meta_titlestring | nullSERP title tag, ≤60 chars.
meta_descriptionstring | nullSERP description, 140–155 chars, includes the keyword.
content_markdownstringFull article in Markdown, H1 first line, inline citation links.
content_htmlstringThe same content rendered to HTML.
citations{claim, url, title}[]Every external source linked in the article, mapped to the claim it supports.
title_variantsstring[]3 alternative titles with different psychological hooks, for A/B testing.
faq{q, a}[]3–4 question/answer pairs actually answered in the article (FAQPage schema source).
json_ldobjectschema.org @graph with BlogPosting and, when FAQ exists, FAQPage. Paste into a <script type="application/ld+json"> tag.
word_countintegerWords in the final edit.
read_minutesnumberEstimated reading time.
flesch_scorenumber | nullFlesch Reading Ease. Computed for English only; null otherwise.
quality_scoreinteger | null0–100 LLM rubric. Strict scale: 80+ means publishable without edits.
quality_breakdownobject{overall, clarity, hook, specificity, information_gain, notes}.
hero_image_urlstring | nullHero image URL when image: true was requested and generation succeeded.
derivatives{type, content}[]Requested repurposed formats — shapes below.
created_atstringISO 8601 timestamp.

Derivative content shapes#

Each derivative's content is format-specific:

derivatives[] shapes
"derivatives": [
  { "type": "x_thread", "content": ["Tweet 1 — the hook…", "Tweet 2…", "…"] },
  { "type": "linkedin", "content": { "text": "Hook line…\n\nBody…" } },
  {
    "type": "newsletter",
    "content": {
      "subject_lines": ["Option A", "Option B", "Option C"],
      "preview": "Preview text under 90 chars",
      "body_markdown": "Personal-tone email body…"
    }
  }
]

Update an article#

PATCH/v1/articles/{id}

Edit the copy or move the article through its review lifecycle. Send at least one field; anything you omit is left untouched. Updating is free — no credits are charged, and the call counts only against your rate limit.

FieldTypeDescription
titlestringoptionalReplacement headline, ≤300 characters.
content_markdownstringoptionalReplacement body. content_html is re-rendered from it and word_count, read_minutes, and flesch_score are recomputed.
meta_titlestringoptionalSERP title tag.
meta_descriptionstringoptionalSERP description.
statusstringoptionalOne of draft, in_review, approved, published.
Approve an article
curl -X PATCH https://scribe.whizztech.ai/v1/articles/9e72c4b0-51af-4c3d-8e6a-2d94b7f01c55 \
  -H "Authorization: Bearer $SCRIBE_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "status": "approved" }'

The response is the full article object, exactly as GET /v1/articles/{id} returns it.