# Serpel CLI reference

URL: https://serpel.app/docs/cli

Updated: 2026-10-10

The `serpel` command line runs crawls, rank checks, keyword research and exports through the same REST API as the dashboard. This page covers installation, sign-in, every command group, JSON output, exit codes and a CI example. Run any command with `--help` for its full list of flags.

## How do you install the Serpel CLI?

The CLI is published on npm as [`@serpel/cli`](https://www.npmjs.com/package/@serpel/cli) under the MIT license. It installs one command, `serpel`, ships as a single bundled file and needs Node.js 20.9 or newer.

1. **Install it globally.** npm puts the `serpel` command on your path. pnpm, Yarn and Bun work the same way.

```bash
npm install -g @serpel/cli
```

2. **Or run it without installing.** `npx` downloads the latest version for a single run, which is handy in CI or on a shared machine.

```bash
npx @serpel/cli --help
```

3. **Check the installation.** The command prints the CLI version.

```bash
serpel --version
serpel --help
```

The package contains `dist/index.js` with its libraries bundled, plus their license notices. The one optional dependency is `@napi-rs/keyring`, which lets the CLI keep your token in the system keyring. Without it, the CLI falls back to a file that only you can read. To update, run `npm install -g @serpel/cli@latest`. To remove it, run `npm uninstall -g @serpel/cli`.

## How do you sign in to the Serpel CLI?

### Browser sign-in (device login)

This is the default and needs no token to copy.

```bash
serpel auth login
```

1. The CLI asks Serpel for a sign-in code and prints a confirmation address and a verification code such as `ABCD-EFGH`. The code is valid for ten minutes. The CLI opens the address in your default browser. With `--no-browser`, it only prints the address, which suits servers without a desktop.
2. In the dashboard, check that the code matches, choose the permissions the token gets and approve the sign-in.
3. The CLI waits for your approval, stores the token and prints your name, email address, permissions and project restriction.

Ctrl+C cancels the wait. If the code expires or you deny the sign-in, the command exits with code 3.

### Sign in with an API token

Create a token in the dashboard under Settings, API tokens, or with `serpel tokens create`, and hand it to the CLI. The CLI checks it with `GET /me` first and stores nothing if the token is invalid. Prefer `--token-stdin` over `--token`, because an argument ends up in your shell history.

```bash
echo "$SERPEL_TOKEN" | serpel auth login --token-stdin
serpel auth login --token-stdin < token.txt
```

### Use SERPEL_TOKEN in scripts and CI

In CI you do not need `serpel auth login`. Set `SERPEL_TOKEN` and the CLI uses it for every command. The variable beats any stored token and is never written to disk.

```bash
export SERPEL_TOKEN=vsk_your_token
serpel projects list --json
```

### Check your session or sign out

```bash
serpel auth status
serpel auth logout
```

`auth logout` revokes the stored token on the server and then deletes it locally. If the server cannot be reached, the CLI still deletes the local copy and warns that the token stays valid until you revoke it in the dashboard or it expires. A token from `SERPEL_TOKEN` is not touched.

## How is the Serpel CLI configured?

The CLI talks to `https://serpel.app` unless you tell it otherwise. The first source that is set wins: the `--api-url <url>` flag, the `SERPEL_API_URL` variable, the config file (`serpel config set api-url <url>`), then the default. Only `https://` addresses are accepted, except `http://` for `localhost`, `127.0.0.1` and `[::1]`. A trailing `/api/v1` is removed, and addresses with credentials, query parameters or anchors are rejected. Most people never change the address. Set it only if you use another Serpel instance.

```bash
serpel config get
serpel config get api-url
serpel config set api-url https://example.com
serpel config unset api-url
```

### Environment variables

| Variable | Meaning |
| --- | --- |
| `SERPEL_API_URL` | API address, see above. |
| `SERPEL_TOKEN` | API token. Takes precedence over a stored token and is never stored. |
| `SERPEL_CONFIG_DIR` | Use another directory for the config file and the credentials file. |
| `SERPEL_TOKEN_STORAGE` | `auto` (default: keyring, otherwise file), `keyring` or `file`. |
| `SERPEL_HTTP_TIMEOUT` | Time limit per request in seconds. Default 30. Keyword research and list refreshes get at least 120 seconds. |
| `SERPEL_POLL_INTERVAL_MS` | Check interval for `--wait` in milliseconds. Default 2000, minimum 50. |
| `NO_COLOR`, `FORCE_COLOR` | Turn colours off, or force them when output is not a terminal. |

### Where does the CLI keep your token?

- In the system keyring when one is available: Windows Credential Manager, macOS Keychain or the Secret Service on Linux. The entry uses the service name `serpel` and the API address as the account.
- Otherwise in `credentials.json` in the config directory, with permissions that only your account can read, and a warning. Under Windows, the permissions of your user profile protect the file.
- The token never appears in output, not even with `--verbose` or `--json`, and the CLI does not follow redirects, so the token cannot be sent to another address.

| System | Config file |
| --- | --- |
| Windows | `%APPDATA%\serpel\config.json` |
| Linux and macOS | `$XDG_CONFIG_HOME/serpel/config.json`, otherwise `~/.config/serpel/config.json` |

## Which global flags does the CLI have?

These flags work with every command and can come before or after it.

| Flag | Meaning |
| --- | --- |
| `--api-url <url>` | API address for this call. |
| `--json` | Machine-readable JSON on standard output only. |
| `--no-color` | Turn colours off. |
| `--verbose` | Log method, URL, status and duration of each request to standard error, without headers, bodies or the token. |
| `-h`, `--help` | Show help for the command. |
| `-V`, `--version` | Show the CLI version. |

## Which commands does the Serpel CLI have?

Every command accepts `--help` and prints its flags. Arguments such as `<id>` stand for IDs you get from other commands, for example `serpel projects list`. Commands that delete or disconnect something send nothing until you add `--yes`, and exit with code 2 without it.

### Account and configuration

| Command | What it does |
| --- | --- |
| `serpel auth login` | Signs in. By default it opens a browser approval (device login). `--no-browser` only prints the address, `--token-stdin` or `--token <token>` stores an existing API token after checking it. |
| `serpel auth status` | Shows the signed-in user, permissions, project restriction, API address and where the token is stored. |
| `serpel auth logout` | Revokes the stored token on the server and deletes it locally. |
| `serpel auth profile` | Changes the name (`--name`) or email address (`--email`) of your own account. Needs a token with all permissions and no project restriction. |
| `serpel config get [api-url]` | Shows the configuration, or a single value. |
| `serpel config set api-url <url>` | Stores the API address in the config file. |
| `serpel config unset api-url` | Removes the stored API address. |
| `serpel tokens list` | Lists your API tokens with status, permissions and last use. |
| `serpel tokens create --name <name> --scope <scope>` | Creates an API token. Repeat `--scope` for several permissions. `--project <id>` (repeatable) restricts the token to projects and `--expires-in-days <n>` (1 to 365) sets an expiry. The secret is printed once. |
| `serpel tokens revoke <id> --yes` | Revokes a token immediately. |

### Projects

| Command | What it does |
| --- | --- |
| `serpel projects list` | Lists your projects. Flags: `--query` (`-q`), `--limit`, `--page`, `--offset`. |
| `serpel projects create --domain <domain>` | Creates a project. Optional: `--name`, `--country` (API default DE) and `--language` (API default de). Exit code 6 with the existing project ID if you already have a project for the domain. |
| `serpel projects show <id>` | Shows the project overview: last crawl, issues, rankings, AI visibility, tasks and next steps. |
| `serpel projects update <id>` | Changes the settings you pass, listed below. |
| `serpel projects delete <id> --yes` | Deletes a project and cancels its running jobs. Owners only. |
| `serpel projects members list --project <id>` | Lists the members and their roles. |
| `serpel projects members add --project <id> --email <address>` | Adds an existing user. `--role editor` or `viewer` (API default viewer). Owners only. |
| `serpel projects members remove <userId> --project <id> --yes` | Removes a member. Owners only. |

`serpel projects update` takes these settings. Pass only the ones you want to change.

| Flag | Setting |
| --- | --- |
| `--name`, `--country`, `--language` | Display name and market. |
| `--location <location>` | Default location for new keywords: a city, postal code, location code or `nationwide`. |
| `--max-pages <n>` | Maximum pages per crawl, 1 to 1,000. |
| `--rank-depth <n>` | Result depth of rank checks: 10, 20, 30, 50, 100. |
| `--device desktop` or `--device mobile` | Device used for rank checks. |
| `--schedule <schedule>`, `--schedule-hour <0-23>` | Automatic rank check: `manual`, `daily`, `every_3_days` or `weekly`, and the hour it starts. |
| `--crawl-schedule <schedule>` | Automatic crawl: `manual` or `weekly`. |
| `--render-mode <mode>` | JavaScript rendering during crawls: `auto` (only nearly empty pages), `always` or `never`. |
| `--ai-overview on` or `off` | Track the Google AI Overview during rank checks and AI visibility checks. |
| `--keyword-metrics on` or `off` | Add search volume and CPC automatically before rank checks. |
| `--slack-webhook`, `--discord-webhook`, `--webhook` | Where monitoring reports are sent. `--no-slack-webhook`, `--no-discord-webhook` and `--no-webhook` remove a target. `--webhook` takes your own https address and receives JSON. |
| `--slack-channel`, `--discord-channel` | Channel name shown in Serpel (up to 80 characters). `--no-slack-channel` and `--no-discord-channel` remove it. |

### Crawls and audit

| Command | What it does |
| --- | --- |
| `serpel crawl start --project <id>` | Starts a crawl as a background job and prints the job ID. Flags: `--max-pages <n>` (1 to 1,000), `--wait`, `--timeout <seconds>`. If a crawl is already running, you get that one back. |
| `serpel crawl list --project <id>` | Lists crawls with status, pages and audit score. |
| `serpel crawl show <crawlId>` | Shows a crawl in detail: status codes, rendering, link check, Core Web Vitals status, robots.txt, sitemaps and llms.txt. |
| `serpel crawl pages --crawl <crawlId>` | Lists the crawled pages. `--status` filters by `ok`, `redirect`, `client_error`, `server_error`, `failed` or `blocked`, `--query` searches URL and title. |
| `serpel crawl page <pageId> --crawl <crawlId>` | Shows one page: metadata, issues, rendering, hreflang, structured data and links. |
| `serpel crawl compare --project <id>` | Compares two completed crawls (`--crawl`, `--base`): score, new, resolved and changed issues. Defaults to the latest crawl and the one before it. |
| `serpel audit issues --project <id>` | Lists the issues of the latest completed crawl (or `--crawl`) grouped by type. Filters: `--severity error`, `warning` or `notice`, `--category`, `--query`. |
| `serpel audit issue <code> --project <id>` | Shows one issue with what was found, why it matters, how to fix it and the affected URLs. |
| `serpel audit web-vitals <crawlId>` | Shows LCP, INP and CLS per URL and device, as field and lab values. |

The `--category` filter accepts `availability`, `redirects`, `meta`, `headings`, `indexing`, `crawling`, `links`, `images`, `security`, `content`, `performance`, `mobile`, `structured_data`, `ai_search`, `rendering`, `international`, `web_vitals`, `social`.

### Jobs

| Command | What it does |
| --- | --- |
| `serpel jobs status <jobId>` | Shows the status of a job. Add `--wait` (and `--timeout <seconds>`) to follow it to the end. |
| `serpel jobs list` | Lists jobs. Filters: `--project`, `--status` (`queued`, `running`, `succeeded`, `failed`, `cancelled`) and `--type` (`crawl`, `rank_check`, `ai_check`, `monitoring`, `search_sync`, `analytics_sync`, `codebase_snapshot`, `recommendation_generate`). |
| `serpel jobs cancel <jobId>` | Cancels a queued or running job. |
| `serpel jobs retry <jobId>` | Restarts a failed or cancelled job. Other jobs end with exit code 6. |
| `serpel jobs activity --project <id>` | Shows the project history: jobs, emailed reports and keyword research with cost and duration. `--days` (1 to 90, default 7). |

### Keyword research and keyword lists

| Command | What it does |
| --- | --- |
| `serpel keywords research "<keyword>"` | Researches keyword ideas and uses credits. Choose exactly one mode: a keyword, `--list "a,b,c"`, `--file <path>` (one keyword per line, `-` reads standard input) or `--domain <domain>`. Up to 700 keywords per request. Flags: `--country`, `--language`, `--limit` (1 to 500, API default 100), `--project`, `--refresh` (bypasses the cache and queries the provider again, which incurs costs), `--save-to <listId>`, `--idempotency-key <key>`. |
| `serpel keywords history` | Lists saved research runs, newest first (`--project`, `--limit`). |
| `serpel keywords research-show <runId>` | Shows a saved result again without a new query and without cost. |
| `serpel keywords lists` | Lists keyword lists (`--project`, `--query`, `--limit`, `--page`, `--offset`). |
| `serpel keywords list-show <listId>` | Shows a list with its description and keyword count. |
| `serpel keywords list-create --project <id> --name <name>` | Creates a list. Optional `--description`. |
| `serpel keywords list-update <listId>` | Renames a list or changes the description (`--name`, `--description`, `--no-description`). `list-rename` is an alias. |
| `serpel keywords list-delete <listId> --yes` | Deletes a list with all its keywords. |
| `serpel keywords items <listId>` | Shows the keywords of a list. `--sort` takes `keyword`, `volume`, `difficulty`, `cpc` or `created`. |
| `serpel keywords items-add <listId> --keyword <keyword>` | Adds keywords. Repeat `--keyword`, or add `--research-run <runId>` to take the metrics from a saved run. A list holds up to 5,000 keywords. |
| `serpel keywords items-remove <listId> <itemId> --yes` | Removes one keyword. The item ID is the ID column of `keywords items`. |
| `serpel keywords import <listId> --file <csv>` | Imports a CSV file of at most 2 MB (`-` reads standard input). Optional `--country` and `--language`. |
| `serpel keywords refresh <listId>` | Fetches the metrics of all keywords in a list again. Uses credits. Optional `--idempotency-key`. |

### Rankings and competitors

| Command | What it does |
| --- | --- |
| `serpel rankings add --project <id> --keyword <keyword>` | Adds keywords to rank tracking. Repeat `--keyword`. Optional `--device`, `--country`, `--language`, `--location`. |
| `serpel rankings run --project <id>` | Starts a rank check for all or selected keywords (`--keyword-id`, repeatable) as a background job. Uses credits. Supports `--wait` and `--timeout`. |
| `serpel rankings list --project <id>` | Lists tracked keywords with position, change, URL, AI Overview status and check time. |
| `serpel rankings history <keywordId>` | Shows the position history of a keyword (`--limit` 1 to 365, default 90). |
| `serpel rankings serp <keywordId>` | Shows the stored Google results of the last check (`--limit` 1 to 100, default 10). |
| `serpel rankings summary --project <id>` | Shows top 3, top 10, average position and changes for all tracked keywords. |
| `serpel rankings locations <query>` | Searches locations for local rank tracking (`--country`, `--limit`). |
| `serpel rankings remove <keywordId> --yes` | Removes a keyword and its history. |
| `serpel competitors list --project <id>` | Lists competitors with visibility, top 10 keywords and estimated clicks. |
| `serpel competitors compare --project <id>` | Compares your position with each competitor, keyword by keyword. |
| `serpel competitors suggest --project <id>` | Suggests domains that often appear in the top 20 for your keywords. |
| `serpel competitors add --project <id> --domain <domain>` | Adds a competitor, up to 10 per project. |
| `serpel competitors remove --project <id> --domain <domain>` | Removes a competitor by domain or ID. |

### AI visibility

| Command | What it does |
| --- | --- |
| `serpel ai add --project <id> --prompt <prompt>` | Adds prompts the way users ask them of an AI. Repeat `--prompt`. `--platform` currently accepts `chat_gpt` only. Optional `--country` and `--language`. |
| `serpel ai prompts --project <id>` | Lists prompts with the latest result: cited, mentioned, Google AI Overview and check time. |
| `serpel ai run --project <id>` | Checks all or selected prompts (`--prompt-id`, repeatable) as a background job. Uses credits. Supports `--wait` and `--timeout`. |
| `serpel ai history <promptId>` | Shows the history of a prompt for ChatGPT and the Google AI Overview (`--limit` 1 to 365, default 60). |
| `serpel ai status --project <id>` | Shows citations, mentions, the most frequent sources, AI crawler access in robots.txt and llms.txt. |
| `serpel ai remove <promptId> --yes` | Removes a prompt with all its results. |

### Monitoring and reports

| Command | What it does |
| --- | --- |
| `serpel monitoring run --project <id>` | Checks rankings and AI visibility now, compares with the last report and creates a new report. Uses credits. Supports `--wait` and `--timeout`. |
| `serpel monitoring webhook-test --project <id>` | Sends a test message to the configured webhooks. `--channel` limits it to `slack`, `discord` or `custom`. |
| `serpel monitoring recipients list --project <id>` | Lists the email recipients of the reports. |
| `serpel monitoring recipients add --project <id> --email <address>` | Adds a recipient, up to 10 per project. |
| `serpel monitoring recipients remove --project <id> --email <address>` | Removes a recipient by address or ID. |
| `serpel monitoring email-test --project <id>` | Sends the latest report as a test email to all recipients, or to one with `--email`. |
| `serpel reports list` | Lists reports, newest first (`--project`, `--unread`, `--limit`, `--page`, `--offset`). |
| `serpel reports show <id>` | Shows a report with rankings, AI visibility and website audit. Does not mark it as read. |
| `serpel reports read <id>` | Marks a report as read. |
| `serpel reports read-all` | Marks all reports as read, or those of `--project`. |
| `serpel reports unread` | Shows the number of unread reports. |
| `serpel reports send <reportId>` | Sends a report again to all active recipients. |
| `serpel reports preview <reportId>` | Shows the email of a report, or saves it as HTML with `--output <file>` and opens it with `--open`. |

### Search data, web analytics and integrations

API keys for Bing, PostHog and Plausible are read from a file with `--api-key-file <path>`, or from standard input with `-`. They are never passed as an argument and never printed. Google connections open a browser approval; `--no-open` only prints the address.

| Command | What it does |
| --- | --- |
| `serpel search status --project <id>` | Shows the Google Search Console and Bing Webmaster Tools connections of a project. |
| `serpel search connect-google --project <id>` | Creates an authorisation address (valid for ten minutes) and opens it. Confirm it with the account that created the token. |
| `serpel search connect-bing --project <id> --api-key-file <path>` | Connects Bing Webmaster Tools. Optional `--site-url`. |
| `serpel search site --project <id> --provider <provider> --site-url <url>` | Chooses the property of a connection. `--provider` is `google` or `bing`. |
| `serpel search queries --project <id>` | Shows clicks, impressions, CTR and position. Flags: `--provider google` or `bing`, `--dimension query` or `page`, `--days 7`, `28` or `90`, `--limit` (1 to 500, default 50). |
| `serpel search disconnect --project <id> --provider <provider> --yes` | Deletes the stored credentials of a connection. |
| `serpel analytics status --project <id>` | Shows which of PostHog, Plausible and Google Analytics 4 are connected. |
| `serpel analytics report --project <id>` | Shows visitors, sessions, pageviews, bounce rate, top pages, entry pages, sources and devices. Flags: `--provider`, `--days 7`, `28` or `90`, `--daily`. |
| `serpel analytics connect-posthog --project <id> --posthog-project <id> --api-key-file <path>` | Connects PostHog. `--region eu` or `us`, or `--host <url>` for your own instance. |
| `serpel analytics connect-plausible --project <id> --site-id <domain> --api-key-file <path>` | Connects Plausible. Optional `--host <url>` for your own instance. |
| `serpel analytics connect-google --project <id>` | Connects Google Analytics 4 in the browser. |
| `serpel analytics property --project <id> --property-id <id>` | Chooses the Google Analytics property. |
| `serpel analytics properties --project <id>` | Lists the Google Analytics properties with their domains. `--refresh` reloads them from Google. |
| `serpel analytics disconnect --project <id> --provider <provider> --yes` | Deletes the stored credentials of a connection. |
| `serpel integrations status` | Shows account integrations and how they map to your projects (`--details` per project). |
| `serpel integrations connect-google` | Connects Google Search Console and Google Analytics for all projects. |
| `serpel integrations connect-bing --api-key-file <path>` | Connects Bing Webmaster Tools for all projects. |
| `serpel integrations connect-posthog --api-key-file <path>` | Connects PostHog for all projects (`--region`, `--host`). |
| `serpel integrations connect-plausible --api-key-file <path>` | Connects Plausible for all projects (`--host`). |
| `serpel integrations sync --provider <integration>` | Maps projects again, for example after new properties. `--provider` is `google`, `bing`, `posthog` or `plausible`. |
| `serpel integrations disconnect --provider <integration> --yes` | Disconnects an integration. Connected projects lose its data. |

### Tasks and recommendations

| Command | What it does |
| --- | --- |
| `serpel tasks list --project <id>` | Lists tasks. Filters: `--status open`, `in_progress` or `done`, `--priority high`, `medium` or `low`, `--query`. |
| `serpel tasks create --project <id> --title <title>` | Creates a task. Optional `--priority`, `--description` and `--url` (repeatable). |
| `serpel tasks create --project <id> --issue <code>` | Creates a task from an audit issue and carries over the affected URLs. `--crawl` picks the crawl, default is the last completed one. |
| `serpel tasks update <taskId>` | Changes `--status`, `--priority`, `--title` or `--description`. |
| `serpel tasks show <taskId>` | Shows a task with its URLs and the result of the recheck after the latest crawl. |
| `serpel tasks delete <taskId> --yes` | Deletes a task. |
| `serpel recommendations list --project <id>` | Lists recommendations (alias `serpel recs`). `--status` takes `open`, `accepted`, `dismissed`, `done`, `stale` or `active`, `--rule` filters by rule, `--query` searches. |
| `serpel recommendations show <id>` | Shows a recommendation with evidence, data sources and the next action. |
| `serpel recommendations accept <id>` | Accepts a recommendation. `dismiss`, `done` and `reopen` work the same way. |

### Codebase scan

`serpel scan [directory]` reads a Next.js project, builds a snapshot of its routes and metadata and uploads it to a project. Serpel then matches routes to crawled pages and search data and flags differences between code and live site. The snapshot contains no source code. The upload is free and needs the `codebase:write` scope.

| Flag | Meaning |
| --- | --- |
| `[directory]` | The folder with the `package.json` of the Next.js app. Defaults to the current folder. In a monorepo, pass the subfolder, for example `apps/web`. |
| `--project <id>` | The project the snapshot belongs to. Required unless you use `--dry-run`. |
| `--dry-run` | Sends nothing and needs no sign-in. The snapshot goes to standard output, or to `--out`. |
| `--out <file>` | Writes the snapshot as JSON to a file. |
| `--include-text` | Also sends a text excerpt of at most 500 characters per page, only for routes with build output. |
| `--yes` | Skips the confirmation before sending. Without a terminal and without `--yes`, the command exits with code 2. |
| `--wait`, `--timeout <seconds>` | Waits until Serpel has processed the snapshot. |

The CLI never reads or sends `.env` files, key files, `node_modules`, `.git` or anything your `.gitignore` excludes, and it removes values that look like credentials. Limits: 2,000 routes, 5,000 sitemap URLs, 2 MB of JSON and 20 uploads per project and day. Answering anything but yes to the confirmation prompt ends the command with exit code 130.

```bash
serpel scan --dry-run
serpel scan apps/web --dry-run --out snapshot.json
serpel scan --project "$PROJECT_ID" --wait
serpel scan apps/web --project "$PROJECT_ID" --yes --include-text
```

### Export

`serpel export --project <id> --type <type>` exports `issues`, `keywords`, `rankings` or `tasks`. `--format` is `csv` (default) or `json`. Without `--output <file>` (`-o`), the file content goes to standard output so you can redirect it. `--crawl <id>` applies to issues (default: the last completed crawl) and `--list <id>` to keywords.

```bash
serpel export --project "$PROJECT_ID" --type issues --format csv --output issues.csv
serpel export --project "$PROJECT_ID" --type rankings --format json > rankings.json
```

### Billing, agents and providers

| Command | What it does |
| --- | --- |
| `serpel billing balance` | Shows available, total and reserved credits, usage today and in the last 30 days, and a forecast of how long the balance lasts. |
| `serpel billing estimate --operation <operation>` | Estimates the cost of an operation and books nothing. Operations: `rankCheck`, `aiCheck`, `keywordResearch`, `keywordListRefresh`, `keywordMetrics`, `crawl`, `monitoring`. Inputs: `--keywords`, `--depth`, `--prompts`, `--no-ai-overview`, `--scheduled`, `--mode`, `--limit`, `--tasks`, `--max-pages`. |
| `serpel billing history` | Lists credit transactions (`--limit`, `--page`, `--offset`). |
| `serpel billing plan` | Shows your plan, limits and subscription. |
| `serpel billing top-up` | Opens Stripe Checkout for a credit top-up. `--package credits500`, `credits1000`, `credits2500` or `credits5000`, or `--amount <cents>`. `--no-open` only prints the address. |
| `serpel billing subscribe --plan <plan>` | Opens Stripe Checkout for `starter` or `pro`. |
| `serpel billing change-plan --plan <plan>` | Changes the plan. Upgrades apply immediately, downgrades at the end of the billing period. |
| `serpel billing cancel`, `serpel billing resume` | Cancels the subscription at the end of the billing period, or withdraws the cancellation. |
| `serpel billing portal` | Opens the Stripe customer portal for payment details and invoices. |
| `serpel agents status` | Shows whether agents may spend credits, the daily limit and today’s usage. |
| `serpel agents allow` | Allows agents to spend credits through the MCP server. Needs the Starter or Pro plan. `--daily-limit <credits>` sets the limit at the same time. |
| `serpel agents deny` | Stops agents from spending credits. |
| `serpel agents daily-limit <credits>` | Sets the daily limit for agents. `--reset` restores the default. |
| `serpel providers status` | Shows the data providers, whether they are configured, daily budget and usage, and when each was last used. |

Top-ups, plan changes and the agent settings need the workspace owner role.

## Which permissions do the commands need?

A token can only run commands whose scope it has. Without the scope, the API answers with HTTP 403 and the CLI exits with code 4. The [REST API reference](https://serpel.app/docs/api) lists every scope.

| Commands | Scopes |
| --- | --- |
| `projects` (read), `reports` (list, show, read, preview), `search`, `analytics` and `integrations` (status and reports), `monitoring recipients list` | `projects:read` |
| `projects` (create, update, delete, members), connect and disconnect commands, `monitoring webhook-test`, `monitoring recipients add` and `remove`, `monitoring email-test`, `reports send` | `projects:write` |
| `crawl` (list, show, pages, page, compare), `audit` | `audit:read` |
| `crawl start` | `audit:write` |
| `rankings` (list, history, serp, summary, locations), `competitors` (list, compare, suggest), `ai` (prompts, history, status) | `rankings:read` |
| `rankings` (add, run, remove), `competitors` (add, remove), `ai` (add, run, remove), `monitoring run` | `rankings:write` |
| `keywords` (history, research-show, lists, list-show, items) | `keywords:read` |
| `keywords` (research, list-create, list-update, list-delete, items-add, items-remove, import, refresh) | `keywords:write` |
| `jobs` (status, list) | `audit:read` for crawls, `rankings:read` for rank checks, AI checks and monitoring runs |
| `jobs` (cancel, retry) | `audit:write` for crawls, `rankings:write` for the other jobs |
| `tasks` (list, show) | `tasks:read` |
| `tasks` (create, update, delete) | `tasks:write` |
| `recommendations` (list, show) | `recommendations:read` |
| `recommendations` (accept, dismiss, done, reopen) | `recommendations:write` |
| `scan` | `codebase:write` |
| `export` | `export:read` |
| `billing` (balance, estimate, history, plan), `agents status` | `billing:read` |
| `billing` (top-up, subscribe, change-plan, cancel, resume, portal), `agents` (allow, deny, daily-limit) | `billing:write` |
| `providers status` | Any valid token. |
| `tokens`, `auth profile` | A token with all permissions. A token restricted to projects can only create tokens for its own projects. |

## How do you wait for background jobs?

Crawls, rank checks, AI visibility checks and monitoring runs run in a worker. `crawl start`, `rankings run`, `ai run` and `monitoring run` print the job ID and return. With `--wait`, the CLI checks the job every two seconds until it succeeds, fails or is cancelled, and then prints a summary. `--timeout <seconds>` limits the wait and only works with `--wait`. `jobs status <jobId> --wait` follows a job you started earlier.

- If the job fails or is cancelled, the CLI exits with code 8 and prints the last error.
- If `--timeout` is reached, the CLI exits with code 10. The job keeps running on the server.
- Ctrl+C stops waiting and exits with code 130. The job keeps running.
- Short network errors while waiting are retried up to three times.
- `SERPEL_POLL_INTERVAL_MS` changes the check interval.

```bash
serpel crawl start --project "$PROJECT_ID" --wait --timeout 900
serpel jobs status "$JOB_ID" --wait
```

## How does pagination work?

List commands accept `--limit <n>` (1 to 200, default 50) and either `--page <n>` (from 1) or `--offset <n>`. When more entries exist, the CLI prints the range and the flags for the next page on standard error. With `--json`, the total is in `pagination.total`.

## How do you use JSON output in scripts?

With `--json`, the CLI writes only JSON to standard output. Hints, warnings and progress go to standard error, and colours are off. Lists come as `{ "data": [...], "pagination": { "limit", "offset", "total" } }`. Single objects and actions return the object itself. Errors are printed to standard output as an object and set the exit code.

```json
{
  "error": {
    "code": "not_logged_in",
    "message": "Not signed in to https://serpel.app."
  }
}
```

API errors keep the code of the API, such as `validation_error` or `unauthorized`. Local errors use their own codes: `usage_error`, `not_logged_in`, `network_error`, `timeout`, `job_failed`, `job_cancelled`, `wait_timeout`, `interrupted` and `webhook_failed`. Some errors add a `details` field.

```bash
serpel projects list --json | jq -r '.data[] | "\(.id) \(.domain)"'
PROJECT_ID=$(serpel projects list --json | jq -r '.data[] | select(.domain == "example.com") | .id')
JOB_ID=$(serpel crawl start --project "$PROJECT_ID" --json | jq -r .job.id)
```

## What do the exit codes mean?

| Code | Meaning |
| --- | --- |
| 0 | Success. |
| 1 | Unexpected error, HTTP 500 `internal_error`, an invalid API response, or a webhook test message that was not delivered (`webhook_failed`). |
| 2 | Usage error such as an unknown flag, a missing argument or an invalid value, or `validation_error` and `bad_request` from the API. Also a destructive command without `--yes`. |
| 3 | Not signed in, HTTP 401, or a device login that expired or was denied. |
| 4 | HTTP 403: a missing scope, no access to the project, or token management with a restricted token. |
| 5 | HTTP 404: not found. |
| 6 | HTTP 409 conflict, such as a project that already exists or `jobs retry` on a running job. Also HTTP 402 `insufficient_credits`. |
| 7 | Provider problem: HTTP 429 (`rate_limited`, `budget_exceeded`), 502 (`provider_error`) or 503 (`provider_not_configured`). |
| 8 | A job failed or was cancelled while waiting with `--wait`. |
| 9 | The API is not reachable: connection error, DNS, TLS, request timeout or an unexpected redirect. |
| 10 | The `--wait --timeout` limit was reached. The job keeps running. |
| 130 | Interrupted with Ctrl+C, or any answer but yes to the `scan` confirmation. |

## How do you run the CLI in GitHub Actions?

Create a token with only the scopes the job needs, restrict it to one project and store it as the repository secret `SERPEL_TOKEN`. The CLI reads that variable on every run and never stores it.

```bash
serpel tokens create --name "GitHub Actions" --scope projects:read --scope audit:read --scope audit:write --project "$PROJECT_ID" --expires-in-days 90
```

The workflow below crawls the site every Monday, waits for the crawl and fails when the latest crawl contains errors. The install step pulls `@serpel/cli` from npm; pin a version such as `@serpel/cli@0.1.0` if you want reproducible builds.

```yaml
name: Serpel crawl
on:
  schedule:
    - cron: "0 6 * * 1"
  workflow_dispatch:
jobs:
  crawl:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - name: Install the Serpel CLI
        run: npm install -g @serpel/cli
      - name: Crawl the site and fail on errors
        env:
          SERPEL_TOKEN: ${{ secrets.SERPEL_TOKEN }}
          SERPEL_PROJECT_ID: ${{ vars.SERPEL_PROJECT_ID }}
        run: |
          serpel crawl start --project "$SERPEL_PROJECT_ID" --wait --timeout 900
          serpel audit issues --project "$SERPEL_PROJECT_ID" --json > issues.json
          jq -e '.counts.error == 0' issues.json
```

> **Crawls use credits:** Every crawl is billed per 20 pages. Run `serpel billing estimate --operation crawl --max-pages 500` before you schedule one, and set `--max-pages` to match your site.

## How do you troubleshoot the CLI?

- `--verbose` prints the method, URL, status and duration of every request to standard error, without headers, bodies or the token.
- `serpel config get` shows which API address applies and where it comes from.
- “Endpoint not found (HTTP 404)” or “The API response is not valid JSON” usually means a wrong API address. Run `serpel config get` and check `--api-url` and `SERPEL_API_URL`.
- If the system keyring is not available, for example on Linux without a running Secret Service, the CLI stores the token in `credentials.json` and prints a warning.
- Exit code 3 means the token is missing, invalid, expired or revoked. Sign in again or create a new token.