CLI

cowl wraps the whole REST API in a single static binary. Every REST operation is a command, and every command uses the same cowl_pat_ agent key and permissions as MCP. On a terminal it prints tables and prose. In a pipe it prints one line of JSON, so scripts and agents can parse it.

Install

On Linux or macOS, install the latest verified release to ~/.local/bin:

installer="$(mktemp)"
curl -fsSL https://github.com/ContextOwl/cowl/releases/latest/download/install.sh -o "$installer"
sh "$installer"
rm "$installer"
export PATH="$HOME/.local/bin:$PATH"
cowl version

The installer selects the right amd64 or arm64 archive and verifies it against SHA256SUMS. Set COWL_INSTALL_DIR to install somewhere else. Windows archives are available from ContextOwl/cowl releases.

Or install from source with go install github.com/ContextOwl/cowl/cmd/cowl@latest.

Authenticate

Create an agent key in Admin > Settings > API, then:

cowl auth login                                       # prompts for the key (input hidden), verifies it, stores it 0600
cowl auth login --with-token -w platform < key.txt   # reads the key from stdin and saves a default workspace
cowl auth status                                      # shows the base URL and key prefix, and checks the key
cowl whoami                                           # shows the role, workspaces, and permissions of the key, and whether it reads drafts

For another ContextOwl host, add --base-url to cowl auth login. cowl saves the base URL with the key and sends the saved key only to that host. Only cowl auth login takes --base-url.

Scripts and CI can skip the config file. Set CONTEXTOWL_PAT, add CONTEXTOWL_BASE_URL for another host, and add CONTEXTOWL_WORKSPACE for an org-wide key. A key from the environment goes only to CONTEXTOWL_BASE_URL or to the default host. cowl refuses any other combination with untrusted_host before it sends a request. The COWL_PAT, COWL_BASE_URL, COWL_WORKSPACE, and COWL_CONFIG names also work.

A workspace-bound key needs no -w at all. The default, -, means "my bound workspace".

Work with content

cowl workspaces list
cowl search "agent keys" --limit 5 -w platform
cowl search "key rotation" --question "How do I rotate my agent key?" -w platform
cowl articles list --updated-since 7d -w platform          # changed in the last 7 days, newest first
cowl articles get mcp api-keys -w platform                  # front matter, then Markdown
cowl articles get api-keys --section rotation -w platform   # one heading and its text
cowl sections list -w platform
cowl articles create --title "Release Notes" --file notes.md --section-key reference -w platform
cowl articles update release-notes --edits edits.json --base-revision 8f3a2c1b9d0e -w platform
cowl articles update release-notes --status STABLE -w platform
cowl uploads image architecture.png -w platform
cowl changelog create --title "v2 ships" --file v2.md --tags new,improved --status published -w platform
cowl changelog list --since 30d -w platform
cowl changelog get 42 -w platform
cowl proposals create --slug getting-started --edits fix.json --note "Fix the install command" -w platform
cowl proposals get 12 -w platform

cowl articles get prints YAML front matter and then the Markdown. The front matter has the status, revision, url, and anchors of the article. Pass one of the anchors to --section to read one heading.

--question on cowl search sends the question of the user, so the docs team sees what agents ask. Leave out names, email addresses, and secrets. The search uses the query, not the question.

cowl search shows a LEARNED column when a hit has learned: true: people and agents opened that page after similar searches, so it ranks higher. The JSON output passes the field through.

--edits takes a JSON file, or - for stdin, with a list of {"old", "new"} objects. Each old text must occur exactly once in the article. Add --base-revision with the revision of a full read, so the change fails with stale_revision when somebody changed the article after you read it.

The changelog tags are new, improved, fixed, deprecated, and security. The server also takes aliases such as added, feature, changed, and fix.

Landing pages and the OpenAPI ingest feature have full command groups too. cowl openapi spec prints the stored spec. Run cowl help for the map. Destructive commands prompt for confirmation, or take --yes in scripts: workspaces delete, changelog delete, and openapi detach, which deletes the generated pages.

To see what AI agents asked a workspace and which questions went unanswered, run cowl insights -w platform. Add --days 90 for a longer range, and --principal key for agent keys only. To list every question, run cowl analytics questions -w platform --unanswered, and add --csv to save the list as a spreadsheet. To read the analytics report, run cowl analytics report -w platform --days 7, and for the next steps, run cowl analytics next -w platform. The report also lists the pages and searches that people and AI agents ask for more than before, and the next steps list the rising topics. These commands need the analytics.read permission. To send a question that the docs didn't answer to the docs team, run cowl analytics gap -w platform "How do I rotate a key?". ContextOwl keeps search text and questions for 90 days.

Writes that wait for review

When your organization reviews agent changes, a write that changes content that readers see waits in a proposal for an editor. cowl then prints the proposal and its review link, for example proposal 42 waits for review: https://contextowl.co/admin/proposals?ws=platform&id=42, and exits 0. In a pipe it prints the REST body, which has pendingReview set to true. Give the link to the person who approves changes, and don't run the command again.

The OpenAPI commands cowl openapi attach, sync, detach, and place can wait for review too.

Pass --note to tell the reviewer what the change does. cowl workspaces delete fails with review_required and exits 4, unless an admin approved the key as a publishing key. cowl whoami shows how the writes of the key apply.

Agent mode

When stdout isn't a terminal, every command that prints a table or a receipt prints one line of compact JSON instead: the REST response body. Add --json to get the same line on a terminal. cowl articles get, cowl changelog get, and cowl openapi spec print their content in both cases, and JSON only with --json.

When stderr isn't a terminal, an error is one JSON line in the error envelope of the API:

{"error":{"code":"stale_revision","message":"the article changed after revision 8f3a2c1b9d0e. Read it again and redo the change","status":409,"details":{"baseRevision":"8f3a2c1b9d0e","currentRevision":"1a2b3c4d5e6f"}}}

A failure that isn't an HTTP error has status 0 and a code such as usage, no_key, untrusted_host, or network_error. On a terminal, an error prints as cowl: <code>: <message>.

Exit code Meaning
0 Success
1 Other error
2 Usage error, or HTTP 400, 413, or 422
3 HTTP 404
4 No key, untrusted host, or HTTP 401, 402, or 403
5 HTTP 409
6 HTTP 429 after one retry
7 HTTP 5xx or a network failure

cowl retries a 429 once, after the time in Retry-After.

Use cowl with a coding agent

To teach a coding agent to use cowl, install the agent skill:

npx skills add ContextOwl/cowl

The skill tells the agent to search before it answers, to read single sections, to cite URLs, and to send doc fixes as proposals that an editor reviews. It pre-approves the read commands only.

When cowl runs inside an agent, it names the agent in the ContextOwl-Agent header, so analytics counts the call as that agent and not as cowl. Inside Claude Code, cowl sends claude-code. Set COWL_AGENT to send another name, or COWL_AGENT=none to send none. cowl doctor prints the name that cowl sends.

Scripting

In a pipe, cowl prints the raw API response, so it composes with jq:

# slugs of every draft, with a key that reads drafts
cowl articles list --status DRAFT | jq -r '.[].slug'

# anything the CLI has no verb for yet: the escape hatch
cowl api GET "workspaces/platform/search" --query q=sso --query semantic=true

Troubleshooting

Run cowl doctor. It checks the config file, the base URL, and the key source, then makes one call to GET /api/v1/me. The report never prints the key, its prefix, names, hosts, or paths, so you can paste it into an issue.

Completions

eval "$(cowl completion bash)"                                # bash
cowl completion zsh > "${fpath[1]}/_cowl"                     # zsh
cowl completion fish > ~/.config/fish/completions/cowl.fish   # fish