Articles endpoints

Reference for GET /articles, GET /articles/{id} and PATCH /articles/{id}, with every parameter, the Article object, full examples and status codes.

On this page
  1. Which articles the API returns
  2. The Article object
  3. List articles
  4. Retrieve an article
  5. Report a live URL
  6. Status codes by endpoint
  7. Related

The articles endpoints deliver a site's finished articles and record where each one went live. This page is the complete reference: the Article object, listing and paging, retrieving one article, reporting a live URL, and every status code each endpoint returns.

All three operations use the base URL https://rankbox.xyz/api/public/v1 and an API key sent as Authorization: Bearer <key>. A key only ever sees its own site's articles. See Authentication and API keys.

Which articles the API returns

The API returns finished articles: the ones in the Published view of Dashboard → Articles. In Rankbox, "published" means the article is written and released for your site. It goes live on your site when your integration publishes it there.

These never appear in any articles endpoint:

  • Ideas, scheduled articles, and articles that are still being written.
  • Unpublished edits to a finished article. Edits reach the API when you click Publish changes in the editor. See Editing articles.
  • Deleted articles. A deleted article disappears from every endpoint, with no tombstone. See Full walk to detect deletions.
  • Articles of any other site, including other sites on the same account.

The Article object

Every endpoint returns articles in the same shape.

FieldTypeDescription
idstring (UUID)Permanent, unique id of the article. Use it as the article's key in your CMS
slugstringURL slug built from the current title plus the first 8 characters of id, for example how-to-price-a-saas-product-8f14e45f. Changes when the title changes. See How slugs are built
titlestringThe article's title
descriptionstringThe meta description. "" when the article has none
body_markdownstringThe article body in Markdown, with Rankbox's writing notes removed. Usually starts with the title as a level-1 heading. See What the body contains
body_htmlstringThe same body rendered to HTML. Not sanitized. "" when the body is empty
tagsarray of stringsTopic tags. [] when there are none
seo_scoreintegerRankbox's SEO and GEO score for the article, from 0 to 100. See The SEO and GEO score
published_urlstring or nullThe live URL Rankbox has on file for the article. null until one is reported with PATCH /articles/{id} or recorded another way (see Live URLs and verification)
published_atstring (timestamp)Currently always equal to updated_at. It is not the date the article went live
updated_atstring (timestamp)When the article last changed in Rankbox. Orders the list and drives since

Timestamps are ISO 8601 in UTC with microseconds, such as 2026-09-30T14:03:11.204518+00:00.

A complete article:

JSON
{
  "id": "8f14e45f-ceea-4c8e-9a3b-2d1f6b7c9a10",
  "slug": "how-to-price-a-saas-product-8f14e45f",
  "title": "How to Price a SaaS Product",
  "description": "A practical way to set SaaS prices: pick a value metric, test three tiers, and review prices every quarter.",
  "body_markdown": "# How to Price a SaaS Product\n\nPrice on the value your customer gets, not on your costs. Pick one value metric, test three tiers, and review prices every quarter.\n\n## Pick a value metric\n\nA value metric grows as the customer gets more out of the product.\n\n- Seats\n- Usage\n- Outcomes",
  "body_html": "<h1>How to Price a SaaS Product</h1>\n<p>Price on the value your customer gets, not on your costs. Pick one value metric, test three tiers, and review prices every quarter.</p>\n<h2>Pick a value metric</h2>\n<p>A value metric grows as the customer gets more out of the product.</p>\n<ul>\n<li>Seats</li>\n<li>Usage</li>\n<li>Outcomes</li>\n</ul>\n",
  "tags": ["pricing", "saas"],
  "seo_score": 92,
  "published_url": null,
  "published_at": "2026-09-30T14:03:11.204518+00:00",
  "updated_at": "2026-09-30T14:03:11.204518+00:00"
}

What the body contains

body_markdown is the article as written in Rankbox, cleaned for a public page. body_html is that Markdown rendered with GitHub-flavored Markdown rules, where a single line break becomes <br>.

  • Writing notes are removed. Rankbox's editor stores image suggestions such as **[Image: …] (alt: "…")** and internal-link placeholders such as [anchor](#internal: target). The API drops the image suggestions and keeps only the anchor text of the placeholders. The body therefore contains no images: add your own in your CMS if your template needs them.
  • The title is usually the first line. Most bodies start with the title as a level-1 heading (# Title in Markdown, <h1> in HTML). If your page template already shows title as the heading, remove that first heading before you save the body, or the title appears twice.
  • A body can include a video embed. Some articles have a section headed ## Watch: … containing a YouTube <iframe> and a plain link to the same video beneath it. Raw HTML in the Markdown passes through to body_html unchanged. If your CMS strips iframes, the link still carries the reference.
  • Links stay as written. Source links and any link placed through the backlink exchange are ordinary links. Publish them without adding rel="nofollow", "sponsored" or "ugc": the exchange checks hosted links on your live page and counts those attributes as a failure. See Backlink exchange.

How slugs are built

Rankbox doesn't store a slug. It derives one from the current title on every response:

  1. Lowercase the title and apply Unicode NFKD normalization.
  2. Replace every run of characters other than a–z and 0–9 with a single -.
  3. Trim leading and trailing hyphens and cut the result to 60 characters.
  4. Append - and the first 8 characters of the article's id. A title with no usable characters becomes article- plus those 8 characters.
TitleSlug
How to Price a SaaS Producthow-to-price-a-saas-product-8f14e45f
Résumé tips for 2026: what works?re-sume-tips-for-2026-what-works-0b3e5f7a

The second row shows an accented letter splitting a word, because the accent mark is removed as a separate character. The id suffix keeps slugs unique within a site, but retitling an article in Rankbox changes its slug. If the article is already live, store the slug you first published under and keep using it. See Keep slugs stable.

When the timestamps change

updated_at moves forward whenever Rankbox writes to the article, including:

  • You click Publish changes on a finished article in the editor.
  • Your integration, or another one, reports a live URL with PATCH /articles/{id}.
  • Rankbox records a live URL for the article some other way.

Because a live-URL report changes updated_at without changing the content, updated_at alone can't tell you whether the content changed. Compare a hash of the fields you publish instead. published_at mirrors updated_at, so it moves at the same moments: don't display it as a publication date. Record the date your site first published the article in your own CMS.

List articles

GET /articles

Returns a page of the site's finished articles, ordered by updated_at from oldest to newest.

Query parameters

ParameterTypeDefaultDescription
limitinteger1 when omittedArticles per page. Values are cut to whole numbers and kept between 1 and 100. Send it on every call; limit=100 is the most efficient
sinceISO 8601 timestampnoneReturn only articles whose updated_at is later than this time. Pass the next_since of your previous page. Read to the millisecond. A value that doesn't parse is ignored
publishedfalsenonepublished=false returns only articles with no live URL on file (published_url is null). Any other value is ignored

The parameters combine. ?published=false&since=…&limit=100 pages through the articles that have no live URL yet.

Response

FieldTypeDescription
articlesarray of ArticleThis page, oldest change first
countintegerThe number of articles in this page, not the total
next_sincestring or nullThe updated_at of the last article in this page. When the page is empty, the since value you sent, unchanged, or null if you sent none

There is no offset, page number or total count. You page forward by sending next_since back as since. A page with fewer articles than limit is the last one.

The cursor overlaps at the page boundary: updated_at has microseconds, but since is read to the millisecond, so the last article of one page usually comes back as the first article of the next. Treat articles as upserts keyed by id, and stop paging on a short page rather than an empty one. How the cursor works explains the details.

Examples for listing

curl -G https://rankbox.xyz/api/public/v1/articles \
  -H "Authorization: Bearer $RANKBOX_API_KEY" \
  --data-urlencode "limit=100" \
  --data-urlencode "since=2026-09-30T14:03:11.204518+00:00"

Example response, the last page of a walk:

JSON
{
  "articles": [
    {
      "id": "8f14e45f-ceea-4c8e-9a3b-2d1f6b7c9a10",
      "slug": "how-to-price-a-saas-product-8f14e45f",
      "title": "How to Price a SaaS Product",
      "description": "A practical way to set SaaS prices: pick a value metric, test three tiers, and review prices every quarter.",
      "body_markdown": "# How to Price a SaaS Product\n\nPrice on the value your customer gets, not on your costs. Pick one value metric, test three tiers, and review prices every quarter.\n\n## Pick a value metric\n\nA value metric grows as the customer gets more out of the product.\n\n- Seats\n- Usage\n- Outcomes",
      "body_html": "<h1>How to Price a SaaS Product</h1>\n<p>Price on the value your customer gets, not on your costs. Pick one value metric, test three tiers, and review prices every quarter.</p>\n<h2>Pick a value metric</h2>\n<p>A value metric grows as the customer gets more out of the product.</p>\n<ul>\n<li>Seats</li>\n<li>Usage</li>\n<li>Outcomes</li>\n</ul>\n",
      "tags": ["pricing", "saas"],
      "seo_score": 92,
      "published_url": null,
      "published_at": "2026-09-30T14:03:11.204518+00:00",
      "updated_at": "2026-09-30T14:03:11.204518+00:00"
    },
    {
      "id": "0b3e5f7a-1c2d-4e5f-8a9b-0c1d2e3f4a5b",
      "slug": "re-sume-tips-for-2026-what-works-0b3e5f7a",
      "title": "Résumé tips for 2026: what works?",
      "description": "What hiring managers say they read first, and how to structure a résumé around it.",
      "body_markdown": "# Résumé tips for 2026: what works?\n\n...",
      "body_html": "<h1>Résumé tips for 2026: what works?</h1>\n<p>...</p>\n",
      "tags": ["careers"],
      "seo_score": 88,
      "published_url": "https://www.example.com/blog/re-sume-tips-for-2026-what-works-0b3e5f7a",
      "published_at": "2026-10-01T09:12:44.871203+00:00",
      "updated_at": "2026-10-01T09:12:44.871203+00:00"
    }
  ],
  "count": 2,
  "next_since": "2026-10-01T09:12:44.871203+00:00"
}

Retrieve an article

GET /articles/{id}

Returns one finished article of the key's site.

Path parameterTypeDescription
idstring (UUID)The article's id, exactly as the API returned it

The response wraps the article in an article field:

curl https://rankbox.xyz/api/public/v1/articles/8f14e45f-ceea-4c8e-9a3b-2d1f6b7c9a10 \
  -H "Authorization: Bearer $RANKBOX_API_KEY"
JSON
{
  "article": {
    "id": "8f14e45f-ceea-4c8e-9a3b-2d1f6b7c9a10",
    "slug": "how-to-price-a-saas-product-8f14e45f",
    "title": "How to Price a SaaS Product",
    "description": "A practical way to set SaaS prices: pick a value metric, test three tiers, and review prices every quarter.",
    "body_markdown": "# How to Price a SaaS Product\n\n...",
    "body_html": "<h1>How to Price a SaaS Product</h1>\n<p>...</p>\n",
    "tags": ["pricing", "saas"],
    "seo_score": 92,
    "published_url": null,
    "published_at": "2026-09-30T14:03:11.204518+00:00",
    "updated_at": "2026-09-30T14:03:11.204518+00:00"
  }
}

The API returns 404 with { "error": "Article not found" } when no finished article with that id exists on the key's site: it was deleted, it isn't finished, or it belongs to another site. An id that isn't a UUID at all returns 500 with { "error": "Internal error" }, so validate ids before you call if they come from user input.

Use this endpoint to refresh one article, for example from a "resync" button. To sync many articles, page through GET /articles instead: one list call returns up to 100 articles for the cost of one request.

Report a live URL

PATCH /articles/{id}

Records the public URL where your site published an article. Rankbox stores it as the article's published_url, and the backlink exchange uses that page to verify links hosted in the article.

Request body

Send a JSON object with Content-Type: application/json:

FieldTypeRequiredDescription
published_urlstringYesThe absolute https:// (or http://) URL of the article's live page on your site. Whitespace at either end is trimmed

Any other field in the body is ignored. Send the full URL with its scheme: Rankbox stores the value as you send it.

Validation rules

Rankbox checks the request in this order and stops at the first failure:

  1. The body has a published_url. A missing body, invalid JSON, a non-string value or an empty string returns 400 with "Send { published_url } — the article's live URL."
  2. The URL is a public web address. The scheme must be http or https, and the host can't be localhost, a private or reserved IP address, or a local-only name such as one ending in .local, .internal, .lan, .localhost or .home.arpa. Otherwise the API returns 400 with "published_url is not a public http(s) URL."
  3. The URL is on the site's own domain. The host must match the Website set for this site in Dashboard → Settings → Your brand, or the domain the site verified in the backlink exchange. A leading www. is ignored on both sides, and subdomains match, so blog.example.com and www.example.com both pass for example.com. Otherwise the API returns 400 with "published_url must be on your own site (example.com)." naming the accepted domains, or "published_url must be on your own site (set your website in Settings)." when neither is set.
  4. The article is a finished article of the key's site. Otherwise the API returns 404 with "Article not found".

Because the URL is checked before the article is looked up, a request with an off-domain URL for an id that doesn't exist returns 400, not 404.

What a successful report changes

  • published_url becomes the URL you sent, replacing any earlier value, including one entered by hand on the Backlinks page.
  • Rankbox records that the URL came from an integration.
  • updated_at and published_at move to the time of the request, so the article appears in your next incremental sync.
  • The article no longer appears in GET /articles?published=false.

The API can't clear a live URL: an empty published_url is rejected. Sending the same URL again leaves the same value, so retrying a PATCH is safe, but each success moves updated_at again. Only report when the URL differs from the article's current published_url.

Examples for reporting

curl -X PATCH https://rankbox.xyz/api/public/v1/articles/8f14e45f-ceea-4c8e-9a3b-2d1f6b7c9a10 \
  -H "Authorization: Bearer $RANKBOX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"published_url": "https://www.example.com/blog/how-to-price-a-saas-product-8f14e45f"}'

The response is the updated article:

JSON
{
  "article": {
    "id": "8f14e45f-ceea-4c8e-9a3b-2d1f6b7c9a10",
    "slug": "how-to-price-a-saas-product-8f14e45f",
    "title": "How to Price a SaaS Product",
    "description": "A practical way to set SaaS prices: pick a value metric, test three tiers, and review prices every quarter.",
    "body_markdown": "# How to Price a SaaS Product\n\n...",
    "body_html": "<h1>How to Price a SaaS Product</h1>\n<p>...</p>\n",
    "tags": ["pricing", "saas"],
    "seo_score": 92,
    "published_url": "https://www.example.com/blog/how-to-price-a-saas-product-8f14e45f",
    "published_at": "2026-10-02T08:41:07.552190+00:00",
    "updated_at": "2026-10-02T08:41:07.552190+00:00"
  }
}

A domain mismatch looks like this:

JSON
{
  "error": "published_url must be on your own site (example.com)."
}

Status codes by endpoint

StatusGET /articlesGET /articles/{id}PATCH /articles/{id}Meaning
200YesYesYesSuccess
204OPTIONSOPTIONSOPTIONSCORS preflight, no body
400NoNoYesMissing, unsafe or off-domain published_url
401YesYesYesMissing, malformed, unknown or revoked key
402YesYesYesThe key's site isn't active on a trial or plan (code: subscription_required)
404NoYesYesNo finished article with that id on the key's site
429YesYesYesRate limit exceeded
500YesYesYesServer error, or an article id that isn't a UUID

GET /articles never returns 404: a site with no finished articles returns 200 with an empty articles array. Exact messages and handling for every status are in Errors.