kusty CLInew

kusty is a command-line interface for the Kustomer API built for AI agents and scripts. Every command returns a stable JSON envelope and a numbered exit code. Install it, connect an org with an API key or OAuth, and make your first calls.

kusty is the Kustomer API as a command-line tool. It's designed to be driven by models as much as by people: output is JSON by default, every error has a stable code and a hint, exit codes tell you what went wrong without parsing anything, and nothing ever stops to wait for input. The same binary also runs as an MCP server, so Claude Code, Cursor, Codex and Gemini CLI can call every Kustomer endpoint as a tool.

Interactivekusty in a terminal
Every command prints one JSON envelope. Pipe it straight into jq, or let an agent read it as is.
CoversEvery endpoint in the public API reference: 104 resource commands with 411 subcommands, plus kusty api for any path
OutputOne JSON envelope per command (jsonl by default), or json, yaml, table
AuthAn API key, or OAuth so each person signs in as themselves
Agentskusty mcp serve exposes the same commands as MCP tools

Install

kusty is a single Go binary. To build it from source you need Go 1.26 and make. From a checkout of the kusty source, build the public profile and install it to ~/.local/bin:

Shell
make install BUILD_PROFILE=public

Make sure ~/.local/bin is on your PATH, then check the install:

Shell
kusty version

Turn on shell completion with kusty completions zsh (or bash, fish, powershell), for example kusty completions zsh > "${fpath[1]}/_kusty".

Connect an org

kusty targets orgs through profiles. A profile names an org and says how to authenticate to it. You can set one up interactively with kusty config, or with flags.

The quickest way to start. Create an API key in Settings › Security › API Keys with the roles you need, then:

Shell
kusty profile add prod --org-subdomain acme --default
export KUSTY_API_KEY=eyJhbGciOi...
kusty auth whoami

--org-subdomain acme points at https://acme.api.kustomerapp.com. You can pass your dashboard URL instead with --tenant-url https://acme.kustomerapp.com.

kusty config is a terminal UI that walks through the same steps. It's the only interactive part of kusty; scripts and agents use the profile, app and auth commands instead.

Sessions and refresh

kusty auth login runs the OAuth authorization-code flow through a local callback on port 53682, 53683 or 53684. The resulting tokens are stored per profile in a file only you can read, in sessions/ next to the config file.

Access tokens last 24 hours. kusty renews them from the refresh token automatically, including inside a long-running MCP server, so you shouldn't need to log in again for weeks.

CommandDoes
kusty auth statusShows token expiry and whether the session can refresh, without changing anything
kusty auth refreshRenews the access token now, and shows the real error if it can't
kusty auth logoutDeletes the stored session

By default kusty requests the scopes org.permission.customer, org.permission.conversation, org.permission.message, org.permission.user and offline_access (needed for refresh). Set oauth_scopes on a profile to request others.

Profiles and the config file

Profiles live in ~/.config/kusty/config.toml, or wherever KUSTY_CONFIG points.

~/.config/kusty/config.toml
default_profile = "prod"

[profiles.prod]
org_subdomain = "acme"

[profiles.sandbox]
org_subdomain = "acme-sandbox"
api_key_env = "SANDBOX_KUSTY_API_KEY"
  • api_key_env makes a profile read its key from a different variable, so you can keep a key per org in your shell profile.
  • Settings resolve in this order: command-line flag, then KUSTY_* environment variable, then profile, then built-in default.
  • For credentials, an API key wins over an OAuth session. An admin profile that holds the OAuth client credentials for sharing still uses its own key.
Shell
kusty profile use sandbox                      # change the default
kusty --profile sandbox api GET /v1/users/current   # one command
KUSTY_PROFILE=sandbox kusty auth whoami        # one shell
kusty context show                             # which profile, org and credential would be used, offline

kusty context show doesn't touch the network: it reports the profile, org, base URL, credential_source (env:KUSTY_API_KEY, oauth, flag, …) and whether a credential is present. Put it at the top of scripts. kusty auth whoami proves the credential works by calling /v1/users/current.

Make requests

There are two ways to call the API.

Named commands cover every operation in the API reference, one per operation, with path and query parameters as flags:

Shell
kusty customers get-by-email --email sam@example.com
kusty conversations list --pageSize 10 --output table
kusty evaluations runevaluation --evaluationId 6716c1d2c4e5f60012ab36ea

Run kusty <resource> --help to list a resource's subcommands, or see the command reference.

kusty api sends any request to any path, which is handy for endpoints you already know from the API reference:

Shell
kusty api GET /v1/customers --limit 5
kusty api PATCH /v1/customers/6716a3a9c4e5f60012ab34c1 --field name="Sam Rivera"
kusty api POST /v1/customers --data @customer.json
echo '{"name":"Sam Rivera"}' | kusty api POST /v1/customers --data @-
kusty api GET /v1/customers --all | jq -r '.data.attributes.name'
FlagBuilds
--data '{…}', --data @file.json, --data @-The body, inline, from a file, or from stdin
--field a.b=textThe body one field at a time, as strings. a.b:=42 sets raw JSON: numbers, booleans, arrays, objects.
--query k=vExtra query parameters. Repeatable.
--header 'Name: value'Extra headers. Repeatable.
--allFollows links.next and streams every page, one item per line
--limit N, --cursor CpageSize and page

--data wins if you pass both --data and --field. Field paths use dots, with emails.0.email for array items and -1 to append (tags.-1=vip).

The output contract

Every command writes exactly one thing to stdout: a success envelope, a stream of JSON lines, or an error envelope.

Success
{
  "schema_version": "1",
  "data": { "type": "customer", "id": "6716a3a9c4e5f60012ab34c1", "attributes": { "name": "Sam Rivera" } },
  "meta": { "request_id": "9f2c41d8…", "duration_ms": 142, "next_cursor": "…" },
  "warnings": []
}
Error
{
  "schema_version": "1",
  "error": {
    "code": "AUTH_REQUIRED",
    "message": "no API key configured",
    "hint": "set SANDBOX_KUSTY_API_KEY or pass --api-key",
    "retryable": false
  }
}
  • data is the JSON:API data from Kustomer's response, so .data.attributes.name works the same as with the raw API.
  • The default format is jsonl, one envelope per line, whether you're in a terminal, a pipe, or an agent. Use --output json for pretty JSON, yaml, or table to read results yourself.
  • --fields id,attributes.name keeps only the paths you list, which saves tokens when an agent needs one value.
  • Error envelopes come with a non-zero exit code. The hint names the specific fix, such as the environment variable the active profile expects.
  • Human-readable diagnostics go to stderr; structured logs go to a JSON-lines file at $XDG_STATE_HOME/kusty/kusty.log. Every request's request_id appears in both the log and meta.

Safety rails

RailBehavior
--dry-runPrints the resolved request (method, base URL, path, query, headers and body) instead of sending it. The Authorization header is masked.
--confirm --yesRequired together for every DELETE. Without both, kusty refuses with DESTRUCTIVE_CONFIRMATION_REQUIRED.
--read-onlyRejects every write with READ_ONLY. Set KUSTY_READ_ONLY=1 to make it the default for a shell or an agent.
ValidationRequest bodies are checked against the API's JSON Schemas before sending, so typos and bad enum values fail fast with SCHEMA_VALIDATION_FAILED.
RetriesUp to three retries, honoring Retry-After on 429. A 5xx is retried only when repeating is safe: GET, HEAD, PUT, DELETE, or a request with --idempotency-key. A failed POST or PATCH isn't resent, because it may already have taken effect.

Release notes

kusty whats-new prints kusty's release notes, newest first, with no login needed. Narrow them with a topic, --product cli|mcp, --since 2026-10-01 or --limit 5.