Platform

REST API

Everything the app does to your content, your own code can do. Create a product, generate keywords, pre-create next week’s articles, pull the markdown, retry a failed publish.

Getting a key#

Keys are created in the app: open Settings → API keys and click Create key. The full key is shown once, at creation. Copy it then — we store a hash, so we cannot show it to you again, and the only recovery is to revoke it and make another.

A key is scoped to one account and carries that account’s permissions. Keys are prefixed rk_live_ so one can be spotted in a log or a commit and revoked.

Treat a key like a password. It can generate articles, which spends your monthly allowance, and it can publish to your live site. Keep it in an environment variable, never in a repository, and revoke it the moment it appears in one.

Base URL#

https://www.rankli.io/api/v1

Every path below is relative to that base — GET /products means GET https://www.rankli.io/api/v1/products. Requests and responses are JSON; send Content-Type: application/json on anything with a body.

Authentication#

Send the key as a bearer token on every request.

Authorization: Bearer rk_live_your_api_key_here

A missing, malformed, expired or revoked key answers 401. A key belonging to a different account than the resource answers 404 rather than 403, because confirming that an id exists in somebody else’s account is itself a leak.

Quickstart#

Confirm the key works before writing anything against it:

curl https://www.rankli.io/api/v1/auth/whoami \
  -H "Authorization: Bearer rk_live_your_api_key_here"
Response
{
  "user":    { "id": "usr_...", "email": "you@example.com" },
  "account": { "id": "acc_...", "name": "Acme", "plan": "prove", "product_count": 2 },
  "credits": { "articles_remaining": 17, "period_ends_at": "2026-10-01T00:00:00Z" }
}

Rate limits#

WindowLimitScope
Per minute120 requestsper API key
Per day10,000 requestsper API key

Every response carries the current state of both windows:

X-RateLimit-Limit:     120
X-RateLimit-Remaining: 117
X-RateLimit-Reset:     1789203600

Over the limit you get 429 with a Retry-After header in seconds. Honour it — retrying inside the window costs a request from the next one.

Article generation is bounded by your plan’s monthly allowance, not by the rate limit. A POST /articles/generate that would exceed it answers 402 with the remaining count in the body.

Endpoints#

Auth

GET/auth/whoami

The user and account this key belongs to, the plan, and the article credits left this period. The cheapest call to make in a health check.

Products

A product is one website: its profile, its keywords, its calendar and its integrations.

GET/products

Every product on the account, with its domain, plan slot and pause state.

POST/products

Create one from a domain. Body: { "domain": "acme.com", "name": "Acme" }. Creating a product reads the site, researches keywords and queues a 30-day plan — the same three stages onboarding runs.

Keywords

GET/keywords?product_id=...

The keyword rows for a product: phrase, intent, cluster, scheduled date, and volume and difficulty when a data provider is configured. Without one those two fields are null — never 0. See nulls below.

POST/keywords/generate

Research more phrases for a product. Body: { "product_id": "...", "count": 40 }.

POST/keywords/bulk-reschedule

Move a set of keywords onto new dates. Body: { "ids": [...], "start_date": "2026-10-01" }.

POST/keywords/bulk-delete

Remove keywords that have not been written yet. Body: { "ids": [...] }.

Articles

GET/articles?product_id=...&status=...

Article rows: id, title, slug, keyword, status, scheduled date, publish state and the check score. Paginated with limit and cursor.

GET/articles/{id}

One article with its metadata, its SEO report and its publish attempts.

GET/articles/{id}/content?format=markdown

The body itself. format is markdown or html; markdown is the default.

POST/articles/precreate

Write an article scheduled for a future date, now, so you can read it before it publishes. Body: { "keyword_id": "..." }. Spends one from the allowance.

POST/articles/generate

Generate the article for a keyword due today. Body: { "keyword_id": "..." }.

POST/articles/{id}/retry-publish

Re-run a publish that failed. Publishing is idempotent: a retry updates the post it already created rather than making a second one.

Subscription and usage

GET/subscription/status

Plan, period, seats and whether the subscription is active.

GET/usage/stats

Articles produced this period, allowance, and spend on model calls.

Nulls are not zeros#

volume, difficulty and cpc are null unless a paid search-data account is configured on your account. They are never filled with a placeholder.

This matters more through the API than in the UI, because your code will do arithmetic on these. A 0 where a measurement is missing looks like a real reading of “nobody searches for this” and quietly demotes the keyword out of your plan. Check for null before you sort.

const ordered = keywords
  .filter((k) => k.volume !== null)      // not `k.volume > 0`
  .sort((a, b) => b.volume - a.volume);

Errors#

{
  "error": {
    "code": "keyword_not_due",
    "message": "That keyword is scheduled for 2026-10-14. Use /articles/precreate to write it now.",
    "field": "keyword_id"
  }
}
StatusMeans
400The body or the query string is wrong. `field` names the offending one.
401Missing, malformed, expired or revoked key.
402The action would exceed the plan’s article allowance.
404No such resource — or it belongs to another account.
409The resource is in a state that forbids this (already publishing, already deleted).
429Rate limited. Honour `Retry-After`.
5xxOurs. Safe to retry idempotent calls with backoff.

Driving it from an AI agent#

The API is designed to be driven by a coding agent as much as by a script — every response is JSON, every error says what to do next, and the CLI wraps the whole surface with a --json flag.

  1. Install the CLI: npm install -g rankli-cli
  2. Export the key: export RANKLI_API_KEY=rk_live_…
  3. Point your agent at it. rankli-cli --help lists every command, and each one mirrors an endpoint above.

The CLI reference has the full command list.