Getting started
Quickstart
From a topic to a published WordPress article in four API calls: find your site, create a job, wait for the draft, publish it. Every step comes in cURL, Node.js, Python and PHP; pick a language once and every sample follows.
Before you begin
- An Autoranko account with a WordPress site connected through the plugin (see WordPress integration).
- For the last step, publishing permission on that site:
wp_connectedmust betrue. Without it you still get the full draft, you just can't publish it from the API. - An article left in the site's allowance: 3 in total on Free, 8 a month on Starter, 30 a month on Pro. A job uses one as soon as it's created; jobs that fail give it back.
| Language | What you need |
|---|---|
| cURL | Any shell with curl. Export your token first: export AUTORANKO_TOKEN=ar_live_…. |
| Node.js | Node.js 18 or later, with the built-in fetch and no packages. The samples use top-level await, so save them as .mjs files (or set "type": "module"). |
| Python | Python 3.8 or later with requests (pip install requests). |
| PHP | PHP 7.4 or later with the curl extension. |
Step 1: Create a token
In Dashboard → API, create a token and copy it; it's shown only once. Keep it in an environment variable.
Shellexport AUTORANKO_TOKEN="ar_live_…"Step 2: Find your site ID
Sites are the WordPress installs connected to your account. Note the
id, and checkwp_connected: it must betruefor publishing.curl https://autoranko.com/api/public/v1/sites \ -H "Authorization: Bearer $AUTORANKO_TOKEN"const res = await fetch("https://autoranko.com/api/public/v1/sites", { headers: { Authorization: `Bearer ${process.env.AUTORANKO_TOKEN}` }, }); const data = await res.json(); if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`); for (const site of data.sites) { console.log(site.id, site.domain, site.wp_connected ? "can publish" : "WordPress not connected"); }import os import requests res = requests.get( "https://autoranko.com/api/public/v1/sites", headers={"Authorization": f"Bearer {os.environ['AUTORANKO_TOKEN']}"}, timeout=30, ) data = res.json() if not res.ok: raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}") for site in data["sites"]: print(site["id"], site["domain"], "can publish" if site["wp_connected"] else "WordPress not connected")<?php $ch = curl_init('https://autoranko.com/api/public/v1/sites'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('AUTORANKO_TOKEN')], ]); $data = json_decode(curl_exec($ch), true); if (isset($data['error'])) throw new RuntimeException($data['error']['message']); foreach ($data['sites'] as $site) { $state = $site['wp_connected'] ? 'can publish' : 'WordPress not connected'; echo "{$site['id']} {$site['domain']} $state\n"; }Example response · 200 OK{ "sites": [ { "id": "1", "site_url": "https://demo.autoranko.com", "site_name": "Autoranko Demo Site", "domain": "demo.autoranko.com", "plan": "pro", "is_active": true, "wp_connected": false, "created_at": "2026-10-25T19:57:53.200Z" } ] }Step 3: Create a job
Send a topic and, optionally, a target keyword (replace
1with your site ID). The job is created inpendingand starts generating right away. Keep the returnedid.curl -X POST https://autoranko.com/api/public/v1/sites/1/jobs \ -H "Authorization: Bearer $AUTORANKO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "topic": "How to winterize a sprinkler system before the first freeze", "target_keyword": "winterize sprinkler system" }'const res = await fetch("https://autoranko.com/api/public/v1/sites/1/jobs", { method: "POST", headers: { Authorization: `Bearer ${process.env.AUTORANKO_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ topic: "How to winterize a sprinkler system before the first freeze", target_keyword: "winterize sprinkler system", auto_publish: false, }), }); const data = await res.json(); if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`); const { job } = data; console.log(`Job ${job.id} created (${job.status}); poll GET /jobs/${job.id} for the draft`);import os import requests res = requests.post( "https://autoranko.com/api/public/v1/sites/1/jobs", headers={"Authorization": f"Bearer {os.environ['AUTORANKO_TOKEN']}"}, json={ "topic": "How to winterize a sprinkler system before the first freeze", "target_keyword": "winterize sprinkler system", "auto_publish": False, }, timeout=30, ) data = res.json() if not res.ok: raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}") job = data["job"] print(f"Job {job['id']} created ({job['status']}); poll GET /jobs/{job['id']} for the draft")<?php $ch = curl_init('https://autoranko.com/api/public/v1/sites/1/jobs'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('AUTORANKO_TOKEN'), 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'topic' => 'How to winterize a sprinkler system before the first freeze', 'target_keyword' => 'winterize sprinkler system', 'auto_publish' => false, ]), ]); $data = json_decode(curl_exec($ch), true); if (isset($data['error'])) throw new RuntimeException($data['error']['message']); $job = $data['job']; echo "Job {$job['id']} created ({$job['status']}); poll GET /jobs/{$job['id']} for the draft\n";Example response · 201 Created{ "job": { "id": "3", "site_id": "1", "topic": "How to winterize a sprinkler system before the first freeze", "target_keyword": "winterize sprinkler system", "status": "pending", "article_title": null, "meta_description": null, "wp_post_id": null, "auto_publish": false, "scheduled_for": null, "created_at": "2026-10-25T20:10:51.035Z" } }There's no idempotency key. If a create request times out, check GET /sites/{id}/jobs before retrying, or you may generate the same article twice and use up allowance for it.Step 4: Wait for the draft
Generation takes a few minutes. Poll the job (replace
3with your job'sid) every 15–30 seconds untilstatusisreview(orfailed), or subscribe to the job.review webhook instead. The draft is inarticle_html.curl https://autoranko.com/api/public/v1/jobs/3 \ -H "Authorization: Bearer $AUTORANKO_TOKEN"const res = await fetch("https://autoranko.com/api/public/v1/jobs/3", { headers: { Authorization: `Bearer ${process.env.AUTORANKO_TOKEN}` }, }); const data = await res.json(); if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`); const { job } = data; console.log(job.status, job.article_title); if (job.status === "review") console.log(job.article_html.slice(0, 300)); if (job.error) console.error(job.error);import os import requests res = requests.get( "https://autoranko.com/api/public/v1/jobs/3", headers={"Authorization": f"Bearer {os.environ['AUTORANKO_TOKEN']}"}, timeout=30, ) data = res.json() if not res.ok: raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}") job = data["job"] print(job["status"], job["article_title"]) if job["status"] == "review": print(job["article_html"][:300]) if job["error"]: print("Error:", job["error"])<?php $ch = curl_init('https://autoranko.com/api/public/v1/jobs/3'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('AUTORANKO_TOKEN')], ]); $data = json_decode(curl_exec($ch), true); if (isset($data['error'])) throw new RuntimeException($data['error']['message']); $job = $data['job']; echo $job['status'], ' ', $job['article_title'], "\n"; if ($job['status'] === 'review') echo substr($job['article_html'], 0, 300), "\n"; if ($job['error']) echo 'Error: ', $job['error'], "\n";Example response · 200 OK{ "job": { "id": "3", "site_id": "1", "topic": "How to winterize a sprinkler system before the first freeze", "target_keyword": "winterize sprinkler system", "status": "review", "article_title": "How to Winterize a Sprinkler System Before the First Freeze", "meta_description": "Learn how to winterize a sprinkler system before the first freeze, with steps for shutting off water, draining lines and protecting equipment.", "wp_post_id": null, "auto_publish": false, "scheduled_for": null, "error": "Publishing failed: WordPress publishing is not authorized for this site.", "outline": { "title": "How to Winterize a Sprinkler System Before the First Freeze", "meta_description": "Learn how to winterize a sprinkler system before the first freeze, with steps for shutting off water, draining lines and protecting equipment.", "slug": "winterize-sprinkler-system", "target_keyword": "winterize sprinkler system", "secondary_keywords": [ "sprinkler blowout", "drain irrigation lines", "insulate backflow preventer" ], "sections": [ { "level": "h2", "heading": "Why winterizing your sprinkler system matters", "description": "How trapped water freezes, expands and cracks pipes, fittings and valves.", "word_count": 220 }, { "level": "h2", "heading": "Step 1: Shut off the water supply", "description": "Find the main shut-off valve and protect it from freezing.", "word_count": 180 } ], "faqs": [ { "question": "When should I winterize my sprinkler system?", "answer_brief": "Before the first hard freeze, once nights approach 32 °F (0 °C)." } ], "image_suggestions": [ { "description": "Homeowner blowing out sprinkler lines with an air compressor in autumn", "alt_text": "Blowing out sprinkler lines with an air compressor" } ], "google_image_queries": [ "sprinkler system blowout" ], "video_query": "how to winterize a sprinkler system" }, "article_html": "<h2>Why winterizing your sprinkler system matters</h2>\n<p>Water left inside an irrigation system can freeze and expand, cracking pipes, splitting fittings, or damaging valves. Exposed backflow preventers are especially vulnerable because they lack the protection that soil provides to buried components.</p>\n<!-- … shortened for the docs … -->", "created_at": "2026-10-25T20:10:51.035Z", "updated_at": "2026-10-25T20:42:05.140Z" } }Skip the polling with a webhook
Add an endpoint in Dashboard → API and Autoranko sends job.review the moment the draft is ready, so you don't need to poll at all. Set up webhooks.
job.review payload{ "event": "job.review", "created_at": "2026-10-25T20:13:48.902Z", "data": { "id": "3", "site_id": "1", "topic": "How to winterize a sprinkler system before the first freeze", "target_keyword": "winterize sprinkler system", "status": "review", "article_title": "How to Winterize a Sprinkler System Before the First Freeze", "meta_description": "Learn how to winterize a sprinkler system before the first freeze, with steps for shutting off water, draining lines and protecting equipment.", "wp_post_id": null } }Step 5: Publish it
Approving the job publishes it to WordPress as a live post. The job moves to
publishing, then topublishedwith the newwp_post_id, and job.published fires.curl -X POST https://autoranko.com/api/public/v1/jobs/3/publish \ -H "Authorization: Bearer $AUTORANKO_TOKEN"const res = await fetch("https://autoranko.com/api/public/v1/jobs/3/publish", { method: "POST", headers: { Authorization: `Bearer ${process.env.AUTORANKO_TOKEN}` }, }); const data = await res.json(); if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`); console.log(data.status); // "publishing"; job.published fires once the post is liveimport os import requests res = requests.post( "https://autoranko.com/api/public/v1/jobs/3/publish", headers={"Authorization": f"Bearer {os.environ['AUTORANKO_TOKEN']}"}, timeout=30, ) data = res.json() if not res.ok: raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}") print(data["status"]) # "publishing"; job.published fires once the post is live<?php $ch = curl_init('https://autoranko.com/api/public/v1/jobs/3/publish'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('AUTORANKO_TOKEN')], CURLOPT_POSTFIELDS => '', ]); $data = json_decode(curl_exec($ch), true); if (isset($data['error'])) throw new RuntimeException($data['error']['message']); echo $data['status'], "\n"; // "publishing"; job.published fires once the post is liveExample response · 200 OK{ "ok": true, "status": "publishing" }Confirm it's live
Poll GET /jobs/{id} untilstatusispublished;wp_post_idis then the new WordPress post (or wait for job.published, which also carries the post URL). If WordPress rejected the post, the job is back inreviewanderrorsays why: fix it and publish again.
The whole flow in one script
The same steps as one program: create the job, poll every 15 seconds until the draft is ready, then publish it. Replace 1 with your site ID.
const API = "https://autoranko.com/api/public/v1";
const headers = {
Authorization: `Bearer ${process.env.AUTORANKO_TOKEN}`,
"Content-Type": "application/json",
};
async function call(method, path, body) {
const res = await fetch(API + path, { method, headers, body: body && JSON.stringify(body) });
const data = await res.json();
if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
return data;
}
// 1. Create the job (site 1; find yours with GET /sites)
let { job } = await call("POST", "/sites/1/jobs", {
topic: "How to winterize a sprinkler system before the first freeze",
target_keyword: "winterize sprinkler system",
});
// 2. Wait for the draft
while (job.status === "pending" || job.status === "generating") {
await new Promise((resolve) => setTimeout(resolve, 15_000));
({ job } = await call("GET", `/jobs/${job.id}`));
}
if (job.status !== "review") throw new Error(`Job ${job.id} is ${job.status}: ${job.error}`);
// 3. Approve: publish to WordPress
await call("POST", `/jobs/${job.id}/publish`);
console.log(`Publishing job ${job.id}: ${job.article_title}`);import os
import time
import requests
API = "https://autoranko.com/api/public/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['AUTORANKO_TOKEN']}"
def call(method, path, body=None):
res = session.request(method, API + path, json=body, timeout=30)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
return data
# 1. Create the job (site 1; find yours with GET /sites)
job = call("POST", "/sites/1/jobs", {
"topic": "How to winterize a sprinkler system before the first freeze",
"target_keyword": "winterize sprinkler system",
})["job"]
# 2. Wait for the draft
while job["status"] in ("pending", "generating"):
time.sleep(15)
job = call("GET", f"/jobs/{job['id']}")["job"]
if job["status"] != "review":
raise RuntimeError(f"Job {job['id']} is {job['status']}: {job.get('error')}")
# 3. Approve: publish to WordPress
call("POST", f"/jobs/{job['id']}/publish")
print(f"Publishing job {job['id']}: {job['article_title']}")<?php
function autoranko(string $method, string $path, ?array $body = null): array
{
$ch = curl_init('https://autoranko.com/api/public/v1' . $path);
$options = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('AUTORANKO_TOKEN'),
'Content-Type: application/json',
],
];
if ($method !== 'GET') {
$options[CURLOPT_POSTFIELDS] = $body === null ? '' : json_encode($body);
}
curl_setopt_array($ch, $options);
$data = json_decode(curl_exec($ch), true);
if (isset($data['error'])) throw new RuntimeException($data['error']['message']);
return $data;
}
// 1. Create the job (site 1; find yours with GET /sites)
$job = autoranko('POST', '/sites/1/jobs', [
'topic' => 'How to winterize a sprinkler system before the first freeze',
'target_keyword' => 'winterize sprinkler system',
])['job'];
// 2. Wait for the draft
while (in_array($job['status'], ['pending', 'generating'], true)) {
sleep(15);
$job = autoranko('GET', "/jobs/{$job['id']}")['job'];
}
if ($job['status'] !== 'review') {
throw new RuntimeException("Job {$job['id']} is {$job['status']}: {$job['error']}");
}
// 3. Approve: publish to WordPress
autoranko('POST', "/jobs/{$job['id']}/publish");
echo "Publishing job {$job['id']}: {$job['article_title']}\n";Generation usually takes a few minutes. If the job ends in failed the script stops with the reason, and that job doesn't count toward the allowance.
Job lifecycle
Every job follows one of these routes. auto_publish decides whether it stops in review for you or goes straight to WordPress; the webhook badges show which event fires as the job enters a status.
Review first
auto_publishoff (the default). The draft waits inreviewuntil you call POST /jobs/{id}/publish.- pending
- generating
- reviewfires job.review
- publishing
- publishedfires job.published
Autopilot
auto_publishon and WordPress connected (wp_connected: true). Without the connection the job stops inreview.- pending
- generating
- publishing
- publishedfires job.published
Generation fails
errorsays why. Failed jobs don't count toward the allowance; create a new job to try again.- pending
- generating
- failedfires job.failed
WordPress rejects the post
The job returns to
reviewwitherrorset (Publishing failed: …). Fix the cause, then publish again.- publishing
- reviewfires job.review
- publishing
Stuck job recovered
Still
generatingafter an hour (a server restart, say): reset topendingand retried automatically.- generating
- pending
- generating
| Status | Meaning | Moves to |
|---|---|---|
| pending | Created and waiting to start. Immediate jobs start within seconds; scheduled jobs start once scheduled_for has passed (the scheduler runs every minute). | generating |
| generating | Autoranko is writing the outline, then the full article. This usually takes a few minutes. A job stuck here for over an hour (after a server restart, for example) is reset to pending and retried automatically. | reviewpublishingfailedpending |
| reviewjob.review | The draft is ready and waiting for approval: auto_publish was off, the site has no WordPress connection, or a publish attempt failed (then error says why). Publish it with POST /jobs/{id}/publish. | publishing |
| publishing | The article is being sent to WordPress. If WordPress rejects it, the job goes back to review. | publishedreview |
| publishedjob.published | The article is live on WordPress, and wp_post_id holds the post ID. | Final |
| failedjob.failed | Generation failed, and error says why. Failed jobs don't count toward the plan's allowance; create a new job to try again. | Final |