RankPine docs
API for AI agents

Articles

List and read finished articles (markdown + HTML), and start article generation for any keyword.

Articles move through planned → writing → ready → published (plus failed and archived). The API reads every state and can start generation.

On a site with additional languages, each language version is its own article with its own id, status, and slug. They share a translation_group_id, and each carries its own language.

List articles

GET /api/v1/sites/{site_id}/articles?status=ready&search=&language=&page=1&per_page=20

Bodies are omitted from lists — fetch one article for content.

ParamNotes
statusplanned · writing · ready · published · failed · archived
searchSubstring match on title or keyword.
languageOnly articles written in this code (e.g. de). See the note below.
{
  "data": [
    {
      "id": "a917…",
      "status": "published",
      "title": "The 7 Best Privacy-First Analytics Tools in 2026",
      "language": "en",
      "translation_group_id": null,
      "keyword": "privacy first analytics",
      "target_keyword": "privacy first analytics",
      "word_count": 2140,
      "seo_score": 92,
      "scheduled_for": "2026-07-03T08:00:00.000Z",
      "generated_at": "2026-07-03T08:09:31.000Z",
      "published_at": "2026-07-03T08:10:02.000Z",
      "gsc_index_state": "indexed",
      "created_at": "2026-07-01T11:00:00.000Z"
    }
  ],
  "page": 1,
  "per_page": 20,
  "total": 58
}

One row per language version

A multi-language site returns every version as its own row, so total counts versions rather than topics. All versions of one article share a keyword — that's the keyword the original targets — so language is what tells them apart, and target_keyword is the phrase that version actually goes after. Filter with ?language=de for one language, or group by translation_group_id to reassemble a topic. translation_group_id is null for an article with no translations.

Get an article

GET /api/v1/sites/{site_id}/articles/{article_id}

The full piece: markdown and HTML bodies, per-check SEO scorecard, cited sources, image gallery, and publish state.

{
  "id": "a917…",
  "status": "ready",
  "title": "…",
  "slug": "best-privacy-first-analytics-tools",
  "meta_description": "…",
  "language": "en",
  "translation_group_id": null,
  "keyword": {
    "text": "privacy first analytics",
    "target": "privacy first analytics",
    "volume": 480,
    "difficulty": 31
  },
  "body_markdown": "## …",
  "body_html": "<h2>…",
  "word_count": 2140,
  "seo_score": 92,
  "seo_checks": [
    { "id": "title-length", "label": "Title length", "status": "pass", "detail": "…" }
  ],
  "sources": [{ "url": "https://…", "title": "…" }],
  "featured_image_url": "https://cdn.…/hero.webp",
  "images": [{ "url": "https://cdn.…/inline-1.webp", "alt_text": "…" }],
  "external_url": null,
  "gsc_index_state": null,
  "failure_reason": null,
  "scheduled_for": "…",
  "generated_at": "…",
  "published_at": null,
  "created_at": "…"
}

Generate an article (asynchronous)

POST /api/v1/sites/{site_id}/articles/generate

Target an existing library keyword by id, or pass a raw keyword — it's matched to the library or created on the fly:

{ "keyword_id": "7b21…" }
{ "keyword": "webhook vs polling for analytics events" }

Returns 202:

{
  "article_id": "c2d8…",
  "status": "writing",
  "queued": true,
  "language": "en",
  "additional_languages": ["de", "es"],
  "credits_expected": 1,
  "message": "Generation started. A full article takes several minutes — poll GET /v1/sites/{site_id}/articles/{article_id} until status is \"ready\". This site also publishes in de, es, so once the en article is ready it is localized into 2 more, each its own article sharing this one's translation_group_id. Those are covered by the language add-on and cost no extra article credits."
}

Notes for agents:

  • Generation consumes exactly one article credit, whatever the language count — translations are covered by a paid add-on, not metered. 403 quota_exceeded when spent, 403 site_inactive without a live subscription.
  • One call can produce several articles. article_id is the original; the translations don't exist yet, and are created once it's ready. To find them, poll GET /sites/{site_id}/articles?language={code} or match on translation_group_id — they aren't listed in this response because they haven't been created at that point. Check additional_languages on GET /sites/{site_id} to know what's coming.
  • If the keyword already has an article in flight or finished, nothing is double-generated — you get that article's id with "queued": false.
  • Rate limit: 15/hour per account on top of the quota.
  • Publishing follows the site's connected integrations and autopilot settings — a ready article auto-publishes exactly as if the dashboard produced it.

On this page