Tài liệu tham khảo

GetWebP CLI JSON Output Reference

Machine-readable output schemas for every GetWebP CLI command.

GetWebP CLI JSON Output Reference

Machine-readable output schemas for every GetWebP CLI command. Use --json on any command to emit structured NDJSON on stdout (human messages go to stderr).

See also: Commands Reference | Exit Codes | CI Integration


Overview#

With --json, the CLI emits NDJSON (newline-delimited JSON) — one JSON object per line. Every line is an envelope:

interface Envelope {
  '@timestamp': string    // RFC3339 UTC with ms precision, e.g. "2026-04-12T10:00:00.123Z"
  '@level': 'info' | 'warn' | 'error'
  '@message': string      // single-line human-readable summary
  '@module': string       // e.g. "getwebp.convert"
  type: string            // event discriminator (see per-command sections)
  data: object            // event payload (always present, {} if empty)
}

Key conventions:

  • Each line is valid JSON. The stream as a whole is not a JSON array.
  • The first line of every --json invocation is always a version preamble.
  • Warnings and progress messages are written to stderr, never mixed into the NDJSON stream.
  • Every command exits with a numeric exit code regardless of --json.
  • Fields prefixed with @ are reserved for the envelope. Command-specific payload lives in data.

Version preamble (first line, every command)#

{"@timestamp":"2026-04-12T10:00:00.000Z","@level":"info","@message":"GetWebP CLI 1.3.0","@module":"getwebp.cli","type":"version","data":{"getwebp":"1.3.0","ui":"1"}}

getwebp <path> --json#

The convert command emits a convert.completed event after all files have been processed, preceded by the version preamble. On the Free plan, when the file limit is exceeded, a convert.truncated event is emitted instead.

Event: convert.completed#

// data shape for convert.completed
interface ConvertCompletedData {
  processed: number       // total files attempted
  total: number           // same as processed
  successCount: number    // files converted successfully
  failedCount: number     // files that errored
  results: FileResult[]   // per-file details (see below)
}

Event: convert.truncated (Free plan, limit hit)#

// data shape for convert.truncated
interface ConvertTruncatedData {
  processed: number       // files actually converted
  skipped: number         // files skipped due to Free plan limit
  limit: number           // plan file limit (20 for Free)
  upgrade_url: string     // "https://getwebp.com/pricing"
}

Event: convert.failed (usage error)#

Emitted when the command cannot start (e.g. bad --format value, missing input).

interface ConvertFailedData {
  code: string            // machine-readable error code, e.g. "usage_error", "missing_input"
  hint?: string           // optional human-readable correction hint
}

FileResult union#

Each element in the results array is one of three shapes:

// Successfully converted
interface SuccessResult {
  file: string            // input file path
  outputPath: string      // absolute path to the written output file (.webp or .avif)
  originalSize: number    // bytes
  newSize: number         // bytes
  savedRatio: number      // 0 to 1 (negative if output is larger than original)
  saved: string           // human-friendly percentage, e.g. "35.0%"
  quality: number         // actual quality used
  qualityMode: 'auto' | 'fixed'  // auto = SSIM binary search; fixed = --quality or AVIF
  ssim?: number           // SSIM score (auto mode only)
  status: "success"
}
 
// Conversion failed
interface ErrorResult {
  file: string
  status: "error"
  error: string           // e.g. "Decode failed: Unsupported format"
}
 
// Skipped (--skip-existing or output-equals-input)
interface SkipResult {
  file: string
  status: "skipped"
  reason: "existing"
}

Examples#

All files succeed (Pro plan):

{"@timestamp":"2026-04-12T10:00:00.000Z","@level":"info","@message":"GetWebP CLI 1.3.0","@module":"getwebp.cli","type":"version","data":{"getwebp":"1.3.0","ui":"1"}}
{"@timestamp":"2026-04-12T10:00:01.234Z","@level":"info","@message":"Converted 2/2 files","@module":"getwebp.convert","type":"convert.completed","data":{"processed":2,"total":2,"successCount":2,"failedCount":0,"results":[{"file":"photos/hero.jpg","outputPath":"/abs/photos/hero.webp","originalSize":204800,"newSize":133120,"savedRatio":0.35,"saved":"35.0%","quality":82,"qualityMode":"auto","status":"success"},{"file":"photos/logo.png","outputPath":"/abs/photos/logo.webp","originalSize":51200,"newSize":20480,"savedRatio":0.6,"saved":"60.0%","quality":85,"qualityMode":"auto","status":"success"}]}}

Mixed success and failure:

{"@timestamp":"2026-04-12T10:00:00.000Z","@level":"info","@message":"GetWebP CLI 1.3.0","@module":"getwebp.cli","type":"version","data":{"getwebp":"1.3.0","ui":"1"}}
{"@timestamp":"2026-04-12T10:00:01.000Z","@level":"info","@message":"Converted 1/2 files","@module":"getwebp.convert","type":"convert.completed","data":{"processed":2,"total":2,"successCount":1,"failedCount":1,"results":[{"file":"photo.jpg","outputPath":"/abs/photo.webp","originalSize":204800,"newSize":133120,"savedRatio":0.35,"saved":"35.0%","quality":82,"qualityMode":"auto","status":"success"},{"file":"corrupt.png","status":"error","error":"Decode failed: Invalid PNG header"}]}}

AVIF output (--format avif):

{"@timestamp":"2026-04-12T10:00:00.000Z","@level":"info","@message":"GetWebP CLI 1.3.0","@module":"getwebp.cli","type":"version","data":{"getwebp":"1.3.0","ui":"1"}}
{"@timestamp":"2026-04-12T10:00:01.500Z","@level":"info","@message":"Converted 1/1 files","@module":"getwebp.convert","type":"convert.completed","data":{"processed":1,"total":1,"successCount":1,"failedCount":0,"results":[{"file":"photo.jpg","outputPath":"/abs/photo.avif","originalSize":204800,"newSize":98304,"savedRatio":0.52,"saved":"52.0%","quality":55,"qualityMode":"fixed","status":"success"}]}}

Free plan, file limit hit:

{"@timestamp":"2026-04-12T10:00:00.000Z","@level":"info","@message":"GetWebP CLI 1.3.0","@module":"getwebp.cli","type":"version","data":{"getwebp":"1.3.0","ui":"1"}}
{"@timestamp":"2026-04-12T10:00:05.000Z","@level":"warn","@message":"Processed 20/25 images — Free plan limit reached. 5 remaining.","@module":"getwebp.convert","type":"convert.truncated","data":{"processed":20,"skipped":5,"limit":20,"upgrade_url":"https://getwebp.com/pricing"}}

Usage error (bad --format):

{"@timestamp":"2026-04-12T10:00:00.000Z","@level":"info","@message":"GetWebP CLI 1.3.0","@module":"getwebp.cli","type":"version","data":{"getwebp":"1.3.0","ui":"1"}}
{"@timestamp":"2026-04-12T10:00:00.005Z","@level":"error","@message":"Unknown --format \"gif\". Supported: webp, avif","@module":"getwebp.convert","type":"convert.failed","data":{"code":"usage_error","hint":"Use --format webp or --format avif"}}

Missing input path:

{"@timestamp":"2026-04-12T10:00:00.000Z","@level":"info","@message":"GetWebP CLI 1.3.0","@module":"getwebp.cli","type":"version","data":{"getwebp":"1.3.0","ui":"1"}}
{"@timestamp":"2026-04-12T10:00:00.003Z","@level":"error","@message":"Please specify an input path","@module":"getwebp.convert","type":"convert.failed","data":{"code":"missing_input"}}

getwebp auth --json#

Schema#

// Success
interface AuthSuccessResponse {
  success: true
  data: {
    message: string   // e.g. "Activated — Pro plan unlocked"
  }
}
 
// Failure
interface AuthErrorResponse {
  success: false
  status: "error"
  error: "unknown_error"
  message: string         // server-provided error description
}

Examples#

Successful activation:

{
  "success": true,
  "data": {
    "message": "Activated — Pro plan unlocked"
  }
}

Failed activation:

{
  "success": false,
  "status": "error",
  "error": "unknown_error",
  "message": "Invalid license key"
}

getwebp status --json#

The shape of the data object varies depending on the license state.

Schema#

// Free plan (never activated)
interface StatusFreeResponse {
  success: true
  data: {
    version: string       // e.g. "1.0.1"
    mode: "free"
  }
}
 
// Active license, online (full status from server)
interface StatusOnlineResponse {
  success: true
  data: {
    version: string
    mode: "pro"
    licenseKeySuffix: string    // last 4 characters, e.g. "A1B2"
    expiresAt: string           // ISO 8601, e.g. "2027-04-01T00:00:00.000Z"
    devicesUsed: number
    devicesLimit: number
  }
}
 
// Active license, server cache (server returned data with cached flag)
interface StatusCachedResponse {
  success: true
  data: {
    version: string
    mode: "pro"
    licenseKeySuffix: string
    expiresAt: string
    cached: true                // indicates data is from local cache
  }
}
 
// Token exists but server unreachable and no status cache (JWT-only fallback)
interface StatusOfflineResponse {
  success: true
  data: {
    version: string
    mode: "pro"
    cached: true
    expiresAt?: string          // ISO 8601 (from JWT exp claim)
  }
}

Note on cached: The cached field is only present (and set to true) when the data comes from a local cache rather than a live server response. When the server responds successfully, cached is omitted and devicesUsed/devicesLimit are included instead.

Examples#

Free plan:

{
  "success": true,
  "data": {
    "version": "1.0.1",
    "mode": "free"
  }
}

Active Pro license (online):

{
  "success": true,
  "data": {
    "version": "1.0.1",
    "mode": "pro",
    "licenseKeySuffix": "A1B2",
    "expiresAt": "2027-04-01T00:00:00.000Z",
    "devicesUsed": 1,
    "devicesLimit": 5
  }
}

Cached status (offline):

{
  "success": true,
  "data": {
    "version": "1.0.1",
    "mode": "pro",
    "licenseKeySuffix": "X9Z7",
    "expiresAt": "2027-04-01T00:00:00.000Z",
    "cached": true
  }
}

JWT-only fallback (offline, no status cache):

{
  "success": true,
  "data": {
    "version": "1.0.1",
    "mode": "pro",
    "cached": true,
    "expiresAt": "2027-04-01T00:00:00.000Z"
  }
}

getwebp logout --json#

When --json is used, the interactive confirmation prompt is automatically skipped (same behavior as --force).

Schema#

// Success
interface LogoutSuccessResponse {
  success: true
  data: {
    message: "Device unbound. Switched to Free plan."
  }
}
 
// Failure
interface LogoutErrorResponse {
  success: false
  status: "error"
  error: string       // machine-readable code (see table below)
  message: string     // human-readable description
}

Error codes#

error valuemessageRetryable
network_unreachableCannot reach server. Retry later or unbind via dashboard.Yes
not_activatedNo active license on this device.No
device_not_foundDevice not found. It may have already been unbound.No
invalid_tokenToken is invalid or expired.No
OtherPassed through from serverVaries

Examples#

Successful logout:

{
  "success": true,
  "data": {
    "message": "Device unbound. Switched to Free plan."
  }
}

Network failure:

{
  "success": false,
  "status": "error",
  "error": "network_unreachable",
  "message": "Cannot reach server. Retry later or unbind via dashboard."
}

No active license:

{
  "success": false,
  "status": "error",
  "error": "not_activated",
  "message": "No active license on this device."
}

Error Codes Summary#

Machine-readable error codes that appear in data.code (convert) or error (auth/logout/status):

CodeCommandsMeaningRetryable
missing_inputconvertNo input path specifiedNo
usage_errorconvertBad flag value (e.g. unknown --format)No
unknown_errorauthUnclassified activation failureVaries
network_unreachablelogoutCannot reach API serverYes
not_activatedlogoutNo active license on deviceNo
device_not_foundlogoutDevice already unboundNo
invalid_tokenlogoutToken expired or revokedNo

jq Recipes#

The CLI emits NDJSON (one JSON object per line). Use grep or jq to select lines by event type, then parse the data field.

Convert command#

# Extract the convert.completed event
getwebp ./images --json | grep '"convert.completed"'
 
# Check if all conversions succeeded (failedCount == 0)
getwebp ./images --json \
  | grep '"convert.completed"' \
  | jq '.data.failedCount == 0'
 
# Get total bytes saved across all successful files
getwebp ./images --json \
  | grep '"convert.completed"' \
  | jq '[.data.results[] | select(.status == "success") | (.originalSize - .newSize)] | add'
 
# List failed files
getwebp ./images --json \
  | grep '"convert.completed"' \
  | jq '[.data.results[] | select(.status == "error") | .file]'
 
# Get output paths for all successfully converted files
getwebp ./images --json \
  | grep '"convert.completed"' \
  | jq '[.data.results[] | select(.status == "success") | .outputPath]'
 
# Get average compression ratio
getwebp ./images --json \
  | grep '"convert.completed"' \
  | jq '[.data.results[] | select(.status == "success") | .savedRatio] | add / length'
 
# Exit with error if any file failed
getwebp ./images --json \
  | grep '"convert.completed"' \
  | jq -e '.data.failedCount == 0' > /dev/null
 
# Check if Free plan limit was hit
getwebp ./images --json \
  | grep '"convert.truncated"' \
  | jq '.data.skipped'
 
# Extract file names and sizes as CSV
getwebp ./images --json \
  | grep '"convert.completed"' \
  | jq -r '.data.results[] | select(.status == "success") | [.file, .outputPath, .originalSize, .newSize, .saved] | @csv'

Status command#

# Get current plan
getwebp status --json | grep '"status.reported"' | jq -r '.data.mode'
 
# Check if license is active (not free)
getwebp status --json | grep '"status.reported"' | jq '.data.mode != "free"'
 
# Get expiry date
getwebp status --json | grep '"status.reported"' | jq -r '.data.expiresAt // "N/A"'
 
# Check device usage
getwebp status --json | grep '"status.reported"' | jq '"\(.data.devicesUsed // "?") / \(.data.devicesLimit // "?")"'

Auth and logout#

# Activate and check result
getwebp auth XXXX-XXXX-XXXX-XXXX --json | grep '"auth.completed"' | jq -e '.'
 
# Logout (--json skips confirmation automatically)
getwebp logout --json | grep '"logout.completed"' | jq -e '.'

CI pipeline pattern#

#!/usr/bin/env bash
set -euo pipefail
 
getwebp ./src/images -o ./dist/images --quality 85 --json > /tmp/gw_out.ndjson
 
completed=$(grep '"convert.completed"' /tmp/gw_out.ndjson)
 
if [ -z "$completed" ]; then
  echo "Conversion did not complete" >&2
  cat /tmp/gw_out.ndjson >&2
  exit 1
fi
 
failed=$(echo "$completed" | jq '.data.failedCount')
if [ "$failed" -gt 0 ]; then
  echo "Conversion failed for $failed file(s):" >&2
  echo "$completed" | jq -r '.data.results[] | select(.status == "error") | "  \(.file): \(.error)"' >&2
  exit 1
fi
 
total=$(echo "$completed" | jq -r '.data.total')
saved=$(echo "$completed" | jq '[.data.results[] | select(.status == "success") | (.originalSize - .newSize)] | add')
echo "Converted $total images, saved $saved bytes total"

Stderr Warnings#

Regardless of --json, the CLI may emit warnings to stderr. These are not part of the NDJSON stream and should not be parsed. Examples:

warn: Free plan: max 20 files, 3s delay between each.
warn: No --output specified: converted files will be written next to source files.
warn: Conflict: a.png, a.jpg all map to a.webp — only the last processed will survive
warn: --format avif: auto-quality is not supported for AVIF. Using quality=55. Pass --quality <N> to override.

To suppress warnings in scripts, redirect stderr:

getwebp ./images --json 2>/dev/null

AI Agent Integration#

For LLMs and AI agents consuming GetWebP output:

  1. Always use --json to get structured NDJSON on stdout.
  2. Each line is an independent JSON object — parse line-by-line, not as a JSON array.
  3. The first line is always a version preamble (type "version"). Skip it or use it to confirm CLI version.
  4. Find the line with "type":"convert.completed" to get the full result summary in .data.
  5. Check data.failedCount == 0 for full success.
  6. If "convert.truncated" appears instead of "convert.completed", the Free plan file limit was hit — check data.skipped.
  7. Per-file errors are in data.results[].error — human-readable strings, not error codes.
  8. data.results[].outputPath contains the absolute path of each generated file (.webp or .avif).
  9. Combine --json with exit codes for robust error handling: exit code 0 = full success, non-zero = some failure.