Skip to content

About

Command-line interface for the Two merchant APIs

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

twoctl

ci Go Report Card License: MIT

Command-line interface for the Two merchant APIs. Generated from the public OpenAPI specs, so every endpoint is exposed as a subcommand.

Install

Homebrew

brew install two-inc/tap/twoctl

Binary releases

Download from releases and place on $PATH.

From source

go install github.com/two-inc/twoctl-cli/cmd/twoctl@latest

Shell completion

twoctl ships completions for bash, zsh, fish, and PowerShell. The binary generates them on demand via twoctl completion <shell>.

bash

Linux:

twoctl completion bash | sudo tee /etc/bash_completion.d/twoctl > /dev/null

macOS (with bash-completion@2 from Homebrew):

twoctl completion bash > "$(brew --prefix)/etc/bash_completion.d/twoctl"

If you don't have write access to a system path, source the completion in your ~/.bashrc:

echo 'source <(twoctl completion bash)' >> ~/.bashrc

zsh

# one-shot (re-run on shell start):
echo 'source <(twoctl completion zsh)' >> ~/.zshrc

# or persist to fpath:
twoctl completion zsh > "${fpath[1]}/_twoctl"

If compinit hasn't been initialised in your ~/.zshrc, add:

autoload -Uz compinit && compinit

before the completion source line.

fish

twoctl completion fish | source                                # current shell
twoctl completion fish > ~/.config/fish/completions/twoctl.fish # persistent

PowerShell

twoctl completion powershell | Out-String | Invoke-Expression                          # current session
twoctl completion powershell >> $PROFILE                                                # persistent

After installing, open a new shell. Tab-completion works on subcommands, flags, and any flag value that has a known enum in the OpenAPI spec (country codes, currencies, statuses).

Contexts

twoctl uses named contexts (kubectl-style). Each context bundles a base URL and an OS-keychain entry holding the API key, so you can switch between sandbox, prod, staging, cyber, perf, release, or any custom environment without copy-pasting keys.

twoctl config set-context sandbox --url https://api.sandbox.two.inc --key secret_test_...
twoctl config set-context prod    --url https://api.two.inc        --key secret_prod_...
twoctl config get-contexts                  # show all contexts, * marks current
twoctl config use-context prod              # switch the default
twoctl config current-context               # print the current name
twoctl config delete-context temp           # remove + clear keychain entry

Per-invocation overrides (no switching):

twoctl --env prod company company-search --country NO --q two
twoctl --context cyber checkout get-order --order-id abc   # kubectl-style alias
twoctl --url http://localhost:8080 --api-key secret_test_x ...

Scripts can select a context without changing the saved selection:

TWOCTL_CONTEXT=staging twoctl order get --order-id abc

Context selection follows --context, then --env, then non-empty TWOCTL_CONTEXT, then the saved current context. --url overrides the API URL and requires --api-key or TWO_API_KEY rather than using a stored context key. API calls, auth whoami, auth login, auth logout, and --dry-run honor the invocation context. config current-context continues to show the saved selection; the environment override never writes it.

For built-in env names (prod, sandbox, staging, cyber, perf, release) twoctl knows the URL. For any other name it falls back to https://api.<name>.two.inc; override with --url.

API key resolution order (highest precedence first):

  1. --api-key flag
  2. TWO_API_KEY env var
  3. OS keychain entry for the active context

Keys are obtained from the Two merchant portal.

If a keychain lookup fails, twoctl reports the underlying error rather than claiming no key is stored. On headless Linux without a running Secret Service/DBus session, the error explains the required backend and suggests TWO_API_KEY for headless use. Explicit --api-key and TWO_API_KEY values bypass the keychain. A missing key still prompts you to run twoctl auth login; keychain failures never fall back to storing keys in a file.

Configuration directory

Set TWOCTL_CONFIG_HOME to use a dedicated twoctl configuration directory:

TWOCTL_CONFIG_HOME=/path/to/job-config twoctl config get-contexts

Directory precedence is TWOCTL_CONFIG_HOME, then $XDG_CONFIG_HOME/twoctl, then ~/.config/twoctl. An empty override falls back to the next location. Both config.yaml (and its lock) and updater state.json use the selected directory; TWOCTL_CONFIG_HOME names that directory directly, with no extra twoctl/ appended. Existing files are not copied between locations. API keys stay in the OS keychain, keyed by context name, so selecting a different directory does not isolate keys.

Usage

The command tree is resource-first: twoctl <resource> <action> [flags]. Every operation across every Two API is classified into a (resource, action) pair derived from the URL path + operationId, then registered under the resource. Non-CRUD actions like cancel, refund, fulfill, search, notify surface as first-class verbs alongside get/create/ edit/delete.

twoctl order get --order-id abc-123          # GET /v1/order/{order_id}
twoctl order create --file order.json        # POST /v1/order
twoctl order cancel --order-id abc-123       # POST /v1/order/{id}/cancel
twoctl order fulfill --order-id abc-123 --file f.json
twoctl order refund --order-id abc-123 --file r.json
twoctl order edit --order-id abc-123 --file patch.json
twoctl company search --q "two" --country NO
twoctl billing-account get --billing-account-id ba_xxx
twoctl explain order create                  # JSON schema for the operation
twoctl api-resources --resource order        # catalog filtered by resource
twoctl api-resources --action cancel         # everything that's a "cancel"

Run twoctl <resource> on its own to see every action available. Each leaf command's --help lists flags derived from the OpenAPI operation; request bodies go via --file path.json or --data '{...}'.

Top-level non-resource commands:

twoctl Purpose
config set-context / use-context / get-contexts / current-context / delete-context Manage contexts (kubectl naming)
auth login / logout / whoami Store the API key in the OS keychain
explain <resource> <action> JSON schema for an operation, no network call
api-resources JSON catalog of every operation, for agent discovery
version Print version and build info
upgrade Self-upgrade from the latest GitHub release
--context / --env (alias) Per-invocation context override

Agent-friendly features

twoctl is designed to be driven by both humans and LLM agents.

  • twoctl api-resources [-r <resource>] [-a <action>] — emits a JSON catalogue of every operation (command, method, path, flags with kind/type/required, body status). Pipe this to an agent's planner so it can pick the right command without parsing --help text.
  • twoctl <resource> <action> --describe — JSON view of one operation's full spec (parameters, request body schema, response schemas) without a network call. Also reachable as twoctl explain <resource> <action>.
  • twoctl <resource> <action> --dry-run — render the HTTP request that would be sent (URL, redacted headers, body presence) and exit. No credentials needed.
  • -o table|json|yaml (default auto: table on TTY, JSON when piped).
  • Errors are emitted as JSON on stderr with shape {error, message, status, response, request_id}. Stdout stays clean for piping.

Pagination

Operations with a page_cursor parameter support --all to collect every page and --no-paginate to explicitly fetch one page. Without either flag, table output on an interactive terminal renders one complete page, then asks next page? [Y/n] on stderr when a next cursor is present. Enter or y loads the next page; n, other answers, or end-of-input stops.

Prompts require terminal stdin, stdout and stderr. JSON/YAML output, pipes, redirected streams and --file - never prompt, preserving single-page machine-readable responses and request-body stdin. --all still works in those modes and emits one combined result. --all and --no-paginate cannot be combined. Dry runs do not fetch or prompt.

Request validation

JSON payloads supplied with --data, --file, or --file - are checked against the embedded OpenAPI request schema before the API call (including --all and --dry-run). Invalid JSON, missing required fields, wrong types, and enum/constraint mismatches return usage exit code 2 without sending. Unknown fields in structured objects produce warnings on stderr but are sent unchanged to tolerate schema drift. Explicitly open and typed maps retain their schema semantics. Payload values are not printed in validation errors or warnings. Use --describe to inspect the expected schema.

Exit codes

Code Meaning
0 success
1 generic error
2 usage / missing required input
3 authentication or authorisation (401/403)
4 not found (404)
5 rate limited (429)
6 server error (5xx)
7 network / transport failure

Version information

twoctl version and twoctl --version print the same version, Go runtime, and platform information. twoctl version --json emits a JSON object with version, go_version, os, and arch fields for scripts. Version queries do not read credentials, check for upgrades, or make network requests.

HTTP diagnostics

Use -v (or --verbose) to write one JSON diagnostic per API request to stderr, including method, URL, HTTP status, elapsed milliseconds, and the upstream request ID when present. This includes successful calls and every page fetched by --all; the duration includes retries and reading the body. Transport and body-read failures include an error field.

Use -vv to include request/response headers and bodies. Sensitive header, query, and JSON field values are redacted. Body diagnostics use the existing 4 MiB cap; an unavailable or oversized request body is reported without interrupting the API request. Treat detailed output as API data when sharing it. Stdout remains the operation response, and successful calls produce no HTTP diagnostics by default.

Retries

API requests retry transient network errors, HTTP 429, and HTTP 5xx up to three times by default (four attempts including the initial request). Use --max-retries N to set the number of retries per request, or --max-retries 0 to make a single attempt:

twoctl order get --order-id abc-123 --max-retries 0
twoctl billing-statement get --all --max-retries 1

The setting applies to each page when using --all. Retries keep the same idempotency key and request body. The existing 60-second request timeout still bounds the attempts and backoff for each request.

Self-upgrade

twoctl checks for new releases at most once every 24 hours and prompts when one is available. The prompt offers:

  • y install now
  • n not now (default)
  • s skip this version (won't ask again until the next release)

Direct controls:

twoctl upgrade                      # check + install
twoctl upgrade --check              # only report, don't install
twoctl upgrade --reset-skips        # forget every skipped version
twoctl upgrade --disable-autocheck  # silence the prompt entirely
twoctl upgrade --enable-autocheck   # turn it back on

--no-upgrade-check is a per-run override for scripts. State lives in ~/.config/twoctl/state.json.

APIs covered

Every operation across all twelve published Two specs surfaces as a twoctl <resource> <action> command. Actions are auto-derived from operationId prefixes (get_/retrieve_/list_/search_, create_/make_/ fulfill_/refund_/cancel_/...) with HTTP-method fall-back when no prefix matches. Run twoctl api-resources for the full machine-readable catalog (120+ operations).

Spec Source
Checkout (Order) checkout-api.yaml
Billing Account billing-account-api.yaml
Repay repay-api.yaml
Recourse recourse-api.yaml
Company company-api.yaml
Limits limits-api.yaml
Autofill autofill-api.yaml
Business Registration business-registration-api.yaml
Marketplace marketplace-api.yaml
Trade Account v2 trade-account-v2-api.yaml
Trade Account v3 trade-account-v3-api.yaml
Webhooks webhooks-api.yaml

The deprecated stubs (search-api.page-deprecated, trade-account-api-deprecated-v1, trade-account-api.page-deprecated) are not wired up.

Regeneration

The openapi/ specs are vendored from two-inc/docs. When that source changes, a repository_dispatch event fires the regenerate.yml workflow, which pulls fresh specs, runs codegen, and opens a PR.

To regenerate locally:

./scripts/codegen.sh
go build ./...

CI runs make check-generated on every pull request and main-branch push. It regenerates the embedded specs from the committed openapi/ inputs and fails if any embedded file changes or is newly generated but untracked. When changing source specs or the preprocessor, run make regen and commit the updated cmd/twoctl/cli/specs/ files with the change. This check uses vendored inputs and does not fetch upstream specs or require API credentials; the nightly regenerate workflow continues to handle upstream refreshes.

Contributing

This repository is generated from the OpenAPI specs in two-inc/docs. Bug reports and PRs for the CLI itself (flags, output formatting, ergonomics) are welcome here. For API behaviour or schema changes, please open an issue against two-inc/docs.

Telemetry

twoctl does not collect telemetry. There are no usage metrics, no anonymous identifiers, no phone-home. The only outbound calls the CLI makes are:

  1. The Two API endpoint configured by your active context (e.g. https://api.sandbox.two.inc).
  2. https://api.github.com/repos/two-inc/twoctl-cli/releases/latest for the daily self-upgrade check. Disable with --no-upgrade-check, TWOCTL_SKIP_UPGRADE_CHECK=1, or twoctl upgrade --disable-autocheck.

Self-upgrade downloads come from github.com/two-inc/twoctl-cli/releases when you (or the prompt) chooses to install.

Versioning + deprecation

Releases follow semver. Breaking changes to flags, commands, or behaviour bump the major version.

When a command or flag is renamed, the old name keeps working for at least one minor cycle and prints a deprecation note to stderr (never stdout). The note includes the version in which the alias will be removed.

Licence

MIT - see LICENSE.

About

Command-line interface for the Two merchant APIs

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages