Output formats
Every command supports two output formats. Human-readable text is the default; the global --json flag switches to machine-readable JSON.
Text vs JSON
tbxl rows list --sheet Users # human-readable table
tbxl rows list --sheet Users --json # JSON for scriptingWhen --json is set:
- Property names are snake_case (e.g.
spreadsheet_id,service_account_email). - Data goes to stdout; warnings and errors go to stderr. This split keeps stdout cleanly parseable even when something goes wrong — see Exit codes & errors.
- Values are strings. Cells are never coerced to numbers or booleans;
IsActivecomes back as"0"/"1".
JSON shapes by command
rows list
total is the number of records that matched before --offset/--limit.
{ "rows": [ { "Id": "4", "Name": "Max", "IsActive": "1" } ], "total": 4 }rows add
row is the 1-based sheet row where the record was appended.
{ "affected": 1, "dry_run": false, "row": 7 }rows update
The rows array is capped at 50 entries; truncated is true when more rows were affected than returned.
{
"matched": 1,
"affected": 1,
"dry_run": false,
"rows": [ { "row": 3, "before": { "IsActive": "0" }, "after": { "IsActive": "1" } } ],
"truncated": false,
"returned": 1
}rows delete
Same shape as update, but each row carries the full deleted record under data instead of before/after.
{
"matched": 1,
"affected": 1,
"dry_run": false,
"rows": [ { "row": 3, "data": { "Id": "7", "Name": "Sam" } } ],
"truncated": false,
"returned": 1
}rows upsert
Like rows update plus an action of "updated" or "inserted".
// insert
{ "matched": 0, "affected": 1, "action": "inserted", "dry_run": false, "row": 7 }auth check
{
"service_account_email": "[email protected]",
"spreadsheet_id": "1AbC...xyz",
"status": "OK",
"sources": { "credentials": "flag", "spreadsheet_id": "config", "sheet": "env" }
}Errors
Errors are emitted on stderr with a snake_case code:
{ "code": "invalid_arguments", "message": "...", "details": null }The full list of code values is on Exit codes & errors.
row is the sheet row, not the Id
In every receipt, row is the 1-based sheet row — the header is row 1, so the first data row is row 2. It is not the record's Id and not a 0-based offset.