Getting your API keys
Every key is per-person and optional — most skills need none at all, and the ones that do still run in a reduced mode without them. This page says where each one comes from and what you lose by skipping it. If you're just getting started, skip this page — try asking for something first; you'll only ever see this if a skill you installed actually asks for a key it needs.
Never paste a key into an agent chat. It lands in the transcript and scrollback for good, and the only remedy is rotating it at the provider. Type keys in your own terminal.
Agents: report which keys are missing by name with --status and hand the step back.
The fastest way
An interactive helper that creates .env, prompts for each key, hides input, and keeps anything you already have:
node scripts/setup-keys.mjsCheck what you have, values masked:
node scripts/setup-keys.mjs --statusOr ask OpenCode to explain what is missing without exposing any values:
Check Toolkit setup. List only missing connections, what each unlocks, and where I can obtain it. Never display secret values.Copy these — same for everyoneSame for everyone here — the value is below, paste it.
FREESCOUT_API_URLFreeScout instance
- Value
https://helpdesk.nkpapps.com- Used by
- Helpdesk (FreeScout), with FREESCOUT_API_KEY
- Without it
- Helpdesk (FreeScout) is INERT — both are required together.
- Note
- The instance you already log into. Helpdesk (FreeScout) appends /api itself, so no trailing path.
OUTLINE_API_URLOutline wiki API
- Value
https://wiki.nkpapps.com/api- Used by
- the wiki layer, with OUTLINE
- Without it
- The wiki layer stays off and every skill falls back to its bundled local playbook.
MONDAY_BOARD_SUBSCRIPTIONSmonday.com — Subscriptions board id
- Value
5949979567- Where
- the number in the board's URL: monday.com/boards/<THIS>
- Used by
- Hosting & DNS (commercial standing) · Analytics & tags (entitlement gating)
- Without it
- Hosting & DNS shows infrastructure only; every skill treats every signal as in scope, which is how they behaved before this existed.
- Note
- Board IDs are CONFIG, never baked into the toolkit — they identify one company's workspace and the skills stay company-neutral. This is the only REQUIRED board: account status is derived from the subscription rows, never read from the deprecated Account Status column.
MONDAY_BOARD_ACCOUNTSmonday.com — Accounts board id (optional)
- Value
18403801770- Where
- the number in the Accounts 3.0 board's URL
- Used by
- Hosting & DNS (adds CS owner, shirt size, SEO hours)
- Without it
- Hosting & DNS still derives account status from subscriptions — this board only adds the owner and sizing.
- Note
- Its own `Account Status` column is DEPRECATED and is deliberately never read: an account can read “Active” while every product that earns is in notice.
MONDAY_BOARD_CONTENTmonday.com — Content & Strategy Guide board id (optional)
- Value
5929854886- Where
- the number in the Content and Strategy Guide 2.0 board's URL
- Used by
- Hosting & DNS (adds the people roster)
- Without it
- Hosting & DNS omits the roster; nothing else changes.
- Note
- Adds specialist / tech SEO / writer / editor / content dev, i.e. who to actually ask about this domain.
Get these yourselfCreate it at the provider yourself, right now.
PAGESPEED_API_KEYPageSpeed Insights
- Where
- console.cloud.google.com → enable "PageSpeed Insights API" → Credentials → API key
- Used by
- Performance (Core Web Vitals) (Core Web Vitals), Chrome UX Report field series (Chrome UX Report field series)
- Cost
- Free, generous quota.
- Without it
- Performance (Core Web Vitals) falls back to --local: synthetic lab metrics, no real-user field data. Chrome UX Report field series is SKIPPED (or uses the --bq BigQuery fallback).
WPSCAN_API_KEYWPScan
- Where
- https://wpscan.com/api
- Used by
- WP plugins & theme (plugin/theme vulnerability lookup)
- Cost
- Free tier: 25 requests/day.
- Without it
- WP plugins & theme still grades freshness (outdated / abandoned); only the vulnerability check is skipped.
OPENAI_API_KEYOpenAI
- Where
- https://platform.openai.com/api-keys — create a RESTRICTED key (List models: Read, Responses: Write)
- Used by
- AI search visibility (required) · Meta title & description writer · Content brief · Readability rewriter
- Cost
- A ~12-query AI search visibility run is well under $1.
- Without it
- The content lane falls back to opencode and needs no key. AI search visibility's ChatGPT engine cannot run.
- Note
- Ours are restricted and text-only. Model choice and the per-skill compatibility table: docs/openai-api-keys.md.
GEMINI_API_KEYGoogle Gemini
- Where
- https://aistudio.google.com/apikey
- Used by
- AI search visibility (required) · content-lane fallback · Alt-text writer (vision)
- Cost
- Search grounding is free up to 5k prompts/month.
- Without it
- AI search visibility's Gemini engine cannot run. Alt-text writer and the content lane fall back to opencode.
GEMINI_KEY_IMAGEGENGemini image generation
- Where
- https://aistudio.google.com/apikey — a SEPARATE key from GEMINI_API_KEY
- Used by
- scripts/mascot-gen.mjs — a maintainer script, not a skill
- Cost
- Image models are billed differently from text.
- Without it
- You cannot regenerate skill mascots. Nothing else is affected.
SEMRUSH_API_KEYSEMrush Analytics API
- Where
- semrush.com → Subscription info → API units
- Used by
- SEMrush research · Keyword research & planning (keyword research)
- Cost
- Spends SEMrush API units. Keyword research & planning --units estimates a run before it spends any.
- Without it
- Both skills are INERT — they exit 0 and do nothing.
MONDAY_API_TOKENmonday.com
- Where
- monday.com → your avatar → Administration → API (or Developers → My Access Tokens)
- Used by
- Task board (monday.com) (task board) · Hosting & DNS's Monday enrichment · Analytics & tags entitlements
- Without it
- Any skill reading it is INERT.
- Note
- Use your OWN personal token, never a shared one: a personal token inherits YOUR board permissions, so a skill cannot show you boards you could not already open. A shared token would silently widen access for everyone.
OUTLINEOutline wiki token
- Where
- https://wiki.nkpapps.com → Settings → API Tokens. A READ-ONLY token is ideal. MCP alternative: https://wiki.nkpapps.com/mcp (Streamable HTTP, Google SSO) via OUTLINE_MCP_URL — see docs/wiki-mcp.md.
- Used by
- the optional wiki layer — 27 skills link a team playbook into their report
- Without it
- Every skill runs identically and falls back to its bundled local playbook. Nothing fails.
- Note
- Skills only ever READ. Cached to ~/.toolkit/wiki-cache (24h TTL). Only docs flagged AI-Safe are surfaced. Transport: REST (OUTLINE+OUTLINE_API_URL) is the toolkit seam; MCP (OUTLINE_MCP_URL) is the per-user OAuth alternative — outline.mjs prefers MCP when OUTLINE_MCP_URL is set and falls back to REST when it is not (issue #563). Governance 2026-09-02: MCP does NOT enforce AI-Safe server-side, so the client-side isAiSafe gate stays as a second check on either transport. Own-client: claude mcp add --transport http nkp-wiki https://wiki.nkpapps.com/mcp.
OUTLINE_MCP_URLOutline wiki MCP
- Value
https://wiki.nkpapps.com/mcp- Where
- No token to mint — sign in with Google via the MCP OAuth flow (Streamable HTTP, Google SSO). Toolkit: set OUTLINE_MCP_URL=https://wiki.nkpapps.com/mcp; own-client: claude mcp add --transport http nkp-wiki https://wiki.nkpapps.com/mcp — see docs/wiki-mcp.md.
- Used by
- the wiki layer, with or without OUTLINE — own-client and toolkit alternative
- Without it
- The wiki layer uses OUTLINE+OUTLINE_API_URL (REST) — nothing fails; either transport enables it.
- Note
- Per-person OAuth, no long-lived secret on disk. Enables the toolkit's MCP→REST fallback (outline.mjs prefers MCP when set). Both transports honour AI-Safe client-side; MCP keeps the gate as a second check until the server enforces it (governance 2026-09-02). Never write a token into .mcp.json.
WP_APP_PASSWORDWordPress application password
- Where
- The site's WordPress → Users → Profile → Application Passwords (WP 5.6+)
- Used by
- Meta title & description writer · Alt-text writer · Content brief (only with --apply, to publish)
- Without it
- Publishing is unavailable; the skills still produce the change for you to apply by hand.
- Note
- An APPLICATION password, NOT the login password. Publishing is dry-run until --apply; every apply writes a rollback.json and --rollback undoes it. See docs/write-approval.md.
MONDAY_BOARD_TASKSmonday.com — task board id (Task board (monday.com))
- Where
- the number in the board's URL: monday.com/boards/<THIS>
- Used by
- Task board (monday.com) (task board overview)
- Without it
- Task board (monday.com) is INERT unless --mcp-json supplies an agent-captured MCP payload instead.
- Note
- Separate from the three account-enrichment boards (Subscriptions/Accounts/Content), which are joined by domain and have no task semantics.
SF_VM_MCP_TOKENScreaming Frog crawler VM (MCP)
- Where
- The crawler's Account page → Generate MCP token. Shown once, and generating a new one revokes the previous — so take it when you generate it.
- Used by
- Site crawl (Screaming Frog) (the CRAWL path only — triage of an existing export needs no key)
- Without it
- Site crawl (Screaming Frog) runs triage-only against a Screaming Frog CSV export someone else produced. Crawling is the only thing that needs it.
- Note
- Per-person, not shared: crawls are attributed to whoever ran them. The HOST is committed in mcp.json and scripts/lib/mcp-registry.json — it is configuration, not a secret — and those files reference this variable as ${SF_VM_MCP_TOKEN}, so the token itself never lands in a config file. Wire it with `Setup & doctor mcp` (issue #307).
GOOGLE_PLACES_API_KEYGoogle Places / Business Profile Reviews (replacement)
- Where
- console.cloud.google.com → enable Places API (or Business Profile API) → Credentials → API key (restrict to Places/Business Profile)
- Used by
- google-reviews (owned replacement — rating/text/attribution, freshness, failure states)
- Cost
- Places Details is billed per request; one fetch per build/window.
- Without it
- google-reviews is SKIPPED — set GOOGLE_PLACES_API_KEY in the toolkit .env to enable — component not rendered, correctly omitted.
Ask an adminProvided to you — do not self-generate. Ask an admin.
FREESCOUT_API_KEYFreeScout helpdesk
- Where
- Ask an admin — you do not self-generate this one.
- Used by
- Helpdesk (FreeScout) (read-only queue overview)
- Without it
- Helpdesk (FreeScout) is INERT.
- Note
- Sent as the X-FreeScout-API-Key header. Read-only — Helpdesk (FreeScout) only ever GETs. Setup: skills/ops/Helpdesk (FreeScout)/references/freescout-api.md.
OPENCODE_API_KEYopencode provider
- Where
- Ask an admin for the team's opencode-go provider key and its base URL.
- Used by
- the content-lane DEFAULT — Meta title & description writer · Content brief · Readability rewriter · Alt-text writer
- Without it
- If you have the opencode CLI installed and logged in (`opencode auth login`), the whole content lane runs on your own subscription with NO key here at all — that is the normal setup.
- Note
- Only needed for a hosted OpenAI-compatible endpoint. Set OPENCODE_BASE_URL with it.
OPENCODE_BASE_URLopencode endpoint
- Where
- Ask an admin — it comes with the key. For a local Ollama instead, use http://localhost:11434/v1
- Used by
- the content lane, with OPENCODE_API_KEY
- Without it
- The content lane uses the opencode CLI, or OpenAI/Gemini if those are set.
SUPERAUDIT_API_KEYSuperAudit engine
- Where
- Ask the SuperAudit engine admin — it comes with the endpoint.
- Used by
- SuperAudit
- Without it
- SuperAudit is INERT. The native skills (On-page SEO, Structured data (schema), Link & image checker, Accessibility (a11y), Analytics & tags) cover page audits without it.
SUPERAUDIT_API_URLSuperAudit endpoint
- Where
- Ask the SuperAudit engine admin — no host is baked in.
- Used by
- SuperAudit, with SUPERAUDIT_API_KEY
- Without it
- The skill is inert. Both are required together.
GOOGLE_SERVICE_ACCOUNT_JSONGoogle service account (Search Console + GA4 + Business Profile)
- Where
- Ask for the shared service-account key file, then set this to its path. One account serves the whole team — do not create your own.
- Used by
- Search Console series · GA4 behaviour series · Google Business Profile client
- Without it
- All three clients are INERT (exit 0) unless you've signed in via GOOGLE_OAUTH_CLIENT_ID instead. Every other skill is unaffected.
- Note
- A PATH to a JSON key file, not a key. Signs a JWT locally and trades it for a token — no browser, no consent screen, no refresh token on disk, which is what lets these run in cron and CI. The one manual step is granting the account's client_email read access on each property/location. Run `Search Console series --sites` to see exactly what it can read. Full setup: docs/google-credentials.md
GOOGLE_OAUTH_CLIENT_IDGoogle OAuth client (GA4 + Search Console + GTM + Gmail + Drive + GBP, per-person)
- Where
- Ask for the shared OAuth client id/secret pair (registered once for the whole team in Google Cloud Console — see docs/google-credentials.md). This is not a personal credential; it identifies the toolkit itself to Google.
- Used by
- Search Console series · GA4 behaviour series · GTM audit and fix · Google Business Profile client · google-login
- Without it
- Search Console series/GA4 behaviour series/Google Business Profile client fall back to GOOGLE_SERVICE_ACCOUNT_JSON if that's set; GTM audit and fix and Gmail/Drive access have no fallback and stay inert.
- Note
- ADR-0004 (docs/adr/0004-oauth-additive-google-credential-path.md). Setting this alone does nothing per-person — after it's set, each teammate runs `node scripts/google-login.mjs login` once to sign in as themselves; that produces a token stored only on their own machine (~/.config/toolkit/google-oauth.json), never in .env. `google-login.mjs status` shows what's granted; `logout` revokes it. For GBP add scope https://www.googleapis.com/auth/business.manage when logging in: `node scripts/google-login.mjs login --scopes gbp`.
GOOGLE_OAUTH_CLIENT_SECRETGoogle OAuth client secret (pairs with GOOGLE_OAUTH_CLIENT_ID)
- Where
- Comes with GOOGLE_OAUTH_CLIENT_ID from the same Console registration — set both together.
- Used by
- Search Console series · GA4 behaviour series · GTM audit and fix · Google Business Profile client · google-login
- Without it
- Same as GOOGLE_OAUTH_CLIENT_ID — both are required for the OAuth path to activate at all.
- Note
- Not sensitive the way a personal API key is — Google's own guidance treats installed-app client secrets as identifying the app, not securing it. The real security boundary is the per-person token google-login.mjs produces, not this value.
CLOUDCLI_MCP_TOKENCloud CLI Execution API (remote gcloud + bq)
- Where
- Ask an admin for the Cloud CLI Execution API OAuth token (roles/mcp.toolUser) — Gcloud & BigQuery CLI falls back to the local gcloud/bq CLIs without it, same guardrails.
- Used by
- Gcloud & BigQuery CLI (remote cloudcli path) · Chrome UX Report field series --bq via shared gcp-acl
- Without it
- Gcloud & BigQuery CLI runs locally via the gcloud/bq CLIs (same ACL/scan caps); the remote MCP path is unavailable.
- Note
- The HOST is committed in mcp.json / scripts/lib/mcp-registry.json as https://cloudcli.googleapis.com/mcp (configuration, not a secret); only the token lives here, referenced as ${CLOUDCLI_MCP_TOKEN} so it never lands in a config file. Gcloud & BigQuery CLI's local fallback keeps the skill working with no token.
Leave blank — these already have a defaultLeave blank unless you are overriding. The default is below.
TOOLKIT_OPENCODE_MODELopencode text model
- Value
opencode-go/deepseek-v4-flash- Used by
- the content lane
- Without it
- The default above is used.
TOOLKIT_OPENCODE_VISION_MODELopencode vision model
- Value
opencode-go/minimax-m3- Used by
- Alt-text writer (alt text)
- Without it
- The default above is used.
- Note
- Must be VISION-capable. For a local Ollama use gemma3:4b — gemma4 is text-only and silently will not work.
TOOLKIT_ALLOW_ALL_MODELSmodel cost guard
- Value
(unset — the guard is on)- Used by
- the content lane
- Without it
- Only cost-effective models are allowed, which is what you want. Set to 1 for a one-off larger run.
TOOLKIT_WRITE_LOGlocal write log
- Value
~/.toolkit/write-log.jsonl- Used by
- the write lane
- Without it
- The default path above is used.
SEMRUSH_API_URLSEMrush endpoint
- Value
(unset — SEMrush's public API host)- Used by
- SEMrush research · Keyword research & planning
- Without it
- The public host is used. Override only for a proxy or a regional endpoint.
OLLAMA_API_KEYOllama provider
- Value
dummy- Used by
- opencode itself · Image optimizer --alt when its provider is opencode
- Without it
- Nothing, unless your ~/.config/opencode/opencode.json references it.
- Note
- A local Ollama needs no real key — the literal string `dummy` satisfies the provider config. If your opencode uses a different provider, set THAT provider's key instead.
TOOLKIT_DRIVE_MAPShared Drive mapping (domain → driveId)
- Value
(unset — set to '{"example.com":"driveId"}' or use data/client-drives.json)- Used by
- drive-upload (audit outputs → client's Shared Drive, #342)
- Without it
- drive-upload is dry-run only and warns about the missing mapping; local reports are unaffected.
- Note
- Per-decision A: per-client Shared Drive (not My Drive). Identity-bound via OAuth (google-login --scopes drive, scope https://www.googleapis.com/auth/drive). Env JSON takes precedence over data/client-drives.json. Offline tests use DRIVE_FIXTURE. See scripts/lib/drive-upload.mjs.
TOOLKIT_ROLEyour role here (reader | contributor | maintainer)
- Value
(unset — ~/.toolkit/role is read instead, and no file at all means reader)- Used by
- the shared-kits setup (scripts/setup-kits.mjs) and the doctor
- Without it
- You are treated as a reader: you are offered the design kit and never the maintainers-only dev kit.
- Note
- Normally recorded once with ./install.sh --role <role> or node scripts/setup-kits.mjs --role <role>. It is per-person, not per-checkout, so a second clone inherits it. Setting the variable overrides the file for one command.
DESIGN_KIT_ROOTa design-kit checkout you keep elsewhere
- Value
(unset — ~/.toolkit/kits/design-kit is used)- Used by
- report-kit's palette check and the doctor's shared-kits report
- Without it
- Nothing changes: the design kit is optional and every report renders identically from report-kit's own mirror of the palette.
- Note
- Get the kit with: git clone git@github.com:Toolkit-os/design-kit.git ~/.toolkit/kits/design-kit (readable by every team). When a checkout resolves, report-kit/sync.mjs holds the mirror to it strictly and compares its revision to report-kit/DESIGN_KIT_PIN.
DEVKIT_ROOTa devkit checkout you keep elsewhere
- Value
(unset — ~/.toolkit/kits/devkit is used)- Used by
- the shared-kits setup and the doctor, for maintainers
- Without it
- Nothing changes: the dev kit is engineering reference material, not a dependency of anything the toolkit runs.
- Note
- Readable by contributors and maintainers only: git clone git@github.com:Toolkit-os/devkit.git ~/.toolkit/kits/devkit. If access is refused, ask a maintainer to add you to the Toolkit GitHub team.
Yours — changes per site or personDepends on the site or person you are working as.
WP_URLWordPress site
- Where
- The client site you are publishing to. Per-site, so it changes as you switch clients.
- Used by
- the write lane — Meta title & description writer · Alt-text writer · Content brief, only with --apply
- Without it
- Every write-lane skill still runs and hands you the change to apply by hand.
WP_USERWordPress user
- Where
- Your own username on that site — the one the application password belongs to.
- Used by
- the write lane, with WP_URL and WP_APP_PASSWORD
- Without it
- As WP_URL — output-only.
Where keys live, and which wins
A skill reads a key in this order — first hit wins, so exporting a variable always overrides the file, which is what CI wants:
- an explicit command option supplied in your own terminal
- the shell environment (
export/ direnv / CI) - the repo-root
.env(gitignored — never shared, never committed) - the capability's own private local config
The rule behind all of it, and what to do if a key leaks:API-key hygiene.