Every command follows one contract, so a script or an agent can parse any of them the same way.
stdout and stderr
- stdout carries data only. Progress, warnings, notices and errors go to stderr.
- No colors, spinners or pagers. On a terminal you get readable tables; in a pipe you get JSON.
- The CLI never prompts. A batch command (an import, update, remove, tag or
--eachwrite) takes--yes; without it, the command plans or refuses. A single generated write, such askenso tags delete --tag-id 7, and a write throughkenso apisend at once: add--dry-runto see the request first, then run the command without it.
Formats
| What the command returns | stdout is a terminal | stdout is a pipe or file |
|---|---|---|
| One object | key-value lines | compact JSON on one line |
| A list | a table (first 8 scalar fields, cells cut at 60 characters) | NDJSON: one JSON object per line |
| A batch run | key-value lines | one JSON summary object |
| No content (HTTP 204) | nothing | nothing |
-o or --output picks the format: json, ndjson, table or ids.
-o jsonon a list prints one JSON array. On a terminal,-o jsonpretty-prints an object.-o idsprints one id per line, ready for--ids -. Items without anidare skipped with one warning.
kenso campaigns list --limit 3
kenso campaigns list --limit 3 -o json
kenso contacts list --campaign-id 812 --all -o ids
Shape the output
--fields a,b.ckeeps only those fields (dots reach into nested objects).--query <JMESPath>filters or reshapes in-process, so you need nojq. On a list it sees the whole list.- Neither applies to a batch summary, which always prints in full.
kenso campaigns list --fields id,name,status
kenso campaigns list --all --query "[?status=='active'].id"
kenso me get --query "length(features)"
Lists and pages
A list command prints one page. When there are more, it prints the command for the next page on stderr, for example next: kenso tags list --limit 1 --cursor MQ. --all follows every page and streams the items. --limit sets the page size and --cursor starts at a cursor.
kenso tags list --limit 50
kenso tags list --all
Errors
When stderr is not a terminal, an error is one JSON line on stderr:
{"error":{"code":"unknown_flag","message":"Unknown flag --fiel for kenso contacts import.","field":"fiel","hint":"did you mean --file?","next":"kenso contacts import --help"}}
| Key | Always there | Meaning |
|---|---|---|
code | yes | A stable snake_case code to branch on |
message | yes | One sentence for a person |
field | no | The flag or input field at fault |
hint | no | What to change |
next | no | The exact command to run next, such as kenso auth login, kenso workspace list --q <name> or the same command with --yes |
httpStatus | no | The API's HTTP status, when the API answered |
requestId | no | The API's request id: quote it when you contact Kenso support |
On a terminal the same error prints as text: Error: <message>, then field:, hint:, next: and requestId: lines. The key never appears in an error.
How API errors map
The CLI looks at the HTTP status first, then at the API's own error.code, which wins when present.
| HTTP status | Default code | Exit |
|---|---|---|
| 400, 422 | validation_failed | 2 |
| 401 | unauthorized (next: kenso auth login) | 3 |
| 403 | forbidden: the key's user or role cannot do this in this workspace | 3 |
| 404 | not_found | 4 |
| 409 | conflict | 7 |
| 429 | rate_limited, after the CLI waited and retried | 6 |
| 5xx | server_error, after retries where they are safe | 8 |
| anything else | http_error | 1 |
Two API codes change the exit code: workspace_required exits 2 with "next":"kenso workspace list --q <name>", and limit_reached (the plan's or the campaign's lead limit) exits 6.
Errors from the CLI itself
| Code | Exit | When |
|---|---|---|
usage_error | 2 | A required flag or argument is missing, or two flags conflict (--yes with --dry-run) |
unknown_command | 2 | No such command; hint suggests the closest one |
unknown_flag, invalid_flag, invalid_flag_value, unexpected_argument | 2 | A flag or argument the command does not take |
invalid_query | 2 | --query is not valid JMESPath |
file_unreadable | 2 | An input file is missing or cannot be parsed |
config_invalid | 2 | ~/.kenso/config.json is not valid JSON: fix or delete it |
feature_not_enabled | 2 | Every action of the resource is a beta command your workspace has not enabled |
invalid_key_format, key_required | 2 | auth login --with-key read something that is not a Kenso key, or stdin is a terminal instead of a pipe |
invalid_workspace_id | 2 | kenso workspace use got an id that is not a positive whole number |
unknown_topic | 2 | kenso docs got a topic it does not have; hint lists the topics |
file_unwritable | 2 or 1 | An output file cannot be written: contacts export --out (2), skill install (1) |
no_api_key | 3 | No key in KENSO_API_KEY or the profile (next: kenso auth login) |
browser_login_unavailable, access_denied, login_expired | 3 | Browser login is off, was denied, or timed out |
run_not_found | 4 | kenso runs show or resume got an unknown run id |
docs_not_found | 4 | kenso docs reference before the reference was generated (a source checkout without pnpm build) |
run_in_progress, already_filled | 7 | Another process is working on the run, or studio fill already ran with this file and these flags |
skill_exists | 7 | kenso skill install found a different SKILL.md; next adds --force to replace it |
job_timeout, job_failed, job_canceled | 1 | kenso jobs wait (or a command that waits on a job) ran out of time, or the job failed or was canceled |
mcp_tool_error | 1 | kenso mcp call: the MCP tool answered with an error |
unsafe_path | 1 | studio pull refused a file path that would land outside --dir |
network_error | 8 | The API could not be reached after retries |
upload_failed, download_failed | 8 or 1 | A signed upload or download of a Studio file failed: 8 when storage was unreachable or answered 5xx, 1 on a 4xx (often an expired link: run the command again) |
pull_incomplete | 8 | studio pull: a file came back with neither text nor a download link |
unexpected_response | 1 or 8 | The API answered something the CLI could not read |
unexpected_error | 1 | A bug: run again with --verbose and report it |
interrupted | 130 | Ctrl-C |
Exit codes
| Code | Meaning |
|---|---|
| 0 | OK, including a batch where every row succeeded or was skipped |
| 1 | Unexpected error |
| 2 | Usage or input error: a bad flag, a bad file, a validation error |
| 3 | Auth: no key, an invalid, revoked or expired key, or HTTP 401 or 403 |
| 4 | Not found |
| 5 | Partial: the batch finished and some rows failed, were not found or were invalid. See results.csv |
| 6 | Rate limited after retries, or a plan or campaign lead limit stopped the run (limit_reached) |
| 7 | Conflict or invalid state (HTTP 409), or a run another process holds |
| 8 | Server error after retries (5xx), or the API is unreachable |
| 130 | Interrupted. A batch run can be resumed: see its next |
Skipped rows (duplicates, blocklisted, already contacted) are not failures: a batch with only created, updated and skipped rows exits 0.
Flags
- Command flags go after the command:
kenso contacts import --campaign 812. Global flags can go anywhere. - A boolean flag takes
--flag,--no-flag,--flag=trueand--flag=false. --helpon any command prints its flags, arguments and examples.kenso describe <resource> <action>prints the same as JSON, with the body schema.
Beta commands
Commands that need a workspace feature flag (CRM, Studio and others) are beta. Help and kenso describe hide the ones your workspace has not enabled and say how many they hid; --all-commands shows them. Calling a hidden command still works: Kenso decides. kenso <resource> on a resource whose every action is hidden answers feature_not_enabled (exit 2) and names the flags it needs.
kenso --help --all-commands
kenso describe --all-commands
What the CLI sends
Every API call carries Authorization: Bearer <key>, Ken-Version: 1.1, X-Ken-Workspace-Id (when a workspace is picked), User-Agent: kenso-cli/<version> (node <version>; <platform>) and X-Ken-Client: Kenso CLI. When a coding agent runs the CLI, X-Ken-Client names it (Kenso CLI via Claude Code) and writes carry X-Ken-Actor: agent, so Kenso's activity log shows who made a change.