Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,52 @@ latest version, run `dci update`, or see
[install and update](https://help.doit.com/docs/cli#download-and-install) in
the CLI guide.

## v2.8.0 — October 1, 2026

### New

- `dci commands --search "<task>"` finds the command for a task in plain
words — `dci commands --search "resolve an anomaly"` — and lists the best
matches with a one-line example each, so you or your agent no longer have
to scan the whole catalog. Beta commands are searchable too, marked with
their stage. See [Agent mode](https://help.doit.com/docs/cli#agent-mode).
- `--help` shows curated examples for more commands, including roles,
service accounts and their tokens, customer group users, shared payers,
commitment policies, widgets, CloudFlow `trigger-cloudflow-flow` and
`stop-cloudflow-flow`, and the signup flow.

### Improved

- `dci <command> --help` lists only the flags that apply to that command.
Report and query flags such as `--chart`, `--pivot`, and `--rollup` show
up on the commands that return reports and queries, `--output-file` on
exports, and `--all` on commands that page; the options that apply to
every command fold into one line, and `--help-full` still lists
everything. Every flag is still accepted on every command.
- `dci --help` states the exit codes and the error format that scripts and
agents can rely on. See
[Exit codes](https://help.doit.com/docs/cli#exit-codes).
- `dci anomalies-recent` and `dci budgets-at-risk` are listed with the
anomalies and budgets commands instead of under "Additional Commands".
- `dci status` shows when your stored session expires, whether a refresh
token is saved, and where the session cache lives, and no longer calls an
expired token a cached login.

### Fixed

- `dci ai` no longer lets the assistant turn on request logging or point the
CLI at another server. The assistant's commands refuse `-v` /
`--rsh-verbose` and `-s` / `--rsh-server`, so your credentials can never be
printed into the conversation or sent to a host the assistant picked. The
commands fail with `FLAG_NOT_ALLOWED`.
- `dci <command> --help` and `dci --help` work without a session. They used
to fail with "no credentials available" when you were signed out or your
cache was stale — or open a browser login just to print usage.
- A login that cannot run, such as in CI or an agent session with no
browser, now fails with `AUTHENTICATION_REQUIRED` and exit code 10 on
every path, instead of "dci encountered an internal error" on some of
them. See [Exit codes](https://help.doit.com/docs/cli#exit-codes).

## v2.7.6 — September 10, 2026

### Improved
Expand Down
40 changes: 40 additions & 0 deletions command-docs/assign-contract-template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
command: assign-contract-template

arguments:
template-id: >-
Contract template ID as shown by `dci list-contract-templates` (the `id` field). The template
must belong to your organization and must not be archived.

examples:
- description: Create a draft contract from a template for two child customers.
command: "dci assign-contract-template <template-id> customerIds: [<customer-id>, <customer-id>]"
output: >-
The response lists `count` and one `createdContracts` entry (`customerId`, `contractId`) per draft,
in the order the customers were given.
- description: Assign the template to a longer list of customers, read from a file.
command: "dci assign-contract-template <template-id> < customers.json"
output: |
# customers.json
{"customerIds": ["<customer-id>", "<customer-id>", "<customer-id>"]}
- description: Capture the new draft contract IDs for a script.
command: >-
dci assign-contract-template <template-id> customerIds: [<customer-id>] --output json
| jq -r '.createdContracts[].contractId'

notes: |
Each customer gets a **draft** contract, which has no billing effect until it is completed and
activated. `customerIds` is required and takes 1 to 250 entries; each must be a tenant one tier
below yours (a distributor supplies resellers, a reseller supplies end customers). Repeated IDs
are ignored, but a customer that already holds a contract from this template receives another
draft. The current date must fall within the template's eligibility window. Requires the
ContractTemplatesAdmin permission, DoiT API access, and the `channelops:contracts:templates`
entitlement.

related:
[
list-contract-templates,
get-contract-template,
create-contract-template,
update-contract-template,
archive-contract-template,
]
27 changes: 27 additions & 0 deletions command-docs/create-role.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
command: create-role

examples:
- description: Create a custom role from permission IDs (take them from the `permissions` of `dci list-roles`).
command: >-
dci create-role name: "FinOps Analyst", description: "Read-only access to cost reports and budgets",
permissions: [<permission-id>, <permission-id>] --idempotency-key "$(uuidgen)"
- description: Create it from a JSON file.
command: 'dci create-role --idempotency-key "$(uuidgen)" < role.json'
output: |
# role.json
{"name": "FinOps Analyst", "description": "Read-only access to cost reports and budgets",
"permissions": ["<permission-id>", "<permission-id>"]}
- description: Capture the new role's ID for a script, for example to assign it to a user.
command: 'dci create-role name: "FinOps Analyst", permissions: [<permission-id>] --idempotency-key "$(uuidgen)" --output json | jq -r .id'

notes: |
Only `name` is required; omit `permissions` (or send an empty list) to create a role without
any. The name must be nonblank, free of leading or trailing whitespace, and unique among the
customer's roles, preset roles included (`409` otherwise). Duplicate permission IDs, or IDs
that do not exist or are not available to the customer, return `400`.

The API requires an `Idempotency-Key` on every call: retrying with the same key and body
returns the original response instead of creating a second role, while the same key with a
different body returns `422`.

related: [list-roles, get-role, update-role, delete-role]
46 changes: 46 additions & 0 deletions command-docs/create-service-account-token.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
command: create-service-account-token

arguments:
service-account-id: >-
Service account ID or name as shown by `dci list-service-accounts`. Names resolve (exact,
unique substring, or close typo); pass `--id` to force a literal ID.

examples:
- description: Mint a token that expires on a given date. Run it with --output json and store the `accessToken` in a secret manager at once.
command: >-
dci create-service-account-token <service-account-id> name: ci-pipeline-prod,
expiresTime: "2027-09-01T08:00:00Z" --idempotency-key "$(uuidgen)" --output json
- description: Mint a token with the server's default expiry.
command: 'dci create-service-account-token <service-account-id> name: ci-pipeline-prod --idempotency-key "$(uuidgen)"'
- description: Check the name server-side first; nothing is created.
command: "dci create-service-account-token <service-account-id> name: ci-pipeline-prod --dry-run"

notes: |
:::caution The secret is shown once
The `accessToken` in the create response is the credential that authenticates as the service
account. It is returned only by this operation and can never be retrieved again, so store it
somewhere safe on receipt and treat it like a password. Later get and list calls never
include it; if it is lost, create a new token and delete the old one.
:::

`name` is required (1-100 characters, no leading or trailing whitespace) and unique among
the account's tokens (`409`). The token inherits the service account's permissions as they
stand at creation; there is no per-token scoping. An account holds at most 10 tokens (`422`
at the cap). Requires the `serviceAccountCreator` permission, and the API requires an
`Idempotency-Key` on every real call.

:::info Dry run is shallow
`--dry-run` sends the API's `dryRun=true`, which checks only the name bounds: it does not
verify that the service account exists, that the name is unused, or that the account is
under the cap, so a dry run can succeed where the real create returns `404`, `409` or `422`.
The preview response has `accessToken` as an empty string.
:::

related:
[
list-service-account-tokens,
get-service-account-token,
update-service-account-token,
delete-service-account-token,
create-service-account,
]
41 changes: 41 additions & 0 deletions command-docs/create-service-account.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
command: create-service-account

examples:
- description: Create a service account with a set of permissions (camelCase names, as in the Console role editor).
command: >-
dci create-service-account name: terraform-ci, description: "Manages DoiT resources from CI",
permissions: [cloudAnalyticsReadOnly, budgetsManager] --idempotency-key "$(uuidgen)"
- description: Check the request server-side first; nothing is created and the CLI supplies a throwaway idempotency key.
command: "dci create-service-account name: terraform-ci, permissions: [cloudAnalyticsReadOnly] --dry-run"
- description: Create it from a JSON file and capture the new ID, to mint its first API token next.
command: 'dci create-service-account --idempotency-key "$(uuidgen)" --output json < service-account.json | jq -r .id'
output: |
# service-account.json
{"name": "terraform-ci", "description": "Manages DoiT resources from CI",
"permissions": ["cloudAnalyticsReadOnly", "budgetsManager"]}

notes: |
Only `name` is required (1-100 characters, no leading or trailing whitespace, unique among
the customer's service accounts, `409` otherwise). An unknown permission name returns `422`.
Requires the `serviceAccountCreator` permission.

A new service account cannot authenticate anything until it has an API token: mint one with
`dci create-service-account-token`. The API requires an `Idempotency-Key` on every real
call; retrying with the same key and request returns the stored response (keys are kept for
24 hours).

:::info Dry run is server-side
`--dry-run` sends the request with the API's `dryRun=true`: it runs the same checks as a real
create (name bounds and uniqueness, permission names, caller scope) and returns the same
errors without creating anything. In the response, `id`, `createTime`, `updateTime` and
`etag` are `null`.
:::

related:
[
list-service-accounts,
get-service-account,
update-service-account,
create-service-account-token,
delete-service-account,
]
34 changes: 34 additions & 0 deletions command-docs/create-signup-request.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
command: create-signup-request

examples:
- description: Start a trial signup for the organization that owns the admin's work email domain.
command: >-
dci create-signup-request --idempotency-key "$(uuidgen)"
email: admin@example.com, companyName: "Example Corp"
output: >-
Returns the signup request with its `id` (sr_ prefix), `status: pending_consent` and a `consentUrl`.
The admin receives a 6-digit verification code by email.
- description: Identify the calling agent for attribution.
command: >-
dci create-signup-request --idempotency-key <request-key>
email: admin@example.com, companyName: "Example Corp", source{channel: agent, agentClient: "my-agent 1.0"}
- description: Create it from a JSON file, and capture the request ID for the follow-up calls.
command: "dci create-signup-request --idempotency-key <request-key> --output json < signup.json | jq -r .id"
output: |
# signup.json
{"email": "admin@example.com", "companyName": "Example Corp"}

notes: |
`email` (the organization admin's work email; free-mail and disposable addresses are rejected) and
`companyName` are required, and so is `--idempotency-key` (1 to 256 characters). Retrying with the
same key returns the same request instead of creating a second one. No authentication is needed.

Nothing is provisioned until the admin verifies the request. Present the returned `consentUrl` to the
organization's admin, or enter the emailed code yourself with `dci verify-signup-request`, and poll
`dci get-signup-request` until the request is `completed`. The human channels (`console`, `web`) also
require a `turnstileToken` and `termsConsent{version, accepted}`; the default `agent` channel does not.

Conflicts return `409`: the email or domain is already registered (the response carries `signInUrl`), a
signup is already pending for it (the response carries `signupRequestId`), or the trial was already used.

related: [verify-signup-request, get-signup-request, resend-signup-verification, create-signup-session]
26 changes: 26 additions & 0 deletions command-docs/create-signup-session.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
command: create-signup-session

arguments:
signup-request-id: >-
Signup request ID (sr_ prefix), the `id` returned by `dci create-signup-request`.

examples:
- description: Exchange the activation token from verification for a sign-in session for the new tenant.
command: 'dci create-signup-session <signup-request-id> activationToken: "<activation-token>"'
output: >-
Returns `customToken`, `tenantId`, `customerId` and `consoleUrl`. Sign in with `signInWithCustomToken`
against `tenantId`, then open `consoleUrl`.
- description: Read the body from a file.
command: "dci create-signup-session <signup-request-id> < session.json"
output: |
# session.json
{"activationToken": "<activation-token>"}

notes: |
The `activationToken` is the one-time value returned by `dci verify-signup-request`. The session
token is bound to the request, single-use, and valid for 10 minutes. Provisioning may still be
running: `409` with `activation_not_ready` means retry shortly, while `activation_token_used` means
the token was already redeemed. An expired token returns `410` (`activation_token_expired`); use
the sign-in link from the completion page instead. No authentication is needed.

related: [create-signup-request, verify-signup-request, get-signup-request, resend-signup-verification]
19 changes: 19 additions & 0 deletions command-docs/delete-role.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
command: delete-role

arguments:
id: >-
Role ID or name as shown by `dci list-roles`. Names resolve (exact, unique substring, or
close typo); the confirmation shows the resolved target.

examples:
- description: Preview what would be deleted without sending a request.
command: "dci delete-role <role-id> --dry-run"
- description: Delete the role without an interactive prompt, for scripts.
command: "dci delete-role <role-id> --yes"

notes: |
Only custom roles can be deleted; a preset role returns `400`. A role that is still assigned
to users or groups, or held as a child tenant role, cannot be deleted (`409`): unassign it
first. A `503` means role cleanup is temporarily unavailable, so retry the deletion.

related: [list-roles, get-role, create-role, update-role]
33 changes: 33 additions & 0 deletions command-docs/delete-service-account-token.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
command: delete-service-account-token

arguments:
service-account-id: >-
Service account ID or name as shown by `dci list-service-accounts`. Names resolve (exact,
unique substring, or close typo); pass `--id` to force a literal ID.
id: >-
API token ID, as shown by `dci list-service-account-tokens` (the `id` field). Tokens are
not resolved by name; pass the ID.

examples:
- description: Check the deletion server-side without performing it (the CLI supplies a throwaway idempotency key).
command: "dci delete-service-account-token <service-account-id> <token-id> --dry-run"
- description: Delete the token without an interactive prompt, for scripts.
command: 'dci delete-service-account-token <service-account-id> <token-id> --idempotency-key "$(uuidgen)" --yes'

notes: |
:::caution Permanent
A deleted token stops authenticating immediately and cannot be restored. To pause a token
instead, use `dci update-service-account-token ... state: disabled`. Deleting an ID that no
longer exists returns `404`. Requires the `serviceAccountManager` permission.
:::

The API requires an `Idempotency-Key` on every real call.

related:
[
list-service-account-tokens,
get-service-account-token,
update-service-account-token,
create-service-account-token,
delete-service-account,
]
31 changes: 31 additions & 0 deletions command-docs/delete-service-account.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
command: delete-service-account

arguments:
id: >-
Service account ID or name as shown by `dci list-service-accounts`. Names resolve (exact,
unique substring, or close typo); the confirmation shows the resolved target.

examples:
- description: Check the deletion server-side without performing it (the CLI supplies a throwaway idempotency key).
command: "dci delete-service-account <service-account-id> --if-match <etag> --dry-run"
- description: Delete the account without an interactive prompt, for scripts.
command: 'dci delete-service-account <service-account-id> --if-match <etag> --idempotency-key "$(uuidgen)" --yes'

notes: |
:::caution Permanent
Deleting a service account also deletes all of its API tokens, which stop authenticating
immediately. Requires the `serviceAccountManager` permission. Deleting an ID that no longer
exists returns `404`.
:::

The API requires `If-Match` with the current ETag (the `etag` field of `dci
get-service-account`); a stale value returns `412`. It also requires an `Idempotency-Key`.

related:
[
get-service-account,
list-service-accounts,
create-service-account,
update-service-account,
delete-service-account-token,
]
23 changes: 23 additions & 0 deletions command-docs/get-commitment-policy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
command: get-commitment-policy

arguments:
policy-id: >-
Commitment policy ID as shown by `dci list-commitment-policies` (the `id` field). The built-in
policies use `conservative`, `balanced` and `max_savings`; custom policies have an opaque ID.

examples:
- description: Show a built-in policy and where it is assigned.
command: "dci get-commitment-policy balanced"
- description: Show a custom policy.
command: "dci get-commitment-policy <policy-id>"
- description: The full record as JSON, including its assignments.
command: "dci get-commitment-policy <policy-id> --output json"

notes: |
`assignments` lists explicit assignments across AWS and GCP only. An account whose settings have not
been materialized falls back to `balanced` implicitly and does not appear there. On AWS the
assignment is currently recorded per tenant and applied to every onboarded organization, so each
organization appears once per product line. The list is not paginated, and the request fails rather
than returning a partial list if either cloud cannot be queried.

related: [list-commitment-policies]
Loading
Loading