Response schema
The JSON contract, field by field.
One document per response. The shape depends on the command, but the conventions do not: source geometry is always in source coordinates, errors always carry a code, and a view always states how to map its pixels back.
Capture
{
"source": { "kind": "desktop|region|window|file", "display": ":1",
"x": 0, "y": 0, "width": 1920, "height": 1080 },
"image": { "width": 960, "height": 540,
"media_type": "image/jpeg", "format": "jpeg",
"byte_length": 14907, "encoding": "base64",
"quality": 75, "data": "/9j/..." },
"transform": { "origin": "top-left", "offset_x": 0, "offset_y": 0,
"scale_x": 2.0, "scale_y": 2.0 },
"timing": { "capture_us": 910, "resize_us": 620, "encode_us": 3870,
"base64_us": 330, "total_us": 5730 }
}- source.kindstring
desktop,region,window, orfile. A window also carries its ID; a file carries its path.- image.encodingstring | absent
Present as
base64when the payload is embedded. Absent when the image went to stdout or a file instead.- image.datastring | absent
The base64 payload. Only present with
--base64.
Diff
{
"before": { "source": {...}, "width": 800, "height": 600 },
"after": { "source": {...}, "width": 800, "height": 600 },
"comparison": {
"mode": "exact", "pixel_threshold": 0, "area_threshold": 0.0,
"changed": true,
"changed_pixels": 5000,
"total_pixels": 480000,
"changed_fraction": 0.010416666666666666,
"bounding_box": { "x": 200, "y": 150, "width": 100, "height": 50 }
},
"timing": { "load_us": 3611, "compare_us": 347, "crop_us": 832,
"total_us": 4791 },
"changed_view": {
"raw_changed_rect": {...}, "returned_rect": {...},
"padding": 20, "fell_back_to_overview": false,
"view": { "name": "changed", "kind": "changed_region", ... }
}
}changed_view is present only when --changed-region was given, and is null when nothing differed at all.
- comparison.bounding_boxobject | null
The factual location of differences. Present even when
changedisfalse.- comparison.changedboolean
The policy verdict, after the area threshold. See comparing two moments.
Observation
{
"observation": {
"kind": "observe",
"result": "observed",
"elapsed_ms": 931,
"captures": 10,
"comparisons": 9,
"stable_for_ms": 300,
"change_detected_ms": 204
},
"source": {...},
"transform": {...},
"image": {...},
"first_change": { "changed": true, "changed_fraction": 0.032,
"bounding_box": {...} },
"comparison": { "changed": false, "changed_fraction": 0.0004,
"bounding_box": {...} },
"timing": {
"captures": 10, "comparisons": 9,
"capture_us_total": 76420, "compare_us_total": 1520,
"sleep_us_total": 853000,
"encode": { "resize_us": 0, "encode_us": 13300, "base64_us": 90 }
}
}- observation.resultstring
changed,stable,observed, ortimeout. The authoritative answer; the exit status is a convenience on top of it.- captures / comparisonsnumber
comparisonsis alwayscaptures - 1, because a comparison needs a pair.- timing.sleep_us_totalnumber
Deliberate waiting, reported separately from work. A large value is the feature, not a problem.
Presentation
Attached as presentation beside the command’s own body, and absent entirely when no presentation flags were supplied. That absence is what keeps earlier behaviour unchanged.
{
"presentation": {
"frames": [
{ "frame_id": 4,
"capture_offset_us": 2384, "capture_duration_us": 2383,
"age_us": 169824,
"views": [
{ "name": "overview", "kind": "overview",
"source_rect": { "x": 0, "y": 0, "width": 640, "height": 480 },
"transform": {...},
"image": { "width": 320, "height": 240,
"media_type": "image/jpeg", "format": "jpeg",
"byte_length": 4213, "data": "..." },
"applied": { "format": "jpeg", "quality": 45, "width": 320,
"base64": true, "metadata_only": false } }
],
"encoded_bytes": 4213, "base64_bytes": 5618 }
],
"payload": { "budget_base64_bytes": 30000,
"actual_base64_bytes": 27841,
"fit": "adjusted",
"adjustments": [...] },
"timing": { "presentation_us": 4690, "crop_us_total": 120,
"resize_us_total": 980, "encode_us_total": 3100,
"base64_us_total": 380, "budget_fit_us": 0 },
"total_encoded_bytes": 27841,
"total_base64_bytes": 37121,
"temporal_mode": "newest_detailed"
}
}- view.appliedobject
The settings actually used, separate from what was requested, so a budget adjustment is visible in the view itself.
- view.imageobject | null
Null for a metadata-only view, which also reports
"metadata_only": true— so an absent image is never confused with a failed encode.- view.transformobject
Always present, derived from the source rectangle and the returned image size. Never stored, so the two cannot disagree.
- total_base64_bytesnumber
Measured from the strings that actually travelled, including padding, rather than derived from
byte_length. Two independent statements of the same number, and they always agree.- temporal_modestring | absent
all_same,newest_detailed,newest_only, ormetadata_older. Absent on a non-temporal request.
Errors
{
"error": {
"code": "invalid_region",
"message": "region 100,200,800,600 does not fit inside 1920x1080"
}
}The full list is on the exit-codes page.