toolkit

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.mjs

Check what you have, values masked:

node scripts/setup-keys.mjs --status

Or 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:

  1. an explicit command option supplied in your own terminal
  2. the shell environment (export / direnv / CI)
  3. the repo-root .env (gitignored — never shared, never committed)
  4. the capability's own private local config

The rule behind all of it, and what to do if a key leaks:API-key hygiene.