AltoRank: open-source, self-hosted AI SEO content writer (Outrank alternative)
An open-source alternative to Outrank and Jasper. AltoRank researches akeyword, writes the article, scores it and fact-checks it: a figure that citesa page is looked for on that page, and one with no source is flagged for thereviewer. Every draft can be copied as Markdown (front matter included) orHTML, or downloaded as a .md file, for you to paste wherever you publish.Approve it, paste it, then Mark as published records the live URL, and thearticle is tracked from there: indexing and rank.
AltoRank never publishes to your site without an approval recorded under anamed person. Approve each draft by hand, or set a rule that approves draftsafter a hold unless you hold them (the rule publishes through a CMS connection,which is built but not yet verified end to end; see Status).Agents can research and draft through the MCP server and the agent API.Neither can approve, and neither can publish anything a person has not alreadyapproved (the one publish-side call retries a failed publish of an approvedarticle).
Self-host it free under AGPL-3.0, or use the hosted app at app.altorank.co.Open source, the whole product. No feature-gated tier, no ee/ directory.
Status
The hosted app at app.altorank.co is this repository, deployed from main(docs/deploy.md). A self-hosted install runs the same codeagainst your own Supabase and your own API keys. Research, drafting, scoring,fact-checking, audits, rank tracking and Search Console / Bing sync run today;the table below has the detail. What is not finished yet:
- No paying customers, no case studies. Nothing here has a growth figureattached to it, and that is on purpose.
- CMS publishing is built but not yet verified end to end. The thirteenadapters are in
apps/web/lib/cms/and the connection form covers all ofthem, but none has been watched publishing to a live site yet, so theplatform tiles on/connectask you to request an integration instead ofoffering Connect. Every draft can be copied as Markdown or HTML today.Details under What works today. - AI visibility tracking is built but not switched on. The probe, theweekly cron and the
/geopage exist, but nothing in the app turns trackingon for a workspace or adds the prompts it measures, so the sidebar marks it"soon" (apps/web/lib/constants.ts). - The CLI is publishable, not yet on npm.
packages/altorank-clibundlesapps/web/scripts/cli.tsinto analtorankbinary (npx altorank auth whoamionce it is published); until then,npm run clifromapps/webdrives the same agent API from a checkout.skills/altorank/SKILL.mdis theskill file a coding agent reads, and it does install withnpx; see below.
Whether you want to read how it works, run it yourself or send a patch,everything is here.
The publishing decision
One gate, two ways through it. lib/publishing/core.ts refuses any articlethat is not approved or scheduled with approved_by set. Only the serverwrites those columns: a signed-in user's own database client cannot make anarticle approved, scheduled or live (migration 109), and an edit to approvedtext sends it back to review. What can write an approval:
- A person, from the editor (
approveArticle), recorded asapproved_by.The button checks for an active plan and refuses a bare, unsourced figure. - A rule a named member of the account set (
auto_approve, migration 079): thepublish cron runs stricter checks than the Approve button - active plan,somewhere to publish, no unsourced figure, no named reviewer still to signoff, no failing audit item, SEO score at or above the floor - after a holdwindow (default 24h) during which the drafted email carries a Hold link.approved_byis whoever turned the rule on;approval_kindsaysauto.Held drafts carry the reason on their own row. Seelib/publishing/auto-approve.ts.
live is written after an approved publish, by Mark as published on anapproved article, and by one thing that is not a publish: the nightlyfound-on-site check (lib/found-on-site/detect.ts) marks a draft live whenits text is already on the customer's own site. That records something doneoutside AltoRank, sends nothing to a CMS, carries no approval, and can beundone from the editor.
What cannot write an approval: an agent. The MCP server and the agent APIexpose no publish or approve tool - not disabled, absent. The MCP tools areall registered in apps/web/lib/mcp/server.ts, and its server instructionssay so to the agent.
Competing tools ship the words "you stay in control" as copy. The differenceis checkable here: every live row is an approved article (by click or byrule) or one found already published on the customer's own site.
What works today
| Keyword research + SERP analysis | DataForSEO |
| Domain audit | 9 readiness checks, crawl, PageSpeed |
| Article generation | research → draft → score → fact-check |
| Brand voice | per-workspace voice profiles |
| Publishing | Markdown and HTML export of every draft; 13 adapters (below), built but not yet verified end to end |
| Locales | 35 (apps/web/lib/seo/locales.ts) |
| Rank tracking | scheduled SERP checks |
| Search analytics | Google Search Console; Bing Webmaster Tools (clicks and impressions per day) |
| AI visibility | not switched on yet: built (lib/geo/, the weekly /api/cron/geo, the /geo page) to record whether AI answers name you and who they name instead, but nothing in the app turns it on for a workspace yet |
Publishing destinations (apps/web/lib/cms/adapter.ts) — thirteen adapterscovering ten CMS platforms; WordPress is reachable two ways, and git andwebhook are publishing targets rather than CMSs. Framer, Ghost, git, HubSpot, Magento,Notion, Shopify, Webflow, webhook, Wix, WooCommerce, WordPress, and theWordPress plugin — a second, recommended route to WordPress that installs aplugin and takes a per-site token instead of an application password(apps/web/lib/cms/wordpress-plugin.ts), which is why WordPress appears twice.
None of the thirteen has yet been verified end to end against a live site, soCONNECTABLE_CMS in apps/web/lib/cms/connectable.ts is empty on purpose andevery CMS tile on /connect offers "Request integration" instead of Connect;adding an id to that set brings that tile's Connect button back. Theconnection form itself is unchanged and still covers all thirteen: thepage's "New connection" button, a draft's publish panel and/connect?connect=<id> all open it. Without a connection, the editor copiesany draft as Markdown (front matter included) or HTML, or downloads it as a.md file.
Install the plugin
Claude Code. From a terminal:
claude plugin marketplace add AltoRank/altorank
claude plugin install altorank@altorank
Or inside a Claude Code session: /plugin marketplace add AltoRank/altorank,then /plugin install altorank@altorank.
plugins/altorank/ bundles the hosted MCP server (https://app.altorank.co/api/mcp,OAuth, no API key to copy) with a skill written for those tools. The same foldercarries the portable plugin.json + mcp.json that ChatGPT and Codex read, and.agents/plugins/marketplace.json lists it for them.
Install the skill
npx skills add AltoRank/altorank
That puts skills/altorank/SKILL.md where Claude Code, Cursor, Codex, Copilot,Windsurf, Gemini CLI, Cline and the other agents theskills CLI supports will read it. Theskill teaches an agent to drive the agent API (/api/agent/v1): preflight thekey, check a site's readiness, list or suggest keywords, generate a draft intoa human's review queue, edit a draft by find-and-replace, move the contentplan. It never publishes, approves or deletes; the API has no such calls.
It needs an API key, created at /settings/api-keys in the dashboard andexported as ALTORANK_API_KEY. Self-hosted installs also setALTORANK_BASE_URL. skills/altorank/commands/ holds five thin slashcommands for the common calls; the skills CLI does not register slash commands,so copy them into .claude/commands/ (Claude Code) or your agent's equivalentif you want them.
Running it
Requires Node 22+, and a Supabase project (local via Docker, or hosted).The full deployment story, hosted and self-hosted, is indocs/deploy.md; the Cloudflare assessment is indocs/deploy-cloudflare.md.
npm install
cp docker/.env.example apps/web/.env.local # every variable, with what each one enables
npm run dev
Required before a self-hosted install works end to end:
- your Supabase URL, its anon key and its
SUPABASE_SERVICE_ROLE_KEY ANTHROPIC_API_KEYENCRYPTION_KEYandCRON_SECRET(openssl rand -hex 32for each).docker composerefuses to start withoutCRON_SECRET, and without it noscheduled job runs.RESEND_API_KEYandRESEND_FROM_EMAIL(a sender on a domain verified inyour Resend account). Signup confirms by email, so without them nobody cancreate an account and nobody can reset a password.
Add DATAFORSEO_LOGIN / DATAFORSEO_PASSWORD for keyword volume and ranktracking; without them those steps skip rather than fail. Everything else inthe example file is optional and each one disables exactly one feature. Thefile says which. Product analytics (the two NEXT_PUBLIC_POSTHOG_* variables,project token and host) are optional too: without them the browser consolesays analytics are off and nothing else changes.
Other entry points:
npm run test # vitest, unit tier: mocks only, needs no services
npm run test:db # vitest, db tier: *.db.test.ts against the local Supabase only
npm run smoke # research → prompt → model → fact check, against real APIs
and from apps/web:
npm run mcp # MCP server over stdio, for driving the engine from an AI assistant
npm run cli -- --help # the agent API from a shell; auth with ALTORANK_API_KEY
npm run readiness -- <domain> # the agent-readiness checks on their own
Hosted MCP endpoint
The same tools are served at /api/mcp (Streamable HTTP) by the running app,so an MCP client adds one URL as a connector instead of running the stdioserver: https://app.altorank.co/api/mcp on the hosted install, or your ownorigin when you self-host. Authentication is a bearer token, either an API keyfrom /settings/api-keys or the token the built-in OAuth flow issues:
- discovery at
/.well-known/oauth-authorization-serverand/.well-known/oauth-protected-resource/api/mcp - dynamic client registration at
/api/oauth/register(public clients, PKCES256 only) - consent at
/oauth/authorize, owner or admin only, write scope opt-in - token at
/api/oauth/token, authorization-code grant, no refresh token
The token a connector receives is an api_keys row (migration 080), so it islisted, expires and is revoked on /settings/api-keys like any other key.Claude Code registers it withclaude mcp add --transport http altorank https://app.altorank.co/api/mcp;ChatGPT and Claude.ai take the same URL as a custom connector.
Neither the CLI nor the MCP server can approve an article or delete anything —not by configuration, but because the agent API has no such call and no DELETEhandler at all. Nor can either of them publish: the single endpoint that reachesa CMS is POST /articles/{id}/retry-publish, which re-runs a publish thata human already approved and that then failed. An article in draft or reviewis refused, and the refusal tells the caller to hand it to a person(apps/web/app/api/agent/v1/articles/[id]/retry-publish/route.ts:13-35).
Database
apps/web/supabase/ carries a config.toml and the numbered migrations, whichapply in order. With the Supabase CLI:
cd apps/web
supabase start # local Postgres + auth; applies the migrations, prints the URL and keys for .env.local
supabase start applies every migration to the local database on firststart (supabase db reset re-applies them from scratch). Do not runsupabase db push for a local stack: it targets the linked remote project.Against a hosted project, supabase link --project-ref <ref> first, thensupabase db push. The app will not get far without the migrations: everyquery targets tables they create.
docker/ has a container setup if you would rather not run Node directly.
Layout
apps/web/ the engine and dashboard (Next.js)
app/(dashboard)/ dashboard routes
lib/audit/ domain analysis, readiness checks
lib/seo/ recommendations, scoring, locales, topical profile
lib/content/ generation (one implementation, shared by route + cron)
lib/ai/ fact checking
lib/cms/ 13 publishing adapters
lib/geo/ AI-answer visibility
scripts/mcp.ts MCP server
scripts/cli.ts CLI over /api/agent/v1
plugins/altorank/ Claude / ChatGPT / Codex plugin: hosted MCP server + skill
skills/altorank/ the skill file a coding agent reads (`npx skills add AltoRank/altorank`)
packages/altorank-cli/ npm package `altorank`: bundles scripts/cli.ts into a bin
docker/ container setup for self-hosting
tools/agent-readiness/ standalone agent-readiness scanner
Licence
GNU AGPL-3.0-only. Full text in LICENSE.
In practice: self-host it, run it for your own clients, commercially, and youowe nothing and publish nothing. The one obligation only reaches peoplerebuilding the product. If you modify the source and offer that modified versionto others over a network, your changes have to be shared back under the samelicence.
Running an account on it is free and unencumbered. Relaunching a modified copy asa closed competing service is not.
Contributing
Issues and pull requests are welcome. Two things that will get a patch rejectedregardless of how good the code is:
- Adding a way to publish that is not attributable to a person - a click, or arule a named member set and can veto. An agent-triggered publish is thecanonical example.
- Adding a claim the repository cannot support. If
grepcannot find thefeature, the README does not get to mention it.
The code conventions a patch is read against, and how this repository relatesto the marketing site, are in CONTRIBUTING.md.