{
  "markdown": "# AltoRank\n\n**An AI SEO content engine where every publish is somebody's decision.**\n\nIt researches a keyword, writes the article, scores it, checks its claims, and\npublishes it to your CMS. Who decides that it ships is yours to choose per\nworkspace: approve each draft by hand, or set a rule that publishes after a hold\nunless you hold it. Either way the approval is recorded under a named person,\nand the article is tracked after - indexing, rank, AI-search visibility.\n\nOpen source, the whole product. No feature-gated tier, no `ee/` directory.\n\n---\n\n## Status: pre-launch\n\nRead this before you invest time in it.\n\n- **No paying customers, no case studies.** Nothing here has a growth figure\n  attached to it, and that is on purpose.\n- **The CLI is publishable, not yet on npm.** `packages/altorank-cli` bundles\n  `apps/web/scripts/cli.ts` into an `altorank` binary (`npx altorank auth\n  whoami` once it is published); until then, `npm run cli` from `apps/web`\n  drives the same agent API from a checkout. `skills/altorank/SKILL.md` is the\n  skill file a coding agent reads, and it does install with `npx`; see below.\n- **The hosted dashboard is what runs today.** It works locally against your own\n  Supabase and your own API keys.\n\nIf you want a finished product, wait. If you want to read how it works or run it\nyourself, everything is here.\n\n## The publishing decision\n\nOne gate, two ways through it. `lib/publishing/core.ts` refuses any article\nthat is not `approved` (or `scheduled` with `approved_by` set), and nothing else\nin the code path writes `live`. What can write an approval:\n\n- **A person**, from the editor (`approveArticle`), recorded as `approved_by`.\n- **A rule the workspace owner set** (`auto_approve`, migration 079): the\n  publish cron runs the same checks the Approve button runs - active plan, no\n  unsourced figure, no failing audit item, SEO score at or above the floor -\n  after a hold window (default 24h) during which the drafted email carries a\n  one-click Hold. `approved_by` is whoever turned the rule on; `approval_kind`\n  says `auto`. Held drafts carry the reason on their own row. See\n  `lib/publishing/auto-approve.ts`.\n\nWhat cannot write an approval: an agent. The **MCP server and the agent API\nexpose no publish or approve tool** - not disabled, absent. See the comment at\nthe top of `apps/web/scripts/mcp.ts`.\n\nCompeting tools ship the words \"you stay in control\" as copy. The difference\nis checkable here: every `live` row points at a person, by click or by rule.\n\n## What works today\n\n| | |\n|---|---|\n| Keyword research + SERP analysis | DataForSEO |\n| Domain audit | 9 readiness checks, crawl, PageSpeed |\n| Article generation | research → draft → score → fact-check |\n| Brand voice | per-workspace voice profiles |\n| Publishing | **13 destinations** (below) |\n| Locales | **35** (`apps/web/lib/seo/locales.ts`) |\n| Rank tracking | scheduled SERP checks |\n| Search analytics | Google Search Console; Bing Webmaster Tools (clicks and impressions per day) |\n| AI visibility | whether AI answers name you, and who they name instead |\n\n**Publishing destinations** (`apps/web/lib/cms/adapter.ts`) — thirteen adapters\ncovering **ten CMS platforms**; WordPress is reachable two ways, and `git` and\n`webhook` are publishing targets rather than CMSs. Framer, Ghost, git, HubSpot, Magento,\nNotion, Shopify, Webflow, webhook, Wix, WooCommerce, WordPress, and the\nWordPress plugin — a second, recommended route to WordPress that installs a\nplugin and takes a per-site token instead of an application password\n(`apps/web/lib/cms/wordpress-plugin.ts`), which is why WordPress appears twice.\n\n## Install the skill\n\n```bash\nnpx skills add AltoRank/altorank\n```\n\nThat puts `skills/altorank/SKILL.md` where Claude Code, Cursor, Codex, Copilot,\nWindsurf, Gemini CLI, Cline and the other agents the\n[skills CLI](https://github.com/vercel-labs/skills) supports will read it. The\nskill teaches an agent to drive the agent API (`/api/agent/v1`): preflight the\nkey, check a site's readiness, list or suggest keywords, generate a draft into\na human's review queue, edit a draft by find-and-replace, move the content\nplan. It never publishes, approves or deletes; the API has no such calls.\n\nIt needs an API key, created at `/settings/api-keys` in the dashboard and\nexported as `ALTORANK_API_KEY`. Self-hosted installs also set\n`ALTORANK_BASE_URL`. `skills/altorank/commands/` holds five thin slash\ncommands for the common calls; the skills CLI does not register slash commands,\nso copy them into `.claude/commands/` (Claude Code) or your agent's equivalent\nif you want them.\n\n## Running it\n\nRequires Node 22+, and a Supabase project (local via Docker, or hosted).\nThe full deployment story, hosted and self-hosted, is in\n[docs/deploy.md](docs/deploy.md); the Cloudflare assessment is in\n[docs/deploy-cloudflare.md](docs/deploy-cloudflare.md).\n\n```bash\nnpm install\ncp docker/.env.example apps/web/.env.local   # every variable, with what each one enables\nnpm run dev\n```\n\nFour things are genuinely required before it will run: your Supabase URL and\nits two keys, an `ANTHROPIC_API_KEY`, and an `ENCRYPTION_KEY`\n(`openssl rand -hex 32`). Add `DATAFORSEO_LOGIN` / `DATAFORSEO_PASSWORD` for\nkeyword volume and rank tracking; without them those steps skip rather than\nfail. Everything else in the example file is optional and each one disables\nexactly one feature. The file says which.\n\nOther entry points:\n\n```bash\nnpm run mcp      # MCP server, for driving the engine from an AI assistant\nnpm run test     # vitest\nnpm run smoke    # research → prompt → model → fact check, against real APIs\n```\n\nand from `apps/web`:\n\n```bash\nnpm run cli -- --help        # the agent API from a shell; auth with ALTORANK_API_KEY\nnpm run readiness -- <domain>  # the agent-readiness checks on their own\n```\n\n### Hosted MCP endpoint\n\nThe same tools are served at `/api/mcp` (Streamable HTTP) by the running app,\nso an MCP client adds one URL as a connector instead of running the stdio\nserver: `https://app.altorank.co/api/mcp` on the hosted install, or your own\norigin when you self-host. Authentication is a bearer token, either an API key\nfrom `/settings/api-keys` or the token the built-in OAuth flow issues:\n\n- discovery at `/.well-known/oauth-authorization-server` and\n  `/.well-known/oauth-protected-resource/api/mcp`\n- dynamic client registration at `/api/oauth/register` (public clients, PKCE\n  S256 only)\n- consent at `/oauth/authorize`, owner or admin only, write scope opt-in\n- token at `/api/oauth/token`, authorization-code grant, no refresh token\n\nThe token a connector receives is an `api_keys` row (migration 080), so it is\nlisted, expires and is revoked on `/settings/api-keys` like any other key.\nClaude Code registers it with\n`claude mcp add --transport http altorank https://app.altorank.co/api/mcp`;\nChatGPT and Claude.ai take the same URL as a custom connector.\n\nNeither the CLI nor the MCP server can approve an article or delete anything —\nnot by configuration, but because the agent API has no such call and no `DELETE`\nhandler at all. Nor can either of them publish: the single endpoint that reaches\na CMS is `POST /articles/{id}/retry-publish`, which re-runs a publish that\n**a human already approved** and that then failed. An article in draft or review\nis refused, and the refusal tells the caller to hand it to a person\n(`apps/web/app/api/agent/v1/articles/[id]/retry-publish/route.ts:13-35`).\n\n### Database\n\n`apps/web/supabase/` carries a `config.toml` and the numbered migrations, which\napply in order. With the [Supabase CLI](https://supabase.com/docs/guides/cli):\n\n```bash\ncd apps/web\nsupabase start      # local Postgres + auth, prints the URL and keys for .env.local\nsupabase db push    # applies the migrations\n```\n\nAgainst a hosted project, `supabase link --project-ref <ref>` first, then\n`supabase db push`. The app will not get far without the migrations: every\nquery targets tables they create.\n\n`docker/` has a container setup if you would rather not run Node directly.\n\n## Layout\n\n```\napps/web/               the engine and dashboard (Next.js)\n  app/(dashboard)/      dashboard routes\n  lib/audit/            domain analysis, readiness checks\n  lib/seo/              recommendations, scoring, locales, topical profile\n  lib/content/          generation (one implementation, shared by route + cron)\n  lib/ai/               fact checking\n  lib/cms/              13 publishing adapters\n  lib/geo/              AI-answer visibility\n  scripts/mcp.ts        MCP server\n  scripts/cli.ts        CLI over /api/agent/v1\nskills/altorank/        the skill file a coding agent reads (`npx skills add AltoRank/altorank`)\npackages/altorank-cli/  npm package `altorank`: bundles scripts/cli.ts into a bin\ndocker/                 container setup for self-hosting\ntools/agent-readiness/  standalone agent-readiness scanner\n```\n\nThe marketing site (altorank.co) lives in its own private repository,\n`AltoRank/altorank-marketing`. It moved out of this tree on 2026-09-06 so the\nproduct stays open while positioning and pricing copy do not. Nothing here\nimports it; the two are coupled only by the plan limits noted in\n`apps/web/lib/stripe.ts` and `apps/web/lib/billing/quota.ts`, which must be\nchanged in both places together.\n\nTwo conventions worth knowing before you send a patch:\n\n1. **Never render an unknown as zero.** A number nobody measured, displayed as a\n   measurement, is a fabricated claim even when no human typed it. Use\n   `number | null`, render an em dash, and average only over rows that have a\n   value.\n2. **One implementation per behaviour.** Generation lives in\n   `lib/content/generate.ts`; the streaming route and the cron both call it.\n   Resist the second copy.\n\n## Licence\n\n**GNU AGPL-3.0-only.** Full text in [LICENSE](LICENSE).\n\nIn practice: self-host it, run it for your own clients, commercially, and you\nowe nothing and publish nothing. The one obligation only reaches people\nrebuilding the product. If you modify the source and offer that modified version\nto others over a network, your changes have to be shared back under the same\nlicence.\n\nRunning an agency on it is free and unencumbered. Relaunching a modified copy as\na closed competing service is not.\n\n## Contributing\n\nIssues and pull requests are welcome. Two things that will get a patch rejected\nregardless of how good the code is:\n\n- Adding a way to publish that is not attributable to a person - a click, or a\n  rule a named member set and can veto. An agent-triggered publish is the\n  canonical example.\n- Adding a claim the repository cannot support. If `grep` cannot find the\n  feature, the README does not get to mention it.\n",
  "bytes": 10652,
  "sha": "b4436c7d7842f8875cce73d58254a2d0d81fd28afdf61b5cc82c93a943f66fab",
  "repo_slug": "altorank/altorank",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_co_altorank_altorank_c7bd1ec2/readme"
}