Publish API
RankBloom writes SEO blog content for the websites you connect to it. The Publish API is how that finished content leaves RankBloom and reaches wherever you want it — a WordPress site, a CMS, an automation platform, or your own code.
It is the same API behind the official WordPress plugin and the Zapier and Make integrations.
Base URL
https://app.getrankbloom.com/api/publish
Authentication
Every request carries a connection token. Create one in RankBloom under Connections: pick the site, choose the platform, and click Create connection. The token is shown once and stored only as a hash, so keep a copy when you make it.
A token is scoped to a single site. Send it in any one of these headers:
Authorization: Bearer rb_live_xxxxxxxx
X-RankBloom-Token: rb_live_xxxxxxxx
X-API-KEY: rb_live_xxxxxxxx
Three are accepted because shared and managed hosts routinely strip Authorization, and some automation platforms send an X-API-KEY header by default.
Never put the token in a query string. It would be recorded in access logs and by every proxy along the way. The API does not read it from there.
Revoking a connection takes effect immediately. Revoked and unknown tokens return the same response, so a caller cannot use the difference to probe for valid tokens.
Endpoints
GET /v1/ping
Verifies a token and reports what it is bound to. Use it as a connection test.
curl https://app.getrankbloom.com/api/publish/v1/ping \
-H "Authorization: Bearer rb_live_xxxxxxxx"
{
"ok": true,
"contract_version": 1,
"label": "Example Site",
"connection": { "id": "…", "name": "Make — Example Site", "platform": "make" },
"site": { "id": "…", "name": "Example Site", "url": "https://example.com" }
}
label is a flat, human-readable name for the connection. Automation platforms use it to label the connected account.
GET /v1/posts
Recent posts for the connected site, newest first. Read-only — it never changes state, so it is safe to poll on a timer.
| Parameter | Type | Default | Notes |
|---|---|---|---|
limit | integer | 25 | Maximum 100 |
offset | integer | 0 | Skip this many posts |
since | ISO 8601 | — | Only posts created at or after this time |
curl "https://app.getrankbloom.com/api/publish/v1/posts?limit=100&offset=0" \
-H "Authorization: Bearer rb_live_xxxxxxxx"
{
"ok": true,
"contract_version": 1,
"count": 46,
"limit": 100,
"offset": 0,
"posts": [ … ]
}
Paging. Increase offset by limit to walk back through the archive. There is no total count: a page shorter than limit means you have reached the end. Results are ordered by creation time and then by id, so paging stays stable even when two posts share a timestamp.
POST /v1/status
Tell RankBloom what happened to a post. This is what turns "we handed it over" into "it is live", and it stops the post being offered again.
curl -X POST https://app.getrankbloom.com/api/publish/v1/status \
-H "Authorization: Bearer rb_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"post_id": "b86cbffe-…",
"status": "delivered",
"external_url": "https://example.com/my-post",
"external_id": "123"
}'
| Field | Required | Notes |
|---|---|---|
post_id | yes | The id from /v1/posts |
status | yes | delivered, failed or skipped |
external_url | no | Where it went live |
external_id | no | The remote system's own id |
error | no | Why it failed, if it did |
A token may only report on posts belonging to its own site.
The post payload
One canonical shape, versioned by contract_version.
| Field | Type | Notes |
|---|---|---|
id | string | Stable. Use it as your idempotency key |
title, slug, excerpt | string | slug is the final, de-duplicated one |
content_html, content_md | string | The body, in both formats |
meta_title, meta_description | string | SEO metadata |
primary_keyword, keywords, category, tags | string / array | Taxonomy |
canonical_url | string | Canonical, when set |
schema_json_ld | object | Parsed, never a JSON string |
featured_image_url | string | Hosted image, ready to use |
audio_url | string | Narration, when the post has it |
audio_duration_seconds, audio_voice | number / string | Narration detail |
status | string | published or draft |
author, author_title, author_url | string | Byline |
created_at, published_at | ISO 8601 | Timestamps |
Every key is always present. A value that does not apply comes back null — it is never omitted. A post without narration simply has audio_url: null; that is the normal case, not an error. Write your integration to expect nulls rather than missing keys.
The contract is additive only. Fields are never renamed, retyped or removed, because integrations in the wild keep requesting version 1 indefinitely. A breaking change would ship as a new version served alongside this one.
Errors
Errors return a matching HTTP status and a JSON body with a human-readable message.
{ "ok": false, "error": "Invalid or revoked connection token." }
| Status | Meaning |
|---|---|
400 | A required field is missing or malformed |
401 | Token missing, unknown or revoked |
404 | The post does not belong to this connection |
Ready-made integrations
You do not have to write code. The same API is already wrapped for:
- WordPress — the official Get RankBloom plugin. Connect with one click, no token to paste.
- Make — a custom app with a Watch Posts trigger and a Mark Post as Published action.
- Zapier — a New Post trigger and a Mark Post as Published action.
Support
Questions about the API, or something not behaving as documented — email support@getrankbloom.com.