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://rankbotic.vercel.app/api/v1Every path below is relative to that base — GET /products means GET https://rankbotic.vercel.app/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_hereA 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://rankbotic.vercel.app/api/v1/auth/whoami \
-H "Authorization: Bearer rk_live_your_api_key_here"{
"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#
| Window | Limit | Scope |
|---|---|---|
| Per minute | 120 requests | per API key |
| Per day | 10,000 requests | per API key |
Every response carries the current state of both windows:
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 117
X-RateLimit-Reset: 1789203600Over 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"
}
}| Status | Means |
|---|---|
| 400 | The body or the query string is wrong. `field` names the offending one. |
| 401 | Missing, malformed, expired or revoked key. |
| 402 | The action would exceed the plan’s article allowance. |
| 404 | No such resource — or it belongs to another account. |
| 409 | The resource is in a state that forbids this (already publishing, already deleted). |
| 429 | Rate limited. Honour `Retry-After`. |
| 5xx | Ours. 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.
- Install the CLI:
npm install -g rankli-cli - Export the key:
export RANKLI_API_KEY=rk_live_… - Point your agent at it.
rankli-cli --helplists every command, and each one mirrors an endpoint above.
The CLI reference has the full command list.