Outrank CLI
Agent-first CLI for the Outrank agent API, with a built-in MCP server for Claude Code, Cursor and other MCP clients.
Install
Requires Node.js 18+. Works on macOS, Linux, and Windows.
Get an API key
- Sign in to your Outrank account at https://www.outrank.so.
- Open Settings → API keys: https://www.outrank.so/dashboard/settings/api-keys.
- Click Create key, give it a name, and copy the
outr_live_... value. The key is shown only once — store it somewhere safe.
- Revoke a key from the same page if it ever leaks. Generate a fresh one and re-run
auth login.
You can also rotate keys at any time; each key is independent and can be revoked without affecting the others.
Current scope
This repo currently targets the agent endpoints that already exist in outrank:
mcp (the stdio MCP server that exposes the commands below as tools, see MCP server)
auth login|whoami|status|profiles|logout
products list|get|create|update|pause|resume
products integrations providers|prepare|connect|poll|validate|list|create|get|update|test|delete
products integrations notion databases|schema
products integrations framer collections|fields
products integrations webflow sites|collections|fields|items
products output-settings get|update for product article settings
products target-audiences list|replace
products competitors list|replace
products gsc status|connect|poll|reconnect|disconnect|accounts|properties|set-property|performance|search-analytics|inspect-url|cannibalization
products linking sources get|update
products linking detect
products linking target-config get|update
products linking targets list|add-manual|upsert|clear-customizations|delete
products backlinks get|update|credits|earned|unverified|check-removal|reverify|subscription|subscribe-quote
keywords list|get|export|generation-status|add|update|suggest|generate|bulk-reschedule|delete|bulk-delete
articles list|create|get|get-content|update|update-content|replace|bulk-replace|upload-image|upload-featured-image|publish|sync|retry-publish|delete|precreate|generate
products blog ... for a hosted Outrank Blog: posts list|create|get|update|delete|get-content|update-content|publish|unpublish|sync, design get|patch|update|publish|discard, settings, pages, categories, authors, domain, redirects, snippets, subscribers, webhooks, preview and analytics
improvements settings get|update
improvements analyze
improvements candidates list|brief|edit-brief|schedule
improvements tasks list|reschedule|cancel|edit-brief|execute
improvements executions list|get|decisions|push
marketplace seller status|onboard|eligibility|settings|integrations|stripe|complete-onboarding|placements|earnings|content-history
marketplace notifications list|mark-read
marketplace buyer status|billing-status|subscription|subscribe-quote|checkout|change-plan|target-pages|participation|placements|article|credits
ai-cited-lists status|overview|lists|outreach|offers|wins|questions|settings|mailbox|top-up-consent|entry|setup|citations for AI-Cited Lists (Backlinks Enterprise)
subscription status
usage stats
billing portal-url
billing purchase-products quote|buy
The broader roadmap from the internal implementation plan is larger than the live backend surface. This CLI is structured so new groups can be added cleanly as the server catches up.
Auth
The CLI reads credentials in this order of precedence:
--api-key <key> and --base-url <url> on the command line (highest priority, per-call)
- A profile selected with
--profile <name> or OUTRANK_PROFILE. A selected profile is used as-is: OUTRANK_API_KEY and OUTRANK_BASE_URL are ignored, and a profile without a stored key fails with CLI_AUTH_REQUIRED instead of silently using another key
OUTRANK_API_KEY and OUTRANK_BASE_URL environment variables
- The default profile in
~/.outrank/config.json, populated by outrank-cli auth login <api-key> (lowest priority, persistent)
auth status shows which profile is active and where its key and base URL came from (flag, env, profile, or profile (env ignored) when an environment variable was set but a higher-priority source won).
Pick one. For interactive use on your own machine, auth login is simplest. For CI / ephemeral environments, prefer the env var.
On Windows PowerShell, set the env var with $env:OUTRANK_API_KEY = "outr_live_xxx". On Windows cmd.exe, use set OUTRANK_API_KEY=outr_live_xxx.
Sign out / rotate:
Profiles (one per organization)
API keys are scoped to one organization. Store one profile per organization and switch with --profile:
auth login and auth logout without --profile act on the default profile. auth login verifies against and stores the base URL it resolves with the same precedence as every other command: --base-url, else OUTRANK_BASE_URL (ignored when a profile is selected), else the profile's stored value, else the default. Re-running auth login for a stored profile therefore keeps that profile's base URL. The config file keeps the default profile mirrored in the top-level apiKey / baseUrl keys, so an older outrank-cli (0.5.x) on the same machine keeps working; it only ever sees the default profile. A config file that cannot be parsed, or that contains a profile name the CLI does not accept, fails every command with CLI_CONFIG_UNREADABLE (the path is in error.details.config_path), and auth login / auth logout never overwrite it. A failed save is CLI_CONFIG_UNWRITABLE and nothing is stored. auth status and auth profiles still work when the selected profile does not exist, so you can always inspect what is stored.
MCP server
outrank-cli mcp starts a Model Context Protocol server on stdio, so Claude Code, Cursor and other MCP clients can call Outrank directly. The server runs on the same command layer as the CLI: every command except auth login, auth logout and auth profiles is a tool, named after the command with underscores (products integrations list → products_integrations_list), and sends exactly the same request.
Requirements: Node.js 18+ (check with node --version), an Outrank API key from https://www.outrank.so/dashboard/settings/api-keys, and an organization on the current Outrank app version. The same guide is on the web at https://www.outrank.so/docs/mcp.
Remote server
Outrank also hosts the same tools at https://www.outrank.so/api/mcp (Streamable HTTP), for clients that connect to a URL instead of starting a command, such as Claude, ChatGPT or Cursor. Sign in with OAuth when the client asks, or send an API key as Authorization: Bearer outr_live_....
- Claude Code plugin:
/plugin marketplace add Outrank-SAS/outrank-cli, then /plugin install outrank@outrank.
- Gemini CLI:
gemini extensions install https://github.com/Outrank-SAS/outrank-cli.
- Other clients: see llms-install.md. The hosted server always uses the
auto toolsets, has no read-only switch and takes images as base64 only: it never reads a file. It is built from createRemoteMcpServer in src/adapters/mcp/remote.js, which takes the key, the Outrank origin and, optionally, the organization's auth whoami envelope so it does not ask for it again.
Claude Code
Run this in the project folder (macOS, Linux, WSL, or the Windows Command Prompt):
If the server does not start on native Windows, use the cmd /c form:
In PowerShell, call claude.exe instead of claude (claude.cmd when Claude Code was installed with npm), so that the -- separator is kept.
Check the connection with claude mcp get outrank, then start a new Claude Code session. That command prints the stored key, so do not paste its output anywhere.
To share the server with a team, commit a .mcp.json that takes the key from each developer's environment, so no key lands in git:
Each developer then sets the variable once:
The variable reaches new terminals only, so open a new one before starting Claude Code.
Cursor
Add the server to ~/.cursor/mcp.json, the file for all projects (on Windows: %USERPROFILE%\.cursor\mcp.json). It is in your home folder, outside any repository, so the key in it cannot be committed by accident:
If the server does not start on native Windows, use "command": "cmd" and "args": ["/c", "npx", "-y", "outrank-cli@latest", "mcp"].
For one project or a team, put the server in .cursor/mcp.json inside the project and take the key from the environment, so the file can be committed without it:
Set OUTRANK_API_KEY as shown in the Claude Code section, then quit and reopen Cursor. A project server may first have to be switched on in the MCP section of the Cursor settings; a server in the all-projects file starts on its own.
Never commit a file that contains the key. A key written into a project file can end up in git with the next commit: keep the key in the all-projects file or in the environment. If a key was committed, revoke it at https://www.outrank.so/dashboard/settings/api-keys and create a new one. If Cursor does not see the variable, which can happen on macOS when it is started from the Dock, every call returns CLI_AUTH_REQUIRED: use the all-projects file instead.
Other MCP clients
Any client that can start a local MCP server (the stdio transport) works. Give it these three values; many clients read the same JSON as the Cursor example above.
- Command:
npx
- Arguments:
-y outrank-cli@latest mcp
- Environment variable:
OUTRANK_API_KEY set to your API key
The examples use the latest tag, so the server picks up each new release the next time the client starts it. To stay on one version, replace latest with an exact version number from the npm page. If you install the package globally (npm install -g outrank-cli), the command is outrank-cli mcp, or the outrank-mcp binary.
Options
--api-key, --base-url and --allow-custom-base-url are not accepted by mcp. A command line the server cannot use (an unknown option, a mistyped toolset name) does not stop it and does not quietly change what it does: it starts, and every tool call returns the problem so the assistant can tell you what to fix.
Toolsets
Tools are grouped into toolsets: core, integrations, gsc, linking, backlinks, keywords, articles, improvements, marketplace-seller, marketplace-buyer, billing, blog, ai-cited-lists.
With the default auto, the server asks Outrank at startup which toolsets the organization can use (entitlements in auth whoami) and lists only those, so a marketplace buyer without an Articles subscription does not see dozens of tools that would only return errors. A toolset the organization can only read (for example after a subscription ended) lists its read tools only. A toolset Outrank could not verify (unverified_toolsets) stays listed. The tools that start a new area (marketplace seller and buyer setup, backlink subscription quotes, the billing portal link, the product-slot quote and the AI-Cited Lists status) are always listed. Typical sizes: 33 tools for a new organization, 245 with everything enabled. ai-cited-lists is listed only for organizations with a product on the Backlinks Enterprise plan.
auto is a noise filter, not a permission system: Outrank checks access on every request whatever the tool list says. If the lookup fails or takes longer than three seconds, the server starts with core plus the setup tools, says so in its instructions, and refreshes the list as soon as a lookup succeeds. After a tool that changes what the organization can use succeeds (products_create, products_pause, products_resume, billing_purchase_products_buy, marketplace_buyer_checkout, marketplace_buyer_change_plan and the seller onboarding tools), the server re-reads the organization in the background and tells the client when the tool list changed; auth_whoami refreshes it on demand. A service the human buys in the dashboard shows up on the next auth_whoami or restart.
When a tool you need is missing, either the organization does not have that service yet, or the list was narrowed by configuration. --toolsets all lists everything; --toolsets core,keywords,articles lists exactly those toolsets and nothing else, so include core unless you already know your product ids. --read-only and an explicit --toolsets list are enforced when a tool is called, not only when tools are listed.
--read-only means nothing in Outrank or on your site changes. A few read tools still send data to Outrank to get an answer: price quotes, checks of third-party credentials (products_integrations_validate, the Webflow and Framer discovery tools) and keyword metric lookups.
Organizations and profiles
An API key belongs to one organization, and one server instance works on one organization. Every tool result repeats it under organization (id, user_email, profile, base_url), so the assistant cannot mix organizations up silently.
Besides tools, the server offers five prompts for clients that support them — onboard_product, plan_keywords (product_id, optional days), improve_declining_articles (product_id), setup_ai_cited_lists (product_id) and setup_publishing_integration (product_id, optional provider) — each a step-by-step brief naming the tools to call, listed only while its toolset is on and never in read-only mode; and one resource, outrank://whoami, a JSON document with the organization, the known entitlements and the toolsets in use, which a client can read without spending a request.
Outrank writes its agent_guidance for the HTTP API, naming endpoints such as GET /api/agent/v1/products/<id>. The server rewrites those references into tool references the assistant can call — products_get {"product_id":"<id>"} — using the registry that maps every tool to its endpoint. Query values become typed arguments (?limit=5 → {"limit":5}). An endpoint it cannot map is passed through unchanged, and so is one whose values the tool would refuse (an unknown filter, a provider that tool does not take) or whose query or path continues with text the rewrite cannot read ([email protected], ?q=%ZZ). The purchase endpoint is named as its two steps in order: billing_purchase_products_quote, then billing_purchase_products_buy. A rewritten reference can name a tool the server configuration leaves out; calling it returns an error saying which toolset to add. The guidance written by the CLI itself already names tools on this surface.
For several organizations, store one profile per organization with the CLI and register one server per profile:
A selected profile ignores OUTRANK_API_KEY, so both servers stay on their own organization even when that variable is exported in your shell. In PowerShell, call claude.exe in the last two commands, as described under Claude Code.
Security
- The key never passes through the assistant. No tool takes a key, a base URL or a profile as an argument,
auth login is not a tool, and auth_status shows neither the key nor the config path. Set the key in the client configuration, in the environment or with outrank-cli auth login in a terminal; do not paste it into a chat and do not put it in a file that is committed.
- The key is only sent to Outrank. The server accepts
www.outrank.so and outrank.so. Any other base URL is accepted only when the key and the URL come from the same stored profile, which you create yourself in a terminal (outrank-cli auth login <key> --profile staging --base-url <url> --allow-custom-base-url, then mcp --profile staging). Environment variables cannot unlock another host, so a .mcp.json from a repository you do not control cannot redirect your key by setting OUTRANK_BASE_URL. Redirects are never followed.
- Tools that cost money or cannot be undone are flagged (
destructiveHint), so the client asks before running them. A client set to approve tools automatically removes that safeguard.
- Purchases take two deliberate steps.
billing_purchase_products_buy charges the stored card: it requires the amount from a preceding billing_purchase_products_quote and an idempotency_key. If that call times out, it must be retried with the same key and arguments: Outrank records the outcome per key and returns it again (data.replayed is true), while a new key can charge twice. marketplace_buyer_change_plan cancels immediately for cancel_subscription and cancel_scheduled, and is refused without a confirm_subscription_id, which the assistant is told to read from marketplace_buyer_subscription. These fields stop a single careless call; they are values the assistant can obtain itself, so they do not replace the approval prompt of your client.
- Uploads are restricted. An image must be inside the upload directory (symlinks are resolved first), at most 5 MB, and its bytes must match its
.jpg, .jpeg, .png, .webp or .gif extension. A path outside the directory is refused before the file system is touched, so nothing else on disk can be sent or probed. Point --upload-dir at a folder that holds only the images you want to publish.
- A write that did not get an answer is never reported as safe to repeat. When a timeout or a dropped connection hits a tool that changes something, the result says the outcome is unknown and to check first; for publishing and generation tools it names the tool to poll.
- Only register this server from a configuration you trust. Whoever controls the server's command line and environment controls the process: the checks above protect your key against a redirected base URL, not against a configuration that replaces the program or its runtime options.
- Third-party tokens are visible in the conversation. Connecting Webflow, Framer, Ghost, Shopify, Wix or a webhook means passing that platform's token as a tool argument. Use a scoped token you can rotate.
- Marketplace text is untrusted. Notifications, placement briefs and placement articles are written by other organizations. The MCP server returns each such field as
{ "untrusted_counterparty_text": "…" } instead of a plain string (notification titles and bodies; the keyword, anchor text, target URL, article title and publish error of a hosted placement; the title, keyword, meta description, slug, instructions, publish error and summary of the articles in marketplace_seller_content_history; the article title, URL, image URL and seller site of a bought placement; the seller site and article title in the credit history; and the placement article itself: title, content, image URL, meta description, slug, keyword, section heading and seller site), so an assistant can tell it apart from Outrank's own fields, and tells the assistant to treat it as data, not instructions. The same wrapping covers the AI-Cited Lists tools: list titles, URLs and hosts, entry names, owner contacts and replies, ChatGPT answer excerpts and source URLs, and cited page titles, URLs and domains in ai_cited_lists_overview, ai_cited_lists_lists_list, ai_cited_lists_outreach, ai_cited_lists_offers_get, ai_cited_lists_wins_get and ai_cited_lists_citations_summary. The CLI returns the plain strings. The articles_* tools return a hosted marketplace article next to the organization's own articles: each article carries is_marketplace_placement, and when it is not false its title, keyword, meta description, slug, instructions, publish error, image URL, summary, body and replace excerpts are wrapped the same way, while the organization's own articles stay plain. This covers every articles_* tool that returns article text, articles_delete included. For articles_retry_publish, articles_generate, articles_precreate, articles_upload_image and articles_upload_featured_image the flag sits at the top of the result and also decides the fields beside the article: the publish error of a failed retry, the keyword, and the alt text, file name and address of an uploaded image. A result without the flag is wrapped. When a tool takes such text back (articles_update, articles_update_content), pass the plain string, not the wrapped object; the tool names the field if it gets the object.
- Rate limits are shared. The server uses the same per-minute and per-day budget as every other client of that key. It paces its own requests to Outrank's limit of 120 per minute, lowers that budget when a response reports fewer remaining requests, and waits out a
Retry-After, so a client that fans out many tool calls at once gets them queued instead of rejected; a queued call still counts against the client's own tool timeout.
Troubleshooting
- The server does not start. Run the same command in a terminal:
npx -y outrank-cli@latest mcp --help must print usage. Check that Node.js 18 or newer is installed (node --version). On native Windows, if the client cannot start npx directly, use the cmd /c npx … form.
- The command works in a terminal, but the client cannot find
npx or node (macOS, Linux). A client started from the Dock or a desktop menu does not always get the PATH of your shell, so a Node.js installed with nvm, fnm or volta can be invisible to it. Run which npx in a terminal, use that full path as the command, and add its folder to PATH in the env block of the server entry, for example "PATH": "/home/you/.nvm/versions/node/v22.0.0/bin:/usr/bin:/bin". The full path alone is not enough: npx starts node through PATH.
- PowerShell reports
unknown option '-y' on claude mcp add. PowerShell drops the -- separator when claude is a script or a function instead of the program itself (an npm-installed Claude Code, or a wrapper in your profile). Call claude.exe mcp add … (claude.cmd when Claude Code was installed with npm) or run the command from the Command Prompt.
- The first start is slow, or the server fails to connect.
npx downloads the package the first time and contacts the npm registry on every start, so an empty cache or a slow registry can push startup past the client's limit. In Claude Code, run /mcp reconnect all to try again; MCP_TIMEOUT (milliseconds) raises the startup limit. To take the registry out of the startup path, install the package globally (npm install -g outrank-cli@latest) and use outrank-cli mcp as the command.
- Every call returns
CLI_AUTH_REQUIRED. The server has no key. Add OUTRANK_API_KEY to the server entry, or select a stored profile with --profile.
- Only a few tools are listed. Read the server instructions in the client: they name the toolsets that are off and why. See Toolsets.
- A long call times out.
improvements_analyze, improvements_candidates_brief, improvements_tasks_execute, improvements_executions_push, articles_generate, articles_precreate, articles_retry_publish, keywords_generate and ai_cited_lists_entry_generate can run for minutes. When the call does not finish, the result is CLI_OPERATION_MAY_STILL_BE_RUNNING and names the tool to poll: the work continues on the server, so poll instead of repeating the call. The server sends progress notifications while such a call runs, but clients do not have to extend their own limit because of them. Claude Code does not cut a tool call off by default: MCP_TOOL_TIMEOUT (milliseconds) is about 28 hours when unset, and a timeout field on the server's .mcp.json entry sets a hard limit for that server which progress notifications do not extend. Cursor (measured on 3.22) stops a call after 120 seconds without a progress notification and keeps waiting while they arrive, so long calls get through there. Other clients may stop after a fixed time whatever the server does; the poll tools are the way through.
- The client warns about too many tools. Some clients cap or warn above a few dozen tools. Narrow the list with
--toolsets, for example --toolsets core,keywords,articles.
- Staging or a local deployment. Store a profile for it in a terminal (see Security) and start the server with
--profile <name>.
- See what the server does. Set
OUTRANK_MCP_DEBUG=1; one line per tool call is written to stderr, which the client shows in its MCP logs.
Tools
Tools that take a JSON body describe its fields in their description; products_create, products_update, products_output_settings_update, keywords_add, articles_update, articles_update_content, improvements_settings_update and ai_cited_lists_settings_update also declare them in the input schema (types, allowed values, and for products_create the required fields), so a body with a missing required field or a wrong type is refused before any request. Unknown keys are passed through; Outrank decides about them. The same checks apply to --json on the CLI.
Generated from the command registry with npm run docs:tools. The registry is checked against the Outrank server's route list in tests/fixtures/agentApiSurface.json, refreshed from a checkout of the Outrank app with npm run surface:extract -- <path-to-outrank-repo>: a server route without a tool, a tool without a server route, a guidance sentence naming an endpoint the server cannot map to a tool, or a rewritten argument the named tool would refuse fails the tests. The fixture records the server commit and, as sourceDirty, whether that checkout had uncommitted changes in the files it reads; extract again from a clean checkout before a release. To see whether the fixture is stale, run node scripts/extractAgentApiSurface.js --check <path-to-outrank-repo>, or run the tests with OUTRANK_SERVER_DIR=<path-to-outrank-repo>: both list the routes and guidance mentions that differ, and without the variable that one test is skipped. Kinds: read changes nothing; write changes Outrank or the connected site; destructive deletes or cannot be undone; money changes what the organization pays; local makes no request. Tools marked ★ are always listed in auto mode.
246 tools.
Examples
Google Search Console workflow
- Start with
outrank-cli products gsc connect <product-id>.
- Open the returned
connect_url in a browser and complete the Google OAuth flow with a human.
- Poll with
outrank-cli products gsc poll <product-id> --attempt-id <attempt-id> until the response reports completion.
- Inspect connected Google identities with
outrank-cli products gsc accounts <product-id>.
- List Search Console properties for one account with
outrank-cli products gsc properties <product-id> --email [email protected].
- Save the verified property with
outrank-cli products gsc set-property <product-id> --email [email protected] --site-url <verified-site-url>.
- Read performance summaries with
outrank-cli products gsc performance <product-id>.
- Run custom keyword/page/device reports with
outrank-cli products gsc search-analytics <product-id>.
- Inspect one page's indexing and canonical state with
outrank-cli products gsc inspect-url <product-id> --url <url>.
- Analyze overlapping-query cannibalization with
outrank-cli products gsc cannibalization <product-id>.
- Use
reconnect when the token is expired and disconnect to remove the product connection.
Linking workflow
- Inspect current source selection with
outrank-cli products linking sources get <product-id>.
- Update the primary or additional sources with
outrank-cli products linking sources update <product-id> --json-file linking-sources.json.
- Re-run discovery with
outrank-cli products linking detect <product-id>.
- List saved targets with
outrank-cli products linking targets list <product-id>.
- Add a manual on-domain URL with
outrank-cli products linking targets add-manual <product-id> --json-file manual-target.json.
- Create or update a saved target with
outrank-cli products linking targets upsert <product-id> --json-file target-upsert.json.
- Inspect or update product-wide behavior with
outrank-cli products linking target-config get|update <product-id>.
- Clear custom anchor text with
outrank-cli products linking targets clear-customizations <product-id> <target-id>.
- Remove a stale target with
outrank-cli products linking targets delete <product-id> <target-id>.
Example payloads:
Backlinks workflow
- Inspect current backlink settings with
outrank-cli products backlinks get <product-id>.
- Update backlink settings with
outrank-cli products backlinks update <product-id> --json-file backlinks.json.
- Inspect current credits with
outrank-cli products backlinks credits <product-id>.
- Review earned backlinks with
outrank-cli products backlinks earned <product-id> --limit 20.
- Add
--include-article or --include-summary when the human needs article context in the earned list.
- Review the unverified queue with
outrank-cli products backlinks unverified <product-id> --limit 20.
- Trigger a live removal check with
outrank-cli products backlinks check-removal <product-id> <backlink-id>.
- Trigger manual reverification with
outrank-cli products backlinks reverify <product-id> <backlink-id>.
- Inspect the current backlink subscription state with
outrank-cli products backlinks subscription <product-id>.
- Quote a dashboard-equivalent subscription plan with
outrank-cli products backlinks subscribe-quote <product-id> --plan premium.
Example payload:
Marketplace workflow
Seller side (live):
- Start with
outrank-cli marketplace seller status <product-id> — its guidance walks the onboarding wizard (Blog -> Articles -> Integration -> Payouts).
outrank-cli marketplace seller onboard <product-id> [--country <ISO2>] orchestrates onboarding: it reports remaining steps, adopts an existing reusable CMS automatically (never ask the user to pick a platform when one is reusable), and creates the Stripe Express account once the earlier steps are done and a country is provided.
- Stripe identity/bank onboarding happens in a browser — surface the returned
onboardingUrl to the human.
- Finish with
outrank-cli marketplace seller complete-onboarding <product-id> and always report the eligibility check results (marketplace seller eligibility) to the human.
- Day-to-day:
placements list|approve|decline|publish|regenerate, earnings, settings get|update, stripe manage, content-history, and marketplace notifications list --role seller.
- Admin-only (server-enforced against the key creator's current role): settings update, integrations writes, stripe commands, complete-onboarding, placements decline.
Buyer side:
- Start with
outrank-cli marketplace buyer status <product-id> — its guidance drives the setup sequence.
- The mandatory sequence: Enterprise backlink plan (
buyer subscribe-quote, a human org admin completes checkout — purchasing is admin-only) -> add target pages (buyer target-pages discover|add) -> buyer participation on.
- Then
buyer placements list|regenerate|revalidate, buyer article get|edit, buyer credits history, and marketplace notifications list --role buyer.
Keyword mutation rules
- Run
outrank-cli keywords generation-status --product <product-id> before keyword writes when you are not sure the product is writable.
keywords generate mirrors the dashboard Generate Keywords flow. Ask the human how many keywords to generate before calling it if they did not specify a count.
- The allowed generation count is 5-30, but the real upper bound depends on free scheduler slots. Check
keywords generation-status first when you need the current capacity.
- Stored keywords are read-only inventory. They can be promoted into the plan with
keywords add, but they cannot be deleted directly.
- Scheduled keywords with created or pre-created articles are read-only.
- Keywords in
generating_article are read-only until generation finishes.
- Past keywords are read-only except past
scheduled or disabled keywords without an article, which can still be rescheduled.
- If the main subscription is
past_due, keyword mutations are blocked and reads still work.
keywords list defaults to planner-first ordering for scheduled results, so page 1 prioritizes upcoming scheduled items before past ones.
- Use
keywords list --product <product-id> --scope scheduled --q "<keyword>" when looking for one planner keyword by text. If next_offset is not null, keep paging before claiming the keyword is absent.
- Keyword records return top-level
article_type, article_subtype, expected_length, and label fields. Read those from the keyword itself when answering article-type or expected-length questions; do not look in product output-settings for per-keyword length.
- Use
keywords update <keyword-id> --json '{"instructions":"..."}' to add or change per-keyword instructions after creation. This matches the dashboard flow, which creates the keyword first and updates the brief afterward.
- Use
keywords update <keyword-id> --json '{"quality_questions":[{"id":"...","question":"...","answer":"..."}]}' to answer or replace the keyword's quality questions through the same PATCH route.
- Use
keywords update <keyword-id> --json '{"scheduled_for":"YYYY-MM-DD","instructions":"..."}' when you need to change the date and brief in one PATCH request.
- Use
keywords update <keyword-id> --json '{"scheduled_for":"YYYY-MM-DD"}' for date-only rescheduling too; there is no separate reschedule command.
keywords get <keyword-id> returns article_type_options, which lists the valid dropdown values for article_type, article_subtype, and expected_length.
- Use
keywords update --json '{"article_type":"guide","article_subtype":"explainer","expected_length":"long"}' to mirror the scheduler card's "Article Type Settings" modal. The API rejects values outside the returned option lists.
Article behavior notes
- Article content is markdown in the current app and agent API.
articles get-content returns markdown by default; --format html returns rendered HTML.
- Featured image can be updated either by hosted URL through
articles update --json '{"image_url":"..."}' or by local-file upload through articles upload-featured-image.
- In-content images can be uploaded from a local file with
articles upload-image; the command returns the hosted URL plus generated alt text so the agent can insert or replace the markdown image reference.
- Per-article instructions are stored on the linked scheduled keyword, so
articles update --json '{"instructions":"..."}' only works for articles that still have a linked planner keyword.
articles get <article-id> returns article_type_options, which lists the valid dropdown values for article_type, article_subtype, and expected_length.
- Article records with a linked scheduled keyword also return top-level
article_type, article_subtype, expected_length, and label fields for direct reads.
- Use
articles update --json '{"article_type":"guide","article_subtype":"comparison","expected_length":"long"}' to mirror the dashboard's "Article Type Settings" modal. The API rejects values outside the returned option lists.
articles update --json '{"status":"published"}' only changes the status inside Outrank; it sends nothing to the CMS. Use articles publish to send an article to the CMS and articles sync to push edits of a published one.
articles create stores an article you wrote (markdown from --content-file, or content / content_html in --json) without using a generation slot; --publish also sends it to the product's CMS. If data.publish_error is set the article was kept: fix the cause and run articles publish. A slug already used by the product returns CONFLICT with details.existing_article_id, so a retry never duplicates the article.
articles publish <article-id> sends an unpublished article to the CMS. Most CMSs create a new post on every publish, so never repeat it after a success.
articles sync <article-id> pushes the current title, body, meta description and cover image of a published article to the post already in the CMS (found by its slug).
- Slug is returned for read-only reference but cannot be changed through the agent API.
articles generate <keyword-id> is only valid for scheduled keywords due today; it mirrors the dashboard's create-and-publish action.
articles precreate <keyword-id> is only valid for eligible future scheduled keywords, is unavailable on trial subscriptions, and should be polled through the keyword state afterward.
articles update --json '{"status":"published"}' cannot be used to manually publish a future scheduled article before its due date.
articles retry-publish <article-id> is only valid for generated articles with a publish error.
Blog publishing workflow
For a product with a hosted Outrank Blog (products blog get shows it):
products blog posts create <product-id> --title "..." --content-file post.md --status published writes and publishes a post in one call. Add --json for metaDescription, excerpt, slug, tags, categoryId or newCategoryName, coverImageUrl, coverAlt, seoTitle, seoDescription, canonicalUrl and noindex; use --status scheduled --scheduled-for <iso-datetime> to schedule. Everything is validated before the post is created, and a slug that is already used returns CONFLICT with details.existingArticleId.
products blog posts update <product-id> <article-id> changes only the fields it is given, including the body (--content-file) and the status (draft unpublishes, published publishes now). A live post is refreshed on the blog automatically; data.applied lists what was saved.
products blog posts delete <product-id> <article-id> takes a post off the blog and deletes it.
products blog update-settings <product-id> --json '{...}' changes the blog name, description, language and subdomain; products blog design patch <product-id> --json '{"config":{...}}' [--publish] changes part of the theme, navigation, footer, homepage or SEO defaults (objects merge, arrays replace).
--content-file is CLI-only. Over MCP, send the markdown in body.content.
Article improvements behavior notes
- Article improvements are a separate surface from generation: they rewrite an already-published post based on Search Console signals, then push the result to the CMS. They require the All-in-One plan. On Basic plan,
improvements candidates list returns an empty list and write actions return PLAN_UPGRADE_REQUIRED.
improvements settings update --json '{"enabled":true}' starts an unattended loop: weekly opportunity discovery, daily auto-scheduling on publishing_days (at most 2 weekday codes from Mon–Sun), and execution every ~15 minutes. Surface this to the human before enabling.
improvements settings update --json '{"auto_push":true}' publishes every successful rewrite to the CMS without human review (still skipped automatically on a hard validation failure or an unverified backlink injection).
improvements settings get returns readiness, which says whether the connected CMS can actually receive a pushed update (ready) or not (not_connected, action_required, unsupported). A push to an unsupported/disconnected CMS is reported as skipped with requires_manual_publish.
improvements analyze, improvements candidates brief, and improvements tasks execute run in Firebase and can take minutes. If the call times out at the network edge, the work still completes server-side — re-list candidates or poll the execution rather than retrying.
- After
improvements candidates brief, poll the candidate (via improvements candidates list) until its brief_status is ready. After improvements tasks execute, poll improvements executions get <execution-id> until status is completed, then read review_status.
- A candidate (or its underlying article) can have only one active scheduled task at a time, and an article can have only one active (pending/running) execution. Conflicts return
409.
improvements executions get returns before/after, paragraph-level hunks (each with an id), and validation_issues. Push with no JSON to accept all changes; send {"decisions":{"<hunkId>":"rejected"},"edited_hunks":{"<hunkId>":"..."}} to reject or edit specific hunks. decisions and push only work while review_status is awaiting_review.
- A push updates the article inside Outrank first, then dispatches to the CMS.
CMS_PUSH_FAILED means the content was saved in Outrank but the CMS rejected it; inspect error.details.cms_response and retry.
improvements candidates list and improvements executions list use offset pagination through data.next_offset.
AI-Cited Lists workflow
AI-Cited Lists is part of the Backlinks Enterprise plan. Outrank asks ChatGPT the product's buyer questions several times a week, keeps the "best of" lists it cites, and emails each list's owner from the customer's own Google mailbox with a paid offer for the #1 or #2 spot. The ai-cited-lists toolset is listed only for organizations with a product on that plan; on any other product every command except mailbox disconnect (admin-only, so a leaving customer can still revoke the Google token) returns AI_CITED_LISTS_ENTERPRISE_REQUIRED with details.upgrade_url, and while Outrank has outreach switched off they return AI_CITED_LISTS_UNAVAILABLE, which is not a plan problem.
ai-cited-lists status <product-id> is the entry point: data.setup.missing names what outreach still needs (mailbox, sender_details, top_up_consent) and data.checked says whether ChatGPT was asked yet.
- Setup needs an admin key for the top-up consent, the mailbox and the settings (questions,
entry generate and setup finish do not) and happens in three parts: top-up-consent get, then top-up-consent confirm after the human agreed to the terms (it authorizes credit-pack purchases on the card on file when the balance cannot cover a verified spot); mailbox connect --json-stdin takes {"email","app_password"} (a Google app password the human created at https://myaccount.google.com/apppasswords; pass it on stdin so it stays out of the shell history) and connects at once, and mailbox get shows the four checks (connection, domain, reply_filter, inbox_test) with mailbox domain-check, mailbox filter-test, mailbox inbox-test and mailbox dkim-selector --selector <name> (or --reset) to fix them; settings update --json '{"sender_name":"...","sender_title":"...","postal_address":"..."}' signs the emails.
questions list|add|edit|remove manage the buyer questions. citations check asks ChatGPT now unless fresh data or a running check exists; citations summary [--weeks 4|12] shows the answers naming the product and the pages ChatGPT cited.
lists list [--view to_send|skipped] shows this week's cited lists; nothing needs approval, every list gets an offer on next_send_at unless lists skip is called. outreach lists every offer with its stage, the latest owner replies and the sending health; offers get opens one and offers withdraw cancels a draft or sent offer; wins get is the proof page of a won list. settings update --json '{"outreach_paused":true}' pauses new emails without hiding live spots.
entry generate rewrites the base entry owners publish (minutes; read it with settings get); settings update --json '{"entry_mode":"custom","custom_entry":{"markdown":"..."}}' publishes custom wording instead (links may only point to the product site).
- Report what ChatGPT said; never claim a placement caused a mention. Credits are charged only once the owner has placed the product (the prices are in
top-up-consent get).
Linking behavior notes
- Linking writes are blocked unless the product has an active backlink subscription.
products linking sources update accepts source_type values sitemap, root_url, or integration.
- When
source_type is sitemap or root_url, source_url is required.
additional_linking_sources is a full replacement list. Each item needs id, source_type, source_url, and purpose.
products linking detect refreshes discovered article URLs from the saved primary source.
products linking targets add-manual and upsert only accept URLs on the product's own domain.
products linking target-config update --json '{"only_prioritized":true}' requires enough prioritized targets; if not, the API returns a conflict with guidance.
Backlink behavior notes
- Use
products backlinks update, not products update, for backlink exchange settings.
- Backlink writes are blocked when the product is hidden for inappropriate content, but reads still work.
- Setting
min_domain_rating requires an active backlink subscription.
products backlinks subscribe-quote is read-only. Purchasing or changing the add-on still requires the dashboard checkout flow.
products backlinks earned uses cursor pagination through data.next_cursor.
products backlinks unverified is the deduped article-level manual-review stream.
Integrations workflow
- Inspect connected publishing integrations with
outrank-cli products integrations list <product-id>.
- Inspect provider availability with
outrank-cli products integrations providers.
- Webhook uses
endpoint plus access_token. Outrank sends Authorization: Bearer <access_token> on each request. HMAC signing is not implemented in the current webhook integration.
- WordPress Plugin uses
domain plus api_key. The Outrank plugin must already be installed and configured on the target site.
- Start WordPress Plugin setup with
outrank-cli products integrations prepare wordpress-plugin <product-id>. That mirrors the dashboard flow by generating the integration token first, before validate/create.
- WordPress.com uses OAuth instead of direct credentials. Start with
outrank-cli products integrations connect wordpress-com <product-id> --json-file wordpress-com-connect.json, have the human open the returned connect_url, then poll with outrank-cli products integrations poll wordpress-com <product-id> --attempt-id <attempt-id> until the handoff completes.
- Notion uses OAuth plus a second configuration step. Start with
outrank-cli products integrations connect notion <product-id>, have the human open the returned connect_url, then poll with outrank-cli products integrations poll notion <product-id> --attempt-id <attempt-id>. After OAuth completes, use outrank-cli products integrations notion databases <product-id> <integration-id> and ... notion schema <product-id> <integration-id> <database-id> before update to save name, database_id, and fields.
- Framer uses the newer Server API flow, not the older plugin flow. Start with
outrank-cli products integrations validate framer --json '{"project_url":"https://framer.com/projects/your-project","server_api_key":"framer_server_key"}'. If you want to publish into an existing user-owned collection, then use framer collections and framer fields before create; otherwise use collection_setup_mode: "create" and let Outrank create the collection automatically.
- Shopify uses a direct app access token. Start with
outrank-cli products integrations validate shopify --json '{"access_token":"shpat_xxx"}'; the response returns shop_name and the available blogs you can use in the create payload, so the human does not need to provide the store name separately.
- Webflow uses a direct API token plus pre-save discovery. Start with
outrank-cli products integrations validate webflow --json-file webflow-sites.json, then use webflow sites, webflow collections, webflow fields, and webflow items to build the exact create or update payload for the chosen CMS collection.
- Wix is a direct credential flow using
site_id, api_key, and publish_status. Validate first, then create or update directly; there is no OAuth or prepare step.
- Next.js Blog is a one-step create flow. Run
outrank-cli products integrations create <product-id> --json '{"provider":"nextjs-blog"}' and store the returned data.secrets.api_key immediately because it is not readable later. One key serves both the articles and the marketplace integration of a product: when the product already hosts marketplace placements through Next.js Blog, create reuses that key and returns no secrets.
- Validate unsaved webhook, Ghost, Framer, Shopify, Webflow, Wix, or WordPress Plugin credentials with
outrank-cli products integrations validate <provider> --json-file integration.json.
- Create a webhook, Ghost, Framer, Shopify, Webflow, Wix, WordPress Plugin, or Next.js Blog integration with
outrank-cli products integrations create <product-id> --json-file integration-create.json.
- Inspect one integration with
outrank-cli products integrations get <product-id> <integration-id>.
- Update a saved webhook, Ghost, Shopify, Webflow, Wix, WordPress Plugin, or Notion integration with
outrank-cli products integrations update <product-id> <integration-id> --json-file integration-update.json.
- Test a saved webhook, Ghost, Framer, Shopify, Webflow, Wix, WordPress Plugin, or Notion integration with
outrank-cli products integrations test <product-id> <integration-id>.
- Delete a stale integration with
outrank-cli products integrations delete <product-id> <integration-id>.
Current scope note:
- Webhook, Ghost, Shopify, Webflow, and Wix support direct validate/create/update/test. Framer supports validate, provider-specific discovery, create, and test for the newer Server API flow. WordPress Plugin supports prepare plus validate/create/update/test. Next.js Blog supports one-step create plus the shared list|get|delete surface, returning a one-time API key on create. WordPress.com supports OAuth connect plus poll, then the shared list|get|delete surface. Notion supports OAuth connect plus poll, provider-specific database/schema discovery, then update/test on the saved row.
- V2 products are intended to keep one publishing integration. If
list returns multiple rows, that usually means legacy V1 residue and stale rows should be reviewed and deleted.
- Backlink exchange management covers configuration, status reads, earned/unverified review queues, and reverification actions. Purchasing or changing backlink add-ons still requires the dashboard checkout flow.
- Other providers are still mostly read/delete only while create, update, test, and provider-specific discovery are added one by one.
Custom API target (advanced)
The CLI talks to https://www.outrank.so by default and you should not need to change that. The override below exists only for Outrank staff debugging against a staging deployment.
On Windows PowerShell, use $env:OUTRANK_BASE_URL = "https://staging.example". On Windows cmd.exe, use set OUTRANK_BASE_URL=https://staging.example.
--base-url <url> is the per-command form and works on every platform.
Only https://www.outrank.so and https://outrank.so are accepted by default, so a key cannot be sent to an unknown host by accident. For any other host, add --allow-custom-base-url or set OUTRANK_ALLOW_CUSTOM_BASE_URL=1. The base URL must be https; plain http is accepted only for localhost together with that switch. Redirects are never followed. The MCP server is stricter: see Security.
The agent API expects product UUIDs, not numeric ids like 88.
Notes on whoami
auth whoami currently returns organization.product_count from the backend. In the current Outrank backend, that value behaves as the organization's purchased or available product-slot count, not necessarily the number of product rows returned by products list.
data.organization.id is the organization the key belongs to. data.entitlements is computed server-side with the same checks the endpoints apply:
toolsets: an object keyed by command group (core, integrations, gsc, linking, backlinks, keywords, articles, improvements, marketplace-seller, marketplace-buyer, billing) whose value is full (reads and writes) or read (the subscription behind the group is no longer active, for example after an Articles subscription ended: inspect existing data and expect writes that need the subscription to be rejected). Groups that are missing are not active for the organization yet; the endpoints that start them (marketplace seller and buyer status, backlink subscription quotes, the billing portal link) still answer.
unverified_toolsets: the same shape, for groups whose access could not be checked for this response (for example improvements when the plan lookup failed). They are left out of toolsets but may still work, and the MCP server keeps listing them.
flows: articles, marketplace_buyer and marketplace_seller booleans saying whether the organization has started that area, so an agent can offer the right onboarding step.
disabled_products_with_services: how many disabled products have services or a started seller setup that do not count. A disabled product can only be reactivated in the dashboard.
entitlements is null when the backend could not compute it; treat every group as available and rely on per-request errors.
Entitlements are a hint for choosing which commands to offer, not an access boundary: they are merged across the organization's active products, and the endpoints that need a subscription check it per product on their own.
products list, products get, products update and products create include services: [{ service, status }] per product (service is one of articles, tools_builder, backlink_enterprise, marketplace_seller; status is active, read_only or cancelled), sorted by service name. On products create the field is null when the services could not be read back after creation.
JSON-first contract
Every command writes a single JSON object to stdout.
- Successful API calls pass through the server's
ok, data, and agent_guidance shape.
- Local CLI-only commands (
auth login, auth logout, usage help) emit the same envelope shape.
- Invalid usage and local runtime failures also emit JSON and exit non-zero.
This makes the CLI easier for AI agents to drive reliably.
Using the agent skill (SKILL.md)
SKILL.md ships alongside this CLI as a Claude Code skill. It tells an AI agent how to drive outrank-cli end-to-end — when to inspect before writing, how to interpret agent_guidance, the new-product preflight, the GSC flow, the linking flow, keyword/article rules, and how to handle 401/402/403/409 errors.
You do not need it if you are using the CLI by hand from a terminal. You want it when you are letting an AI assistant operate the CLI for you. With the MCP server connected, the skill is optional: it adds workflow guidance on top of the tools and tells the assistant to use them instead of the terminal.
Claude Code
- Install
outrank-cli globally so the agent can call it: npm install -g outrank-cli.
- Make
SKILL.md available to Claude Code as a skill. Either:
- Project-scoped — copy
SKILL.md into .claude/skills/outrank/SKILL.md inside the project you want the agent to work in, or
- User-scoped — copy it into
~/.claude/skills/outrank/SKILL.md (Windows: C:\Users\<you>\.claude\skills\outrank\SKILL.md) so it is available in every Claude Code session.
- Provide the API key once via env var or
outrank-cli auth login (see Auth).
- In Claude Code, ask the agent to do something Outrank-shaped (e.g. "list my products", "create a new product for example.com", "regenerate next week's keyword plan"). The skill's
description triggers it automatically; Claude will run outrank-cli commands and follow the rules in SKILL.md.
Other agents (Cursor, custom Anthropic SDK apps, etc.)
SKILL.md is plain Markdown — feed it as a system prompt, a tool description, or load it into the model's context however your runtime expects. The key contract is:
- Every command returns JSON with
ok, data, and agent_guidance.
- The agent should always read
agent_guidance before deciding the next step.
- Mutations should be preceded by an inspect call (
products get, keywords list, articles get, etc.).
Updating the skill
The CLI and SKILL.md are versioned together. When you upgrade outrank-cli, also refresh the copy of SKILL.md in your .claude/skills/... directory from the installed package (<global node_modules>/outrank-cli/SKILL.md) or from this repo.