unkey projects list and unkey keys get share the flags below. The legacy unkey api paths accept the same flags. These output options don’t apply to unkey login, unkey deploy, or unkey healthcheck.
Root key used for the request. Falls back to
UNKEY_ROOT_KEY, then stored credentials. See CLI authentication for the full precedence order.Base URL of the API. Falls back to
UNKEY_API_BASE_URL. You don’t normally need to set it.Path of the TOML config file. Falls back to
UNKEY_CONFIG. An explicit --config takes precedence over a stored CLI session. A custom file is read as-is and is not copied into the keychain. unkey login --root-key=- writes the keychain, or ~/.unkey/config.toml when the keychain is unavailable. It does not write a custom config path.Use
human (default) for name-first summary tables and single-response trees, or json for the unchanged full response envelope, including all fields and pagination. Trees wrap long values without truncation. Terminal tables use relative timestamps and may shorten display names and descriptions, but never identifiers, slugs, or credentials. Redirected human output keeps complete values and uses RFC3339 UTC timestamps. Falls back to UNKEY_OUTPUT; other values are rejected. --json also selects JSON output. Add --debug to print the request ID to stderr. See Output formats.Print the full response as JSON. Takes precedence over a valid
--output or UNKEY_OUTPUT value.Print the successful response’s request ID to stderr, labeled
Request ID:. It stays separate from stdout, including when you use --json.Select
human or json for errors on stderr, including flag parsing, missing inputs, and API failures. This is independent of success output: --json alone does not change errors.A JSON request body to send instead of building one from the command’s flags. Commands without a request body, such as
unkey workspace flags get and unkey workspace limits, don’t accept this flag. See Send a raw body.Output formats
The defaulthuman format shows name-first summary tables for lists, trees for individual responses, and a confirmation for actions without response data. Trees apply to resource details, create and update results, key verification, and rate-limit checks. For example, unkey projects list shows the name, slug, and update time in a terminal:
2 hours ago. When stdout is redirected to a file or pipe, headers remain uppercase and human-readable timestamps use RFC3339 in UTC, such as 2026-10-28T12:00:00Z. JSON timestamps keep the API’s original representation.
To fit the terminal width, tables shorten only display names and descriptions with an ellipsis (…). Identifiers, slugs, and credentials remain complete, including names used to identify resources. If the remaining columns still don’t fit, the CLI prints labeled records instead of truncating identifiers. Trees wrap long values and keep the branch indentation without truncating text. Returned keys stay contiguous so you can copy them without inserted line breaks. Redirected output isn’t shortened or wrapped.
An empty list prints a message instead of a table:
✓ Updated settings or ✓ Saved variables. A create confirmation adds the requested slug or name. Returned data and request targets follow on indented lines:
✓ Deletion requested instead of claiming the work has finished. The check mark is green only on a color terminal. NO_COLOR, CLICOLOR=0, and TERM=dumb turn color off.
After guided selection, target lines include the selected resource’s label and ID. Targets entered manually remain unchanged. JSON output does not include these display labels.
Clearing role or permission assignments prints ✓ Updated roles or ✓ Updated permissions, followed by No roles assigned. or No permissions assigned. An empty list operation still prints No roles found. or No permissions found.
Human environment-variable lists hide values by default. Add --reveal to unkey environments variables list to show recoverable values. Write-only values show not retrievable. An empty recoverable value shows (empty) when revealed. JSON preserves returned values regardless of --reveal, so treat redirected JSON as sensitive data.
Key creation and reroll show the key ID before the complete returned key. Human mode also writes a storage warning to stderr. Ordinary keys cannot be retrieved later unless created as recoverable, with the required encryption and decryption access. Root key plaintext cannot be retrieved later. JSON mode does not add a storage warning.
Tables select fields for readability. They aren’t a complete representation of the response. Use --json or --output=json to get every field, including meta, data, and pagination when present. JSON mode preserves the full response envelope and writes only JSON to stdout:
UNKEY_OUTPUT=json to select it for every resource command in a shell session. Both --output and UNKEY_OUTPUT accept only human and json; unsupported values fail instead of falling back to human output. An explicit valid --output overrides UNKEY_OUTPUT, including an invalid environment value. Help and offline discovery remain available when UNKEY_OUTPUT is invalid.
When more results are available, human output includes a hint to repeat the command with --cursor. JSON output includes the API’s pagination fields instead. Keep the same filters when requesting the next page:
--body, the hint tells you to set cursor inside the JSON body instead. Keep the other request fields and don’t combine --body with --cursor:
--debug when you need it for troubleshooting. JSON output still includes meta.requestId, with or without --debug.
Interactive prompts
Canonical resource commands such asunkey projects create ask for required inputs that are missing. Update commands also ask what to change when you supply only the target. Both stdin and stderr must be terminals.
--interactive to edit optional settings before the final review.
Each answered or supplied field stays above the current question as one line, such as ✓ Name Demo Service, with nested objects and lists indented below it. Selected resources show their name and ID. Before an action, select the action, such as Create project, to proceed. Select Edit… to show editable fields in the same menu. After an edit, select the action to submit without another confirmation screen.
--interactive and target-only updates open this menu with editable fields visible and targets last. Destructive actions start with the compact action, Edit…, and Cancel menu. They include their warning here and default to Cancel, even with --yes.
Nested edits keep the other entered fields. Secrets, environment variable values, and arbitrary JSON values show as [hidden]. Select Cancel or press Ctrl+C to stop without sending the request.
Read-only commands, such as lists and searches, run after you finish selecting filters. They don’t ask you to confirm a request.
The summary uses Yes and No for booleans and distinguishes Clear (null), (empty text), and (no items). It stays human-readable on stderr even with --json; the JSON response on stdout is unchanged.
Prompts use your terminal theme’s colors. Set NO_COLOR to any value, or CLICOLOR=0, to turn color off; labels and values are then separated by a colon. On Windows or when TERM=dumb, prompts don’t move the cursor: each question is printed on its own line, and choices are numbered so you type a number.
Complete commands run directly unless you pass --interactive. Review does not add a prompt to scripts or complete flag-based commands.
Ask for inputs even when all required fields are supplied. Use this for optional settings, list filters, or search fields. Requires terminals on stdin and stderr. You can’t combine it with
--body or --no-input.Never ask for input. Missing required flags fail instead of opening prompts. This doesn’t confirm destructive commands. Use
--yes separately when needed.--interactive. For example, unkey projects list --interactive lets you set search text and a page limit:
null. Choosing No sends false, entering zero sends 0, and submitting a list with no items sends []. These values aren’t the same as leaving a field out.
Correct rejected input
If the API rejects the input with field errors, the CLI lists each rejected field with the API’s message and offers Edit answers. Your answers stay in memory until the command exits. Edit the rejected field, check the summary, and select the action again to send the corrected request. Selecting Cancel returns the API error. The CLI does not resend an unchanged request. This correction flow applies to the API’sinvalid_input 400 response with field errors. It does not reopen input for permission failures, conflicts, other 400 responses, or 422 resource-limit errors. Resource commands do not automatically retry API requests, including network and 5xx failures. A failed mutation may have completed. Check its state before running it again.
Choose existing resources
Resource selectors show existing resources and a text input together. Use the arrow keys to choose a result, type to filter the loaded results, or enter a custom value and select Use. The CLI does not submit a target until you select it. Browsing is available for projects, apps, environments, keys, root keys, permissions, roles, identities, rate limit namespaces and overrides, deployments, domains, and gateway policies. Selectors without a browser, such as API IDs, use text input. On Windows or withTERM=dumb, choose Browse existing resources or Enter a value manually first.
Project choices include slugs. Key choices include their start prefix, not the complete key or its end. Environment-variable removal offers existing variable names within the selected project, app, and environment. Variable values never appear in this picker.
If a custom value fails validation in the combined picker, the input stays editable and the list does not reload. Result counts include resources only, not Use, Search all for, or Next page actions.
In the combined picker, Esc clears the filter. With an empty filter, Esc returns from editing a selected resource without discarding your other answers. At the first selection, it cancels the command. Ctrl+C always cancels. When you edit a resource, its current ID is highlighted if it appears in the loaded results.
Browsing asks for parent scope only when the list endpoint requires it. Domains and deployments can be listed across the workspace. Search all for sends your text to the API where the list endpoint supports search. Typing alone does not send a search request. Clear search removes the filter. Next page uses the API’s pagination cursor when more results are available.
Manual entry doesn’t require list permission. If loading choices fails, the CLI falls back to manual entry. The final request still requires the endpoint’s permissions.
Keep scripts noninteractive
Prompts are available only on canonical resource commands, not legacyunkey api paths. An explicit --body, --no-input, or --interactive=false disables them. Redirecting stdin or stderr also disables automatic prompts. Explicit --interactive fails when either isn’t a terminal.
Prompts go to stderr, leaving stdout for the result. Redirecting only stdout or selecting --json doesn’t disable prompts when stdin and stderr remain terminals. Use --no-input in scripts to make that choice explicit:
Find command options
Use--help to list commands or read a command’s flags, requirements, and examples. Request flags appear before shared controls such as authentication and output options. Help works without credentials or API requests.
Send a raw body
Normally a command builds its request from its flags, one per request field, and checks them before sending. If you already have the request as JSON, for example from a file or another tool, pass it with--body instead:
--body:
- It must be one JSON value that matches the endpoint’s request. Most endpoints accept an object.
unkey ratelimit multi-limitaccepts an array. - Unknown fields fail before anything is sent, so a typo in a field name doesn’t get silently ignored.
- You can’t combine it with the command’s request flags. Required flags aren’t needed either, so
unkey keys create --body='...'doesn’t also need--api-id. - Authentication, API URL, configuration, and output flags still work.
Confirm destructive commands
Delete commands and the commands that remove environment variables, key roles, or key permissions ask for confirmation before sending the destructive request. The same rule applies to canonical and legacy paths, with request flags or--body. Promotion, rollback, stop, and general update commands don’t use this confirmation.
The prompt appears on stderr and shows the target from your request. It doesn’t infer an environment:
- Environment variable removal shows the requested project, app, environment, and variable names.
- App and project deletion warn that all their environments are affected.
- Other targets show their requested IDs, slugs, names, or identifiers. Key deletion distinguishes permanent erasure from soft deletion.
[y/N]: enter y or yes to proceed. An empty answer, any other answer, or end of input cancels with a nonzero exit status. Both stdin and stderr must be terminals. If either is redirected, the command refuses to proceed and tells you to use --yes.
In scripts, pass --yes to confirm explicitly. Use --json separately when you need a machine-readable response:
--yes skips the local [y/N] prompt for complete commands. It does not skip the final review after guided input or bypass authentication, permissions, or delete protection. --json and --body do not imply confirmation. Redirecting only stdout also does not imply confirmation; the CLI can still prompt if stdin and stderr remain terminals.
Terminal output and scripts
Resource commands write results to stdout. Confirmation prompts, secret-storage warnings, debug request IDs, and errors go to stderr, so they don’t become part of a JSON pipe. Human output remains the default when stdout is redirected; choose--json explicitly in scripts.
unkey deploy writes progress and deployment context to stderr, then final deployment details and domains to stdout. It does not support JSON output. Deployment spinners and colors require stderr to be a terminal. TERM=dumb disables both, and a nonempty NO_COLOR disables color without disabling a terminal spinner. Redirected stderr contains plain progress messages. See Deploy output.
unkey login writes the confirmation code, browser URL, instructions, and waiting messages to stderr. Only the final success goes to stdout. unkey logout also keeps diagnostics separate from its success message, including any notice that another stored workspace is now active. See CLI authentication.
Errors and exit codes
A failed command writes its error to stderr and exits with status 1. Resource action errors start withError: unkey <command> failed. and give recovery guidance. API errors include a labeled Request ID: when the response provides one, even without --debug. These diagnostics never become part of stdout or its JSON response.
Cancellation also exits with status 1, but prints cancelled; no request was sent without the failure banner. Cancelling while editing an API-rejected request says no further request was sent instead.
Use
--error-format=json to get an error object on stderr with message and optional status, type, requestId, hint, and fields. This does not include raw HTTP response bodies. Local action errors without an HTTP status still include message. This option does not change success output or exit codes.
Flag input diagnostics use stable urn:unkey:cli:<kind> types, such as missing_required_flag, invalid_flag_value, or conflicting_flags. Their fields[].location entries identify flags such as --name. Invalid flag values are not echoed. Validation guidance uses the declared flag help instead of arbitrary validator messages.
Completed API calls exit 0, including empty lists, accepted asynchronous actions, and key verification responses with valid: false. A verification NOT_FOUND can mean a missing or inaccessible key. It does not prove the key is absent. Help and version requests (--help, -h, help <command>, --version, unkey version) exit 0 without calling the API.
Flag syntax
--api-id=api_123 and --api-id api_123 are the same. A boolean flag on its own (--enabled) means true, and you can write --enabled=false. Flags can go before or after positional arguments. An unknown flag fails right away, and the message lists the flags the command accepts.