Skip to content
Search Documentation
Search across all documentation pages
Output Reports

Output reports

status: "completed" says the encode finished. It does not say the file is the one you asked for.

An encoder exits zero on plenty of files that are not what was promised: an audio-only source packaged as video, a source truncated at 60% of its length, an 8-bit file still wearing HDR tags. The output report closes that gap. Finished outputs carry report once the worker that produced them measures it; older outputs have no report. It holds the facts read back from the produced file, plus a verdict on whether those facts match the output spec the job was created with.

{
  "id": "out_m1n2o3p4q5",
  "object": "job_output",
  "status": "completed",
  "report": {
    "container": "mp4",
    "video": {
      "codec": "h264",
      "profile": "high",
      "level": "4.0",
      "pix_fmt": "yuv420p",
      "width": 1920,
      "height": 1080,
      "frame_rate": 29.97,
      "bitrate_kbps": 4800,
      "color": {
        "primaries": "bt709",
        "transfer": "bt709",
        "matrix": "bt709",
        "range": "tv"
      },
      "hdr_format": "none"
    },
    "audio": [
      {
        "codec": "aac",
        "channels": 2,
        "sample_rate_hz": 48000,
        "bitrate_kbps": 128,
        "language": "eng"
      }
    ],
    "duration_seconds": 763.1,
    "verdict": {
      "matches_request": true,
      "mismatches": []
    },
    "checked_at": "2026-09-14T10:55:08Z"
  }
}

The facts are measured on the machine that produced the file. The verdict is computed by the API, from the job’s own spec — so one job yields one verdict no matter which build encoded it, and an encoder cannot report itself correct.


When a report appears

report is present once the output has been measured, and omitted otherwise. Two things carry no report: an output that never produced a file, and an output produced before this field existed.

Absence means “not measured”. It never means “nothing wrong”. Check that report is present before reading verdict, and treat a missing report as unknown rather than as a pass.

A failed output carries its report too, and that is the case where it matters most: an output rejected for not being what was asked for is exactly the one where you need to see what it was instead. A canceled output carries none — it was interrupted rather than finished, so there is no produced file to describe.

Reports ride JobOutput, so they appear everywhere an output does:

SurfaceCarries the report
JobService.GetJob, JobService.WatchJobYes
JobService.ListJobsYes
output.ready, output.failed webhooksYes, on the output object itself
job.succeeded, job.failed, job.canceled webhooksYes, inside outputs[]
The job detail page in the dashboardYes, as an “Output report” panel per output

No webhook event was added for this. The field rides the events you already receive, so an existing webhook integration picks it up without changing anything.


Attributes

AttributeTypeDescription
containerstringDelivery format of the output, lowercase: mp4, mov, webm, mkv, hls, dash or adaptive. The same vocabulary as the request’s type. It names what was delivered, not what a demuxer would call an intermediate file. It is the one field here that is not a measurement — see What container is, and is not.
videoobject | nullThe produced video stream. Absent when the file carries no video. See The video stream.
audioarrayThe produced audio streams, in the order the container carries them. Empty for an output encoded without audio. See Audio streams.
duration_secondsnumber | nullMeasured playback duration of the produced file. Read from the file itself, which is what lets it catch an encode that stopped early.
verdictobjectWhether the file matches what was asked for, and where it does not. See The verdict.
checked_atstringISO 8601 timestamp of when the produced file was measured.

Which rendition the facts describe

For a single-file output, the file itself.

For a multi-rendition output — an ABR ladder, HLS, DASH, adaptive — the facts describe the highest-resolution rendition. That is the same rendition width, height and average_bitrate_kbps already report, and the one the verdict is judged against. Per-rendition detail stays in variant_results.

Measurements are taken on the encoded rendition before packaging, which is why container states the delivery format rather than whatever a probe would call an individual segment.

What container is, and is not

Every other field in a report is read off the produced bytes. container is not: it states the output’s delivery format, which is the thing the request already named.

That makes it the one field the verdict cannot really police. A container mismatch is in the vocabulary below for completeness, but in practice a report that echoes the requested type always matches. The real check that a file is in the container it was promised runs on the encoder host, before the file is delivered, and it fails the output outright rather than describing it. container is here so a report is self-describing on its own, not as that check’s second opinion.

The video stream

AttributeTypeDescription
codecstringCodec the stream is encoded with, lowercase: h264, hevc, vp9, av1. Note hevc — that is the decoder name for the codec a request calls h265.
profilestring | nullEncoded profile, lowercase and without spaces, e.g. high, main, main10, baseline. Absent when the codec has no profile concept or the encoder reported none.
levelstring | nullEncoded level as the codec expresses it, e.g. 4.0, 5.1.
pix_fmtstring | nullPixel format, e.g. yuv420p (8-bit) or yuv420p10le (10-bit). This is how bit depth is read back: a 10-bit output that came out 8-bit says so here.
widthintegerEncoded frame width in pixels, as stored.
heightintegerEncoded frame height in pixels, as stored.
frame_ratenumber | nullMeasured average frame rate, e.g. 29.97, 30, 59.94.
bitrate_kbpsinteger | nullMeasured video bitrate. Absent when the container carries no per-stream bitrate.
colorobject | nullColour signalling carried by the stream. See Colour signalling.
hdr_formatstringHDR format the stream signals. See What hdr_format can say.

Colour signalling

Each value is the lowercase name the format assigns it. Absent means the stream carries no value for it.

AttributeTypeDescription
primariesstring | nullColour primaries, e.g. bt709 (SDR), bt2020 (wide gamut).
transferstring | nullTransfer characteristics, e.g. bt709 (SDR), smpte2084 (PQ / HDR10), arib-std-b67 (HLG).
matrixstring | nullMatrix coefficients, e.g. bt709, bt2020nc.
rangestring | nullSignal range: tv (limited) or pc (full).

Audio streams

AttributeTypeDescription
codecstringCodec the stream is encoded with, lowercase, e.g. aac, opus, mp3, flac.
channelsinteger | nullChannel count: 1 for mono, 2 for stereo, 6 for 5.1.
sample_rate_hzinteger | nullSampling rate in hertz, e.g. 48000.
bitrate_kbpsinteger | nullMeasured audio bitrate. Absent when the container carries no per-stream bitrate.
languagestring | nullLanguage tag carried by the stream, e.g. eng, spa. Absent when the stream is untagged.

Why the values are strings and not enums

container, codec, profile, level, pix_fmt, the colour fields and hdr_format are lowercase strings rather than fixed enums. Those vocabularies belong to the media formats, not to us: a pixel format or profile we have not catalogued yet has to be reportable the day it appears, rather than wait for a new enum value to travel through three SDKs.

Two consequences worth planning for:

  • Compare with == against the spellings documented above, and treat an unrecognised value as something new, not as an error.
  • container is the closed one. It is the delivery format of the output, from the same list as the request’s type.

What hdr_format can say

Today hdr_format resolves to exactly one of none, hdr10 or hlg. Nothing else is ever emitted.

It is derived from the colour signalling above, so it is a reading of the file rather than an echo of the request: an SDR source that arrived wearing HDR tags, and whose tags we rewrote, reports none.

hdr10_plus and dolby_vision are reserved spellings — documented so you can switch on them ahead of time, and not emitted. Separating HDR10+ from static HDR10, or finding a Dolby Vision RPU, needs per-frame side data the report does not carry. Until that ships, an HDR10+ or Dolby Vision output reads as hdr10 here, which is the static layer it also genuinely signals. Do not read hdr10 as proof the dynamic layer is absent.


The verdict

AttributeTypeDescription
matches_requestbooleanTrue when every property we can check matched the request. Properties the request left open cannot make this false.
mismatchesarrayEvery property that did not match, one entry each. Empty when matches_request is true, and never empty when it is false.

A file that is not what was asked for:

{
  "verdict": {
    "matches_request": false,
    "mismatches": [
      { "field": "video.codec", "expected": "h264", "actual": "hevc" },
      { "field": "video.resolution", "expected": "1080p", "actual": "720p" }
    ]
  }
}

Mismatches

AttributeTypeDescription
fieldstringWhich property differs, from the fixed list below. Branch on this, not on the values beside it.
expectedstringWhat the request asked for, as a short value, e.g. h264, 1920, high, present. Where the request allows several answers, this is the alternatives joined with a vertical bar — see below.
actualstringWhat the produced file carries instead, in the same vocabulary as expected, e.g. hevc, 1280, main, absent.

field comes from a fixed vocabulary. These are the only values it takes:

ValueRaised when
containerThe delivery format differs from the requested type. An adaptive output accepts adaptive, hls and dash, since it delivers both manifests from shared segments. In practice this does not fire — see What container is, and is not.
video.presentThe file carries no video stream.
video.codecThe video codec differs. An ABR ladder matches any codec it declares.
video.profileThe request named a profile and the file carries a different one. Matched as a substring, since encoders name profiles more fully than requests do.
video.pix_fmtThe request pinned a bit depth — H.265 main10, or HDR kept or forced — and the file does not carry it.
video.resolutionThe request named a resolution tier and the file landed in a different one. Carries tier names, e.g. 1080p.
video.width, video.heightThe request named exact pixel dimensions and the file differs. Carries pixel counts.
video.frame_rateThe request named a frame rate and the file is more than 0.5 fps away from it.
video.color.transfer, video.color.primariesHDR was requested without tone mapping, and the file’s colour signalling does not carry it.
audio.presentdisable_audio was set and the file carries audio anyway.
duration_secondsThe file is shorter than the length agreed at probe time, by more than the tolerance below.

When several answers are acceptable

A multi-codec ABR ladder declares more than one codec, so more than one answer satisfies it. expected then carries the alternatives joined by a vertical bar, in the same short vocabulary as a single value:

{
  "field": "video.codec",
  "expected": "h264|hevc",
  "actual": "av1"
}

Split on the bar before comparing, or just render expected as-is.

What the verdict deliberately does not check

The verdict only asserts what your request actually pins. A property you left open cannot produce a mismatch, because telling you a correct file is broken is worse than saying nothing about it.

  • Audio codec, channels and sample rate. A request names none of them — an audio track config carries language, label, source track and a default flag. They are reported as facts, with nothing to compare them against.
  • Absent audio on an output that did not disable it. The source may legitimately have carried no sound.
  • Colour on an SDR request. The encoder carries the source’s signalling through, and the source is yours.
  • An output longer than promised. A clipped encode starts on a keyframe and can legally run a little long. Only a shortfall is a truncation.
  • Exact pixels when a tier was requested. Scaling preserves the source’s aspect ratio, so a 4:3 source at 1080p is legitimately 1440 pixels wide.

The duration tolerance

A shortfall is a mismatch when it exceeds the larger of one second and 2% of the promised length. The fixed second absorbs container rounding and the keyframe-aligned seek of a clipped encode; the fraction keeps the allowance proportionate on a long source.

The promise is the length agreed at probe time — your input’s duration, or the clip window for a clipped job. It is never the output’s own measured duration, which would compare the file with itself.


What a report never carries

No encoder command lines, no tool output, no stack traces, no storage paths, no raw probe dumps. Every value is a short identifier read off the media itself, and anything that does not look like one is dropped rather than passed through.

The rule is a character allow-list — lowercase letters, digits, and ., -, _, at most 32 characters — rather than a length cap. A length cap is not a guarantee: the file did not match is twenty-two characters and would sail through a generous one straight onto a customer-facing field. Excluding the space is what makes a sentence impossible.