> ## Documentation Index
> Fetch the complete documentation index at: https://docs.internal.nscale.com/llms.txt
> Use this file to discover all available pages before exploring further.

# fleetctl API CLI

> Use generated fleetctl API commands to inspect resources, run approved operator mutations, and dispatch workflows.

`fleetctl api` calls Fleet Manager HTTP API endpoints from a shell. Its resource
commands are generated from `docs/openapi.yaml`: all readable GET operations are
exposed, plus a small allowlist of operator mutations ([allocation](../../start-here/glossary#allocation) [device](../../start-here/glossary#node)
assignment, job creation, and [burn-in](../../start-here/glossary#burn-in) rerun planning and dispatch) that need an
authenticated API workflow instead of direct database access. The command list
therefore follows the committed OpenAPI surface instead of a hand-written
catalogue.

Use it when you need a quick API inspection tool, a copy-pasteable debugging
command, a way to dispatch an existing workflow, or a stable way for automation
to discover read endpoints. For writing a dedicated client, start with
[Client Authentication](./client-authentication) and
[Scopes & Permissions](./scopes-and-permissions).

## Quick start

Install dependencies, then confirm the command group is wired up:

```bash theme={null}
make dev
uv run fleetctl api --help
```

Before login works, Fleet Manager must have an OAuth2 application for the CLI.
The default client ID is `fleetctl-api`; that application must allow the
authorization-code + PKCE flow and register `http://127.0.0.1/callback` as a
redirect URI. Django OAuth Toolkit treats loopback redirect URIs this way as
valid for any callback port, so the CLI's default runtime redirect
`http://127.0.0.1:8765/callback` is covered.

Log in with the browser-based OAuth flow:

```bash theme={null}
uv run fleetctl api login
uv run fleetctl api profiles
uv run fleetctl api whoami
```

By default, `fleetctl api login` uses the production API
(`https://api.fleet.nscale.com`) and stores credentials in the `default`
profile. Pass `--base-url` and `--profile` when you need dev, local, or a named
profile:

```bash theme={null}
uv run fleetctl api login --base-url https://api.fleet.dev.nscale.com --profile dev
uv run fleetctl api whoami --profile dev
```

If you are on a remote host where opening a browser is not useful, use
`--no-open-browser`; the command prints the authorization URL so you can open it
yourself.

If you change `--client-id` or `--callback-port`, the selected OAuth client must
still allow the resulting loopback callback.

Profiles are stored in `~/.config/fleetctl/api-tokens.json` by default with
`0600` file permissions. The file contains plaintext access and refresh tokens,
so treat it like any other local credential file. Set
`FLEETCTL_API_TOKEN_STORE=/path/to/api-tokens.json` when you need an isolated
token store for tests or automation.

## Discover commands

Human users can discover the generated surface through `--help`:

```bash theme={null}
uv run fleetctl api device --help
uv run fleetctl api device list --help
uv run fleetctl api allocation get --help
```

Agents should instead read the generated command reference once before running commands:
`src/fleet_manager/cli/commands/api/generated/fleetctl-api-agent-command-reference.md`.
It is the compact command index for agent context, and outside a source checkout
`fleetctl api agent-reference` prints the same content. Generated help pages end with a
runnable example when the command needs no caller-specific values, or an explicitly
labelled usage template when IDs or request data must be supplied.

Resource names are singular and kebab-cased, for example `device`,
`device-group`, `inventory-device`, and `job-device-status`. Most generated
resources expose `list` for collection endpoints and `get` for detail endpoints.
Some OpenAPI actions are exposed as their action name when the endpoint is not a
plain list or retrieve.

## Common call patterns

List resources:

```bash theme={null}
uv run fleetctl api device list
uv run fleetctl api allocation list --site-id 019d8c7c-5bdf-7f28-b26e-7e4397a05d5c
```

Get one resource by path parameter:

```bash theme={null}
uv run fleetctl api allocation get 019d8c7c-5d09-7a6e-8875-c03b1b52f6fb
```

Assign devices to an existing allocation by rack and role:

```bash theme={null}
uv run fleetctl api allocation assign \
  019d8c7c-5d09-7a6e-8875-c03b1b52f6fb \
  --rack-names A01,A02 \
  --device-role-slugs gpu-node \
  --dry-run true
```

Pass `--all-devices true` to select every device in the allocation's site; a
request with no selector is rejected. Run without `--dry-run true` to apply the
memberships. The operation is idempotent for existing members. If another allocation overlaps,
the API still assigns every non-conflicting device but returns HTTP 409 with a
typed conflict list; the CLI prints that result and exits non-zero so the
conflict cannot be missed.

Unassign devices using the same selectors:

```bash theme={null}
uv run fleetctl api allocation unassign \
  019d8c7c-5d09-7a6e-8875-c03b1b52f6fb \
  --rack-names A01,A02 \
  --device-role-slugs gpu-node \
  --dry-run true
```

Run without `--dry-run true` to remove the selected memberships. Devices that
are not members of the target allocation are reported as idempotent no-ops;
memberships in other allocations are never changed. To select the whole site,
pass `--all-devices true` explicitly; a bare unassignment request is rejected.

Override the profile URL for one call:

```bash theme={null}
uv run fleetctl api device list --base-url http://localhost:8000
```

Add `--debug` to print HTTP method, final URL, status code, and request duration
to stderr. Authorization headers and token values are not printed.

```bash theme={null}
uv run fleetctl api device list --debug
```

## Dispatch an existing workflow

The job-create command dispatches an existing Fleet Manager workflow. It does
not create or edit workflow definitions:

```bash theme={null}
uv run fleetctl api job create --format json --data '{
  "workflow": "<workflow-uuid>",
  "devices": ["<device-uuid>"],
  "parameters": {}
}'
```

`--data` must contain a JSON object. Fleet Manager validates the workflow ID,
devices, parameters, eligibility, and conflicts.

### Plan and dispatch burn-in reruns

Preview the minimum policy-satisfying reruns for selected devices without creating Jobs:

```bash theme={null}
uv run fleetctl api burn-in-rerun plan "<site-uuid>" --format json --data '{
  "device_ids": ["<device-uuid>"],
  "mode": "non_passing"
}'
```

Dispatch recalculates the plan from live state and creates the safe Jobs it finds:

```bash theme={null}
uv run fleetctl api burn-in-rerun dispatch "<site-uuid>" --format json --data '{
  "device_ids": ["<device-uuid>"],
  "mode": "non_passing"
}'
```

Both rerun commands deliberately use the same authorization boundary as direct Job creation. The plan command is non-mutating, but it still requires permission to launch the Jobs it previews.

Before using Job creation, rerun planning, or rerun dispatch:

1. Add `job:create` to the `fleetctl-api` OAuth Application's allowed scopes.
2. Grant the invoking user or group the Django permission
   `fleet_manager_core.add_job`.
3. Run `fleetctl api login` again so a new token is issued with the updated
   access.

The scopes issued to a token are the intersection of the requested scopes, the
application's allowed scopes, and the user's permission-derived scopes:

```text theme={null}
requested scopes ∩ application scopes ∩ user scopes
```

Logging in with `--scope read-only` deliberately excludes `job:create`, even
when both the application and user permit it.

## Filters and output

Generated query parameters become kebab-case options. Check the command help for
the exact options on each endpoint:

```bash theme={null}
uv run fleetctl api allocation list --help
```

Array filters can be passed as comma-separated values or by repeating the
option. Both forms produce the same API query for endpoints whose OpenAPI
parameter uses comma-separated array encoding:

```bash theme={null}
uv run fleetctl api allocation list --device-id dev-1,dev-2
uv run fleetctl api allocation list --device-id dev-1 --device-id dev-2
```

Deep-object filters use `key=value` entries at the CLI:

```bash theme={null}
uv run fleetctl api allocation list --annotation-eq rack=A1 --annotation-eq pool=burn-in
```

Boolean filters take explicit values:

```bash theme={null}
uv run fleetctl api device list --customer-ready false
uv run fleetctl api device list --in-triage true
```

The default output is a table. When a supported AI-agent harness is detected, including
Claude Code, Codex, Conductor, Cursor, or Gemini, the default switches to ANSI-free
`--format json` so structured output does not have to be requested on every call. Set
`FLEETCTL_AGENT_MODE=true` or `false` to force either behaviour, and an explicit
`--format` always wins.

After a successful call, commands print a `Next:` hint on stderr suggesting follow-up
commands pre-filled with the first result's id — `device list` suggests
`fleetctl api device get <id>` — so results can be chained without re-deriving the
workflow. Hints never appear on stdout, so piped output stays clean.

Use `--columns` to keep only the fields you need, including dot paths for nested
fields. With the default table format it selects the table columns; with `--format json`
it projects every object down to those fields, whatever the response shape — a `results`
collection, a bare array, or a single object. Projection only narrows the fields inside
rows; it never adds or removes response-level keys such as `count`:

```bash theme={null}
uv run fleetctl api device list --columns id,name,site.name,status
uv run fleetctl api device list --format json --columns id,operational_status
```

Use JSON when another tool will consume the response:

```bash theme={null}
uv run fleetctl api device list --format json | jq '.results[] | .name'
```

List commands also support a count-only format:

```bash theme={null}
uv run fleetctl api device list --format count
```

For paginated list endpoints, table and JSON output follow every `next` page and
merge the results. `--format count` reads the count from the first page and does
not fetch the remaining pages.

At a large site an unfiltered list is slow and returns a snapshot whose per-row status
can lag the underlying record, because every page is merged in memory. Filter
server-side and size with `--format count` first; when a decision depends on one
resource's current state, read it back by id or `--name-exact` rather than trusting a
row from a big merged list.

## Troubleshooting

* `Error: API profile 'default' is not logged in`: run `fleetctl api login`, or
  pass the profile you stored with `--profile`.
* Login fails before a profile is stored: verify the OAuth client exists in Fleet
  Manager, uses the expected client ID, supports authorization-code + PKCE, and
  allows `http://127.0.0.1/callback`.
* `401 Unauthorized`: the token is missing, expired without a refresh token, or
  rejected by the API. Re-run `login` and then `whoami`.
* `403 Forbidden`: the user or token is valid but does not have the required
  scope or Django permission. See [Scopes & Permissions](./scopes-and-permissions).
* Command help does not show a new supported endpoint: regenerate the OpenAPI-derived
  CLI metadata. If API code changed, run `make openapi`. If only
  `docs/openapi.yaml` changed, run `make api-cli`.
* Generated artifact drift in CI: run `make api-cli-check` locally to confirm
  whether `src/fleet_manager/cli/commands/api/generated/operations.py` and
  `src/fleet_manager/cli/commands/api/generated/fleetctl-api-agent-command-reference.md`
  match `docs/openapi.yaml`.

## Logging out

Remove a stored profile when you are done:

```bash theme={null}
uv run fleetctl api logout
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.