Automation and JSON output
Every tv command that supports --json writes one newline-delimited JSON document (two, for tv login --browser's pending/authenticated pair) to standard output per invocation, and reports failure as a non-zero exit code with a JSON error object on standard error. Human-readable output is the default; nothing below changes when --json is omitted except formatting.
The envelope
Every success document carries the shared schemaVersion field:
{ "schemaVersion": 1, "command": "version", "...": "..." }
schemaVersion is 1 for every implemented command today; automation should check it before trusting the rest of the shape, since it is the compatibility signal for future breaking changes. Every failure document carries the shared error shape instead:
{
"schemaVersion": 1,
"error": { "code": "AUTHENTICATION_REQUIRED", "message": "...", "correlationId": "..." }
}
error.code is one of the stable machine codes in the table below — never a free-text string. error.message is safe to print but not safe to pattern-match; match on error.code instead. error.correlationId is present on every JSON error (and is null only for IDENTITY_NOT_ALLOWED before the safe-correlation boundary in the device-authorization login path is reached).
Exit codes
| Exit code | Meaning |
|---|---|
0 | Success. |
1 | Unexpected local/internal failure without a mapped CliError. |
2 | Usage or INVALID_REQUEST error: missing/conflicting flags or malformed local input. |
3 | Authentication and mapped backend/domain errors. Branch on error.code, not the numeric exit code alone. |
130 | The interactive tv login prompt (email or password) was cancelled (Ctrl-C) before completion. |
Verified from this build (tv nonsense, a usage error, and tv whoami with no session, an authentication error):
$ tv nonsense; echo "exit=$?"
Error: Unknown command: nonsense
Run tv help for commands.
Correlation ID: 80d3fc27-2f01-420c-8d43-7b01f83ec1c7
exit=2
$ tv whoami --json; echo "exit=$?"
{"schemaVersion":1,"error":{"code":"AUTHENTICATION_REQUIRED","message":"Run tv login before using tv whoami.","correlationId":"9ec9dec8-90a9-4741-946a-bdd7f6ade22f"}}
exit=3
Correlation IDs
A correlation ID connects one operation across every boundary it actually reaches: CLI output, service-boundary evidence, and a database audit event, when those records exist. Local-only failures (a usage error, a pre-network failure) still return a safe, generated correlation ID, but imply no server or database record — a correlation ID supports lookup, it does not promise every layer produced one. See Incident investigation for a worked example reconstructing an operation end-to-end from its correlation ID, and Audit and observability for the full boundary contract.
Login JSON streams
Password sign-in (--user <email> --password-stdin --json) writes exactly one JSON document on success:
{"schemaVersion":1,"command":"login","status":"authenticated","expiresAt":"...","scopes":["account:read"],"tokenPrefix":"tv_...","correlationId":"..."}
The password itself is never accepted as a command-line argument and never appears in any output; pipe it on standard input as exactly one line. --json without --user/--password-stdin is a usage error (exit 2).
The fallback browser flow (tv login --browser --json) is the one invocation that writes more than one JSON line:
{"schemaVersion":1,"command":"login","status":"authorization_pending","userCode":"...","verificationUriComplete":"...","expiresAt":"...","correlationId":"..."}
{"schemaVersion":1,"command":"login","status":"authenticated","expiresAt":"...","scopes":["account:read"],"tokenPrefix":"tv_...","correlationId":"..."}
Neither line contains the device code or the issued token. Consumers must parse stdout one line at a time and wait for status: "authenticated" before treating login as complete.
Confirmation boundaries
Automation may pass --yes only on commands that document it (currently tv file move). A script should log its own intent and retain the returned correlationId for every access-reducing, update, or administrative call, so a human can look it up later in tv admin audit list.
Stable error codes
Every code below is a literal error.code value from packages/contracts/src, grouped by the command family that returns it. INVALID_REQUEST and INTERNAL_ERROR are common to every family and are not repeated in each row.
Session (tv login / tv whoami / tv logout)
| Code | Meaning |
|---|---|
AUTHENTICATION_REQUIRED | No valid session on disk. |
INVALID_CREDENTIALS | Unknown email, wrong password, or an account without a password — deliberately identical for all three so sign-in cannot enumerate accounts. |
AUTHENTICATION_FAILED | The device/browser handshake did not produce a valid session. |
IDENTITY_NOT_ALLOWED | The signed-in identity is not permitted to use tv. |
ACCOUNT_DEACTIVATED | The account or organization membership is deactivated. |
RATE_LIMITED | Too many attempts for this email, or too many organization-wide (per-email and global counters); wait and retry later. |
SESSION_STORE_UNAVAILABLE | The local credential file could not be read, written, or verified. |
AUTHORIZATION_PENDING | (device flow) Waiting on browser approval; keep polling. |
SLOW_DOWN | (device flow) Poll less frequently. |
AUTHORIZATION_DENIED | The browser approval was explicitly denied. |
DEVICE_CODE_EXPIRED | The one-time device code expired before approval. |
INVALID_DEVICE_CODE | The device code is unknown or already consumed. |
SCOPE_DENIED | The token's scope does not permit the requested operation. |
INVALID_BACKEND_RESPONSE | The backend returned a shape the CLI does not recognize. |
Update (tv update [--rollback])
See Command reference and Install and update from a private Git checkout for the full per-path refusal-code table (UPDATE_* for a checkout install, RELEASE_* for a standalone install).
Files (tv file *)
| Code | Meaning |
|---|---|
FORBIDDEN | Authenticated, but not permitted to act on this Root Folder/organization scope. |
ACCOUNT_DEACTIVATED | The acting account is deactivated. |
NOT_FOUND | No file with sufficient access exists at this ID (anti-enumeration: identical for "doesn't exist" and "exists but you can't see it"). |
FOLDER_NOT_FOUND | The named Root Folder does not exist. |
FOLDER_INACTIVE | The named Root Folder is deactivated. |
FILE_NAME_INVALID | The uploaded filename failed validation. |
FILE_MIME_TYPE_INVALID | --mime-type is not a well-formed MIME type. |
FILE_DESCRIPTION_INVALID | --description failed validation. |
FILE_METADATA_INVALID | A --meta/--set value does not match its field's type or choices. |
FILE_VERIFICATION_INVALID | The backend's own re-measurement of an uploaded object did not match. |
FILE_ARCHIVED | The command is refused because the file is archived (metadata updates and new content versions are rejected on an archived file; restore it first). |
FILE_COMMIT_CONFLICT | A concurrent commit already advanced this file's version. |
FILE_UPLOAD_FAILED | The upload to object storage failed. |
FILE_CHECKSUM_MISMATCH | The downloaded or uploaded bytes' SHA-256 did not match the declared value. |
FILE_SIZE_MISMATCH | The downloaded or uploaded byte count did not match the declared value. |
FILE_SEARCH_FILTER_INVALID | A tv file find filter is malformed or self-contradictory. |
FILE_UPDATE_REASON_REQUIRED | tv file update/archive/restore requires --reason/--set/--unset. |
FILE_METADATA_REQUIRED | A required metadata field was not supplied. |
FILE_METADATA_INCOMPATIBLE | A metadata value is incompatible with a field that changed since it was set. |
Access (tv file access * / tv folder access *)
| Code | Meaning |
|---|---|
FORBIDDEN | Not permitted to grant/revoke/list at the requested scope. |
NOT_FOUND | Anti-enumeration not_found for a file you cannot manage. |
USER_ROLE_INVALID | tv admin user role given a role other than admin/user. |
FOLDER_NOT_FOUND | Named Root Folder does not exist. |
FOLDER_INACTIVE | Named Root Folder is deactivated. |
GRANT_LEVEL_INVALID | --level is not read/write/manage. |
GRANT_SUBJECT_NOT_FOUND | The --user/--group grant subject does not exist. |
Groups (tv group *)
| Code | Meaning |
|---|---|
GROUP_NAME_INVALID | Group name failed validation. |
GROUP_NAME_CONFLICT | The caller already owns a group with that name (names are unique per owner, not organization-wide). |
GROUP_NAME_AMBIGUOUS | The caller belongs to more than one group with that name (different owners); a unique identifier is required. |
GROUP_NOT_FOUND | Anti-enumeration: no group by that name is visible to the caller, or it genuinely does not exist. |
GROUP_MEMBER_NOT_FOUND | The target email is not a member of the group. |
GROUP_ROLE_INVALID | The role is not owner/manager/member, or the caller cannot assign it (a manager promoting to owner). |
GROUP_ALREADY_MEMBER | The invited email already belongs to an active member of the group. |
GROUP_INVITATION_NOT_FOUND | Anti-enumeration: the invitation ID does not resolve to a real, still-pending invitation the caller may act on. |
GROUP_INVITATION_ALREADY_RESOLVED | The invitation was already accepted, declined, revoked, or has expired. |
GROUP_LAST_OWNER | The action would leave the group with zero active owners. |
GROUP_TRANSFER_TARGET_NOT_MEMBER | tv group transfer named someone who is not already an active member. |
Group administration (tv admin group *)
| Code | Meaning |
|---|---|
FORBIDDEN | Not an administrator. |
NOT_FOUND | The group does not exist. |
GROUP_MEMBER_NOT_FOUND | The target email is not a member of the group. |
GROUP_TRANSFER_TARGET_NOT_MEMBER | tv admin group transfer named someone who is not already an active member. |
GROUP_OWNER_DEACTIVATION_BLOCKED | tv admin user deactivate was refused because the subject solely owns a group that still has other active members; transfer ownership first. |
Root Folders and metadata (tv folder * / tv metadata * / tv admin folder * / tv admin metadata *)
| Code | Meaning |
|---|---|
FORBIDDEN | Not an administrator. |
ACCOUNT_DEACTIVATED | The acting account is deactivated. |
NOT_FOUND | Named Root Folder/definition does not exist. |
FOLDER_NAME_INVALID | Root Folder name failed validation. |
FOLDER_NAME_CONFLICT | A Root Folder or subfolder with that name already exists in the same location (owner for a root, parent for a subfolder), including a deactivated one — retired names stay reserved. |
FOLDER_PARENT_NOT_FOUND | --parent names a folder that does not exist, is in a different organization/root, or that the caller lacks live manage access to — deliberately indistinguishable from a nonexistent parent. |
FOLDER_DEPTH_EXCEEDED | The parent is already at the maximum nesting depth (32); the child would exceed it (on create or move). |
DEFAULT_FOLDER_PROTECTED | The default Root Folder ("My Files") cannot be deactivated or archived. |
FOLDER_ROOT_IMMOVABLE | tv folder move was given a root -- only a subfolder may move; a root may only be renamed. |
FOLDER_CROSS_ROOT_MOVE | tv folder move --parent names a folder in a different root tree; a subfolder may move only within its own root. |
FOLDER_CYCLE_DETECTED | tv folder move --parent would self-parent the folder or make it a child of its own descendant. |
FOLDER_NOT_EMPTY | tv folder archive was given a folder that still contains a file or a child folder, active or archived. |
FOLDER_PARENT_INACTIVE | tv folder restore was refused because a folder in its parent chain is not active. |
FOLDER_REASON_INVALID | --reason on a rename/move/archive/restore is empty, over 500 characters, or contains a control character. |
METADATA_KEY_INVALID | key failed validation. |
METADATA_KEY_CONFLICT | A definition with that key already exists. |
METADATA_LABEL_INVALID | --label failed validation. |
METADATA_TYPE_INVALID | --type is not text/number/boolean/date/choice. |
METADATA_CHOICES_INVALID | --choice values failed validation. |
METADATA_CHOICE_MUTATION_INVALID | --add-choice/--remove-choice given for a non-choice field, or would remove a choice in use. |
METADATA_INACTIVE | The definition is deactivated. |
Audit (tv admin audit list)
| Code | Meaning |
|---|---|
FORBIDDEN | Not an administrator. |
ACCOUNT_DEACTIVATED | The acting account is deactivated. |
AUDIT_FILTER_INVALID | A filter combination is malformed (bad --limit, --cursor, date, or enum value). |
Agent Skill (tv skill *)
| Code | Meaning |
|---|---|
SKILL_BUNDLE_NOT_FOUND | Neither a linked Git checkout's disk payload nor a standalone binary's embedded payload could be located (a compiled release binary embeds its own skill bundle, see Agent Skill). |
SKILL_NO_REPOSITORY | --scope project requested outside a Git repository. |
SKILL_NOT_INSTALLED | tv skill update found no prior install for this target/scope. |
SKILL_MODIFIED_FILES | Install/update would overwrite files changed outside tv; retry with --backup-and-replace. |
SKILL_MANIFEST_CORRUPT | The install's manifest file is not valid JSON matching the expected schema. |
Compatibility
Every implemented command's JSON documents are schema version 1 today. Production backend-compatibility negotiation (comparing tv version --json against an authenticated backend response) remains planned.