Reference

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

capture response
{
  "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, or file. A window also carries its ID; a file carries its path.

image.encodingstring | absent

Present as base64 when 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

diff response
{
  "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 changed is false.

comparison.changedboolean

The policy verdict, after the area threshold. See comparing two moments.

Observation

observation response
{
  "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, or timeout. The authoritative answer; the exit status is a convenience on top of it.

captures / comparisonsnumber

comparisons is always captures - 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 section
{
  "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, or metadata_older. Absent on a non-temporal request.

Errors

any failure
{
  "error": {
    "code": "invalid_region",
    "message": "region 100,200,800,600 does not fit inside 1920x1080"
  }
}

The full list is on the exit-codes page.