> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/infra-controller/llms.txt.
> For full documentation content, see https://docs.nvidia.com/infra-controller/llms-full.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/infra-controller/_mcp/server.

# nicocli Reference

Operator reference for `nicocli`, the CLI for the NICo REST API. Covers installation, configuration, authentication, command mechanics, output formatting, debugging, and TUI mode. Targeted at operators who have completed the [Quick Start Guide](/infra-controller/documentation/getting-started/quick-start-guide) and want to drive NICo from scripts or one-off interactive sessions.

The [Day One Operations](day_one_operations.md) guide uses this reference for all CLI mechanics. Read this page first if you plan to script anything beyond the Quick Start happy path.

## Installation

Build and install from the `infra-controller-rest` repo:

```
make nico-cli                                # installs to $(go env GOPATH)/bin/nicocli
make nico-cli INSTALL_DIR=/usr/local/bin     # install elsewhere
```

Verify:

```
nicocli --version
```

If `nicocli` is not on `$PATH`, add `$(go env GOPATH)/bin` to it.

> **Note on CLI naming**: older docs and shipped binaries reference `carbidecli` (built via `make carbide-cli`). It's the same source under a previous name; the binary was renamed during the transition from carbide-core to NICo. The Makefile retains both `make nico-cli` and `make carbide-cli` targets. Prefer `nicocli` for new work.

## Configuration

### Config file location

Default: `~/.nico/config.yaml`. Create one with `nicocli init` (errors if a file already exists). Override the path per-command:

| Override | Mechanism |
|----------|-----------|
| Per command | `--config <path>` |
| Per shell session | `export NICO_CONFIG=<path>` |
| Per environment (TUI) | Selected interactively from the config picker |

The config is written with `0600` permissions (private to the owning user). nicocli writes tokens and refresh tokens back to the active config on every successful login or auto-refresh, so the permissions matter.

### Multi-environment configs

The TUI auto-discovers any file matching `~/.nico/config*.yaml`. Use one file per environment, naming the file after the environment so the picker is self-documenting:

```
~/.nico/config.yaml             # default
~/.nico/config.local.yaml       # local kind dev
~/.nico/config.staging.yaml     # shared staging
~/.nico/config.prod.yaml        # production
```

For non-interactive commands, pass `--config <path>`. For TUI, start `nicocli tui` and pick from the list.

### `api.base`, `api.org`, `api.name`

| Field | Meaning |
|-------|---------|
| `api.base` | Base URL of the REST API server (`http://localhost:8388`, `https://nico.example.com`, etc.) |
| `api.org` | Org name in API paths (`/v2/org/<api.org>/<api.name>/...`) -- must match the org claim in your token |
| `api.name` | API path segment between org and resource. Defaults to `nico` in fresh installs; some deployments override this to a deployment-specific value. |

`api.name` is the most common source of misconfiguration. If every command returns:

```
HTTP/1.1 404 Not Found
{"message":"The requested path could not be found"}
```

your `api.name` does not match the deployment. Find the deployment's expected value in the API server's running config (the `api.name` key in the carbide-rest-api configmap for `carbide-rest`-namespace deployments) and set it in your config.

### Sample config

```yaml
api:
  base: https://nico.example.com
  org: my-org
  name: nico

auth:
  # Choose ONE auth method. nicocli login picks based on what's present.

  # Static bearer token (no login required; refreshed manually).
  # token: eyJhbGciOi...

  # Shell script that prints a bearer token on stdout. Recommended when
  # the auth provider's token semantics do not match nicocli's built-in
  # OIDC grants.
  # token_command: /home/me/.nico/get-nico-token.sh

  # OIDC password / client-credentials grant (Keycloak and similar).
  # oidc:
  #   token_url: https://keycloak.example.com/realms/nico-dev/protocol/openid-connect/token
  #   client_id: nico-api
  #   client_secret: <client-secret>

  # NGC API key. nvapi- prefixed keys are bearer tokens directly;
  # legacy keys must specify authn_url for exchange.
  # api_key:
  #   key: nvapi-xxxx
  #   # authn_url: https://your-authn-server/token   # only for legacy keys
```

`nicocli init` writes a fully commented template -- start from that rather than copying the snippet above by hand.

## Authentication

`nicocli login` runs the appropriate auth flow and saves the resulting bearer token back to the active config. Pick the method that matches your deployment.

### Auth method selection order

When you run `nicocli login`, nicocli decides which flow to use in this order:

1. `--token-command` flag (overrides everything; persists `auth.token_command` to config).
2. Explicit `--api-key` / `--authn-url` flags.
3. Explicit `--token-url` / `--client-secret` / `--username` / `--password` / `--keycloak-url` flags.
4. `auth.token_command` in config.
5. `auth.oidc` in config.
6. `auth.api_key` in config.
7. Default OIDC password grant (fails if no token URL is configured).

If your config is set up correctly, plain `nicocli login` picks the right method. The flag forms below are mostly useful for first-time setup and CI scripts.

### Static bearer token

For short interactive sessions or when token issuance is handled outside nicocli:

```yaml
auth:
  token: eyJhbGciOi...
```

No `nicocli login` is needed -- the token is used directly. When it expires, paste a fresh one or move to `token_command`.

### Token command (external auth scripts)

When your auth provider's token semantics do not match nicocli's built-in OIDC grants -- for example, when the provider requires a custom OAuth scope, an mTLS-fronted token endpoint, or a wrapper that fetches credentials from a secret store -- write a small script that prints a bearer token to stdout and reference it from config:

```yaml
auth:
  token_command: /home/me/.nico/get-nico-token.sh
```

Or for a one-shot login that also persists `token_command` to the config:

```
nicocli --config ~/.nico/config.staging.yaml \
        --token-command ~/.nico/get-nico-token.sh \
        login
```

After login, every subsequent nicocli command re-runs the script when the cached token is near expiry. The bearer token is redacted in `--debug` output, but the path to the script is visible in the config.

The script must:

- Print exactly one line: the bearer token. No log noise on stdout.
- Exit zero on success, non-zero on failure.
- Be reasonably fast (re-run on token expiry, so seconds matter).

Minimal example -- adapt the curl payload and headers to whatever your provider requires:

```bash
#!/usr/bin/env bash
set -euo pipefail

# Source credentials however your deployment manages them (env vars,
# secret store CLI, file with 0600 perms, etc.). This example assumes
# the client_id and client_secret are already exported.

curl -sS \
     -u "${CLIENT_ID}:${CLIENT_SECRET}" \
     -d grant_type=client_credentials \
     -d scope="${OAUTH_SCOPE:-openid}" \
     "${TOKEN_URL}" \
  | jq -r '.access_token'
```

`chmod 700` the script -- it's invoked on every token refresh.

### OIDC password / client-credentials (Keycloak)

For deployments backed by Keycloak (or any OIDC IdP that supports the standard password or client-credentials grants), put the token URL and client identity in config:

```yaml
auth:
  oidc:
    token_url: https://keycloak.example.com/realms/nico-dev/protocol/openid-connect/token
    client_id: nico-api
    client_secret: <client-secret>
```

Then:

```
nicocli login                                    # password grant; prompts for user/pass
nicocli login --username alex@example.com        # supply username, prompted for password
nicocli login --client-secret "$NICO_CLIENT_SECRET"   # client-credentials grant
```

Keycloak shorthand (constructs the token URL automatically as `<keycloak-url>/realms/<realm>/protocol/openid-connect/token`):

```
nicocli --keycloak-url https://keycloak.example.com \
        login --username alex@example.com
```

The default realm is `nico-dev`; override with `--keycloak-realm` or `NICO_KEYCLOAK_REALM`.

After login, the access token, refresh token, and expiry time are saved back to the config. The TUI auto-refreshes tokens 30 seconds before expiry and retries failed requests on `401 Unauthorized` up to three times.

> **OIDC scope is hardcoded**: the built-in password and client-credentials grants both send `scope=openid` -- this is not configurable. If your IdP requires a different scope, use `auth.token_command` instead.

### NGC API key

For NGC-backed deployments:

```yaml
auth:
  api_key:
    key: nvapi-xxxx
```

Keys prefixed with `nvapi-` are bearer tokens directly -- no `authn_url` is required. Legacy NGC API keys without the prefix must be exchanged via the `authn_url`:

```yaml
auth:
  api_key:
    key: <legacy-key>
    authn_url: https://your-authn-server/token
```

The CLI errors clearly if `authn_url` is missing on a legacy key. `nvapi-` keys never need it.

### Environment variables

Every auth flag has a corresponding env var, useful for CI/CD pipelines:

| Flag | Env var |
|------|---------|
| `--config` | `NICO_CONFIG` |
| `--base-url` | `NICO_BASE_URL` |
| `--org` | `NICO_ORG` |
| `--token` | `NICO_TOKEN` |
| `--token-command` (alias `--auth-script`) | `NICO_TOKEN_COMMAND` (alias `NICO_AUTH_SCRIPT`) |
| `--token-url` | `NICO_TOKEN_URL` |
| `--keycloak-url` | `NICO_KEYCLOAK_URL` |
| `--keycloak-realm` | `NICO_KEYCLOAK_REALM` |
| `--client-id` | `NICO_CLIENT_ID` |
| `--client-secret` | `NICO_CLIENT_SECRET` |
| `--api-key` | `NICO_API_KEY` |
| `--authn-url` | `NICO_AUTHN_URL` |

### Verifying authentication

After login, confirm nicocli can reach the API and your identity is correct:

```
nicocli site list                        # any list returns -> auth works
nicocli user get                         # returns the caller's user record
nicocli service-account get              # for service-account deployments only
```

`user get` returns the authenticated identity as NICo sees it. Service accounts have blank `email`/`firstName`/`lastName`; human users have those populated from the IdP.

## Command Structure

### Resources and actions

nicocli commands are generated from the OpenAPI spec. Each tag becomes a top-level resource; each operation becomes an action under it:

```
nicocli <resource> <action> [flags...] [positional args]
```

Examples:

```
nicocli site list
nicocli allocation get <allocation-id>
nicocli instance update --trigger-reboot=true <instance-id>
```

Some resources have sub-resources -- a third grouping level. Constraints under allocations are a typical case:

```
nicocli allocation constraint update --constraint-value 12 <alloc-id> <constraint-id>
```

Run `nicocli <resource> --help` to enumerate available actions and sub-resources for any tag.

### Action name resolution

The CLI's action-name resolver collapses `get-current-X` operation IDs to a short `get` action when there's no sibling collision. When two operation IDs would collide on the short form, both keep their full names.

| Operation ID | Resource | Action name |
|--------------|----------|-------------|
| `get-current-user` | `user` | `get` (no collision) |
| `get-current-service-account` | `service-account` | `get` (no collision) |
| `get-current-tenant` | `tenant` | `get-current-tenant` (collision with `get-current-tenant-stats`) |
| `get-current-tenant-stats` | `tenant` | `get-current-tenant-stats` (collision) |

Use `--help` to confirm the actual action name for any resource if you're unsure.

### Flag ordering

Flags MUST come before positional arguments. nicocli uses urfave/cli, which stops parsing flags at the first positional. Examples:

```
# correct
nicocli instance update --trigger-reboot=true <instance-id>
nicocli allocation constraint update --constraint-value 12 <alloc-id> <constraint-id>
nicocli tenant-account update --data '{}' <account-id>

# wrong -- flags after positionals are rejected
nicocli instance update <instance-id> --trigger-reboot=true
```

When the ordering is wrong, the CLI prints a clear error:

```
Error: flag(s) --data placed after a positional argument; urfave/cli (stdlib flag)
stops parsing flags at the first positional, so these flags are being ignored.
Move all flags before positionals, e.g.
  nicocli tenant-account update [flags...] <accountId>
```

### `--data` vs flag forms

Most create and update operations expose every body field as an individual flag. Prefer the flag form -- it's shorter and gets validated up front. For example:

```
nicocli instance update --trigger-reboot=true <instance-id>
nicocli instance update --name acme-worker-01-renamed <instance-id>
nicocli vpc create --name acme-prod --site-id <site-uuid> --routing-profile internal
```

Use `--data '<json>'` or `--data-file <path>` (use `-` for stdin) only when:

- A field is array-typed. `interfaces[]`, `sshKeyGroupIds[]`, `allocationConstraints[]`, `nvLinkInterfaces[]`, and similar arrays cannot be set through individual flags -- they go through the body.
- You want to round-trip an entire resource through `get -o json` -> edit -> `update --data-file`.

JSON bodies use camelCase (`siteId`, `vpcId`, `ipv4BlockId`) -- the same as the REST API request bodies. Flag names are kebab-cased mechanically, which is not always invertible. Two gotchas:

- Digit-to-letter transitions get no separator: `ipv4BlockId` -> `--ipv4block-id`, not `--ipv4-block-id`.
- Acronyms preserve their grouping: `vniId` -> `--vni-id`; `nvLinkLogicalPartitionId` -> `--nv-link-logical-partition-id`.

When in doubt, run `<command> --help` to see the exact flag name.

## Output Formatting

### `--output`

List and get commands support `--output <format>`:

| Format | Use |
|--------|-----|
| `json` (default) | Structured output, suitable for `jq` |
| `yaml` | Structured output, easier to read by hand |
| `table` | Minimal columns. Some endpoints (notably `audit list`) only show `id` -- use `json` for full detail. |

```
nicocli site list --output table
nicocli tenant get-current-tenant --output yaml
nicocli audit list --output json --page-size 50 | jq '.[] | select(.statusCode >= 400)'
```

The detail (`get <id>`) view is always richer than the list view -- list endpoints often omit nested objects for performance. If `list` doesn't show a field you need, `get` likely will.

### Pagination

List commands accept `--page-size`, `--page-number`, and `--all`:

```
nicocli audit list --page-size 50 --page-number 2
nicocli allocation list --all
```

When results span multiple pages, a one-line pagination summary is printed to **stderr** above the data:

```
Page 1/18 (5 items, 88 total). Use --all to fetch everything.
```

When piping to `jq`, suppress the summary with `2>/dev/null`:

```
nicocli audit list --output json --page-size 50 2>/dev/null \
  | jq '.[] | {id, endpoint, method, statusCode}'
```

`--all` follows pagination links automatically (capped at 1000 pages). For very large result sets, paginate explicitly to stay within memory budgets.

### Filter flags vs `--query`

List endpoints expose dedicated filter flags derived from the OpenAPI spec. For example, `nicocli allocation list --help` shows `--site-id`, `--tenant-id`, `--resource-type`, `--resource-type-id`, `--status`, `--constraint-type`, `--constraint-value`, `--include-relation`, `--order-by`, plus the standard pagination flags. Use those.

The `--query` flag is a **free-text search across `name`, `description`, and `status` fields** -- it is NOT a key=value filter. `--query "resourceType=InstanceType"` matches the literal substring `resourceType=InstanceType` in those fields, which is almost never what you want. Reach for the dedicated flags.

## Debugging

### `--debug`

The global `--debug` flag logs the full HTTP request and response for the wrapped command. The bearer token is redacted; everything else is visible:

```
$ nicocli --debug tenant get-current-tenant
time=... msg="API request: GET https://nico.example.com/v2/org/my-org/nico/tenant/current"
time=... msg="Request headers: {\"Accept\":[\"application/json\"],\"Authorization\":[\"Bearer <redacted>\"]}"
time=... msg="API response: ... -> 200 OK"
time=... msg="Response body: {...}"
```

Use it for:

- Confirming the rewritten URL has the right `api.name` segment.
- Seeing the exact body the server received (handy for `--data` debugging).
- Capturing the full failure payload when a command returns a non-2xx response.

### `api.name` rewriting

nicocli substitutes the API name segment in every URL before sending. The OpenAPI spec uses `nico` as the placeholder; the deployment may use any value its operators chose. nicocli rewrites `/v2/org/<org>/nico/...` to `/v2/org/<org>/<api.name>/...` transparently. `--debug` shows the rewritten URL, which is what you should see in the API server's access log.

### Version mismatch is normal

```
nicocli --version
```

The CLI is generated from the OpenAPI spec at build time; the server reports its own image version (visible as `apiVersion` on audit responses). The two version schemes are independent -- mismatches are normal and rarely matter, as the wire protocol is stable across patch and minor releases.

## TUI Mode

For exploratory work and one-off operations, the TUI is the recommended interface:

```
nicocli tui     # full command
nicocli i       # alias
```

Behavior:

- Discovers every `config*.yaml` in `~/.nico/` and presents them as a selection list at startup.
- Authenticates using the chosen config's auth method (token, token-command, OIDC, or API key).
- Drops into an interactive prompt with tab completion across all generated commands.
- Scopes lookups appropriately -- for example, `allocation create` only shows instance types and IP blocks that belong to the selected site, which avoids the common mistake of cross-site allocations.
- Auto-refreshes tokens 30 seconds before expiry and retries failed requests on `401 Unauthorized` up to three times.

The TUI's interactive forms for `create` commands prompt for fields in order with type-aware pickers. For first-time operators this is significantly easier than constructing JSON bodies by hand. For scripts and automation, fall back to the non-interactive command form.

## Quick Reference

| Operation | Command |
|-----------|---------|
| Generate sample config | `nicocli init` |
| Authenticate (config-driven) | `nicocli login` |
| Authenticate (token-command flag) | `nicocli --token-command <path> login` |
| Authenticate (API key flag) | `nicocli login --api-key nvapi-xxxx` |
| Authenticate (Keycloak shorthand) | `nicocli --keycloak-url <url> login --username <user>` |
| Verify identity | `nicocli user get` |
| Pick environment interactively | `nicocli tui` |
| Pick environment non-interactively | `nicocli --config ~/.nico/config.<env>.yaml ...` |
| List with table output | `nicocli <resource> list --output table` |
| Fetch every page of a list | `nicocli <resource> list --all` |
| Inspect HTTP exchange | `nicocli --debug <command>` |
| Print CLI version | `nicocli --version` |

## Related Documentation

- [Quick Start Guide](/infra-controller/documentation/getting-started/quick-start-guide) -- NICo deployment and Day Zero walkthrough; covers the bundled Keycloak setup.
- [Day One Operations](day_one_operations.md) -- Operator workflow for tenant management, allocations, VPCs, subnets, and instance provisioning. Uses this reference for all CLI mechanics.
- [Reference Installation](/infra-controller/documentation/getting-started/installation-options/reference-installation) -- Deployment configuration surface, including the `issuers` block that maps OIDC IdPs to NICo.