REST API
Last updated
Everything your account stores is readable over HTTPS with a key from your account, Keys tab: the same rw_ keys the MCP server takes, with the same scopes. The API returns stored data and adds to your own lists; it never spends on DataForSEO or credits. Runs that spend (the agent, Why, keyword research, the writer) live in the MCP server, where every answer carries its cost line.
Authentication and scopes
curl https://rankwhy.com/api/v1/me \
-H "Authorization: Bearer rw_your_key"
read covers every GET. research is needed for the POSTs below (they add to your watchlist, calendar and tasks). A key without the scope gets 403 with the scope named. A missing or revoked key gets 401 with a WWW-Authenticate: Bearer challenge.
Limits and pagination
Per account: 200 requests a minute, 5,000 an hour, 50,000 a day; over the limit the answer is 429 with Retry-After: 60. Lists take ?limit= (default 50, at most 200) and ?offset= and answer { data, meta: { limit, offset, total } }; single objects answer { data }; errors answer { error }.
Endpoints
| Method and path | Scope | Returns |
|---|---|---|
GET /api/v1/me | read | the key's name, scopes and the limits |
GET /api/v1/sites | read | every domain the account has data for: calendar site, watched keyword count, Search Console window |
GET /api/v1/jobs, GET /api/v1/jobs/:id | read | agent jobs (intent, patterns, pages, costs) |
GET /api/v1/watch | read | the memory digest: every watched keyword with its position now and before, the events and dates |
POST /api/v1/watch | research | adds { keyword, locationCode, languageCode, site? } to the watchlist |
GET /api/v1/calendar?month=YYYY-MM, GET /api/v1/calendar/entries/:id | read | planned and finished days; one entry carries the article (draft Markdown, lint, citations) or the refresh diff |
POST /api/v1/calendar/entries | research | plans { keyword, locationCode, languageCode, site, scheduledOn, kind?, url? } |
GET /api/v1/guard?site= | read | Site Guard: pages short of their position's clicks, length findings, split queries with winner and redirects, your curve's source |
GET /api/v1/coverage?site= | read | every known keyword with ranking / drafted / planned / open and its cluster |
GET /api/v1/memory?site=&status=all | read | the facts the writer may state, each with its source page and quote |
GET /api/v1/search-console/imports | read | your imports with their windows |
GET /api/v1/tasks?status=open|done|all | read | the task list with evidence |
POST /api/v1/tasks, POST /api/v1/tasks/:id/status | research | adds tasks ({ title, detail?, site?, link? } or { tasks: [...] }), marks one open or done |
A client in twenty lines
const BASE = 'https://rankwhy.com/api/v1';
const KEY = process.env.RANKWHY_KEY;
async function rankwhy(path, init = {}) {
const res = await fetch(BASE + path, { ...init, headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json', ...(init.headers || {}) } });
if (res.status === 429) { await new Promise(r => setTimeout(r, 60_000)); return rankwhy(path, init); }
const body = await res.json();
if (!res.ok) throw new Error(body.error);
return body;
}
const { data: sites } = await rankwhy('/sites');
const { data: guard } = await rankwhy('/guard?site=' + encodeURIComponent(sites[0].site));
for (const f of guard.findings) console.log(f.page, f.kind, f.evidence);
await rankwhy('/tasks', { method: 'POST', body: JSON.stringify({ title: 'Fix the title on /pricing', detail: guard.findings[0]?.evidence, site: sites[0].site }) });
Webhooks: the calendar sends every finished article to your endpoint, signed; see publishing.