Skip to main content

Troubleshooting: recording integrity, legal hold, and QC failures

The recording pipeline fails closed: every endpoint returns a specific error.code when something is wrong, and — like every Orbit error — the response still arrives in the standard envelope:
Read error.code first, then match it to a section below. For the happy-path calls this page troubleshoots, see Recording lifecycle operations and the Recordings API reference.

Symptom map

Codes that begin RECORDING_CONSENT_ are a different class — see Consent-gate errors are tenant controls.

RECORDING_INTEGRITY_NOT_SEALED (400)

You called GET /recordings/{id}/integrity/export-digest on a recording that has no integrity seal. The digest is built from the stored seal record, so an unsealed recording has nothing to sign and ship. Fix. Seal the recording first, then re-issue the GET:
If the seal call itself fails with RECORDING_INTEGRITY_MISSING_CONTENT_HASH, handle that first (next section), then come back here. Retry-safe: no — repeat the GET only after a successful seal. A GET that keeps returning 400 with a successful seal on record means you are requesting a different recording id than the one you sealed.

RECORDING_INTEGRITY_MISSING_CONTENT_HASH (400)

The seal attempts to anchor a content hash, and none exists yet. Two states produce this:
  • Sealing before finalize — the egress pipeline stamps the content hash when the recording finalizes; a seal that lands earlier finds no hash.
  • Sealing after a failed finalize — finalize did not complete, so no hash was ever stamped.
Fix.
  1. Wait for the recording to reach a finalized state, then repeat the seal with an empty body — the seal picks up the hash the pipeline stamped. Check the recording’s status before sealing rather than polling blindly.
  2. If you have the media bytes already (e.g. you downloaded the file out-of-band), compute the SHA-256 yourself and pass it explicitly:
content_sha256 is a 64-character hex string. If finalize itself keeps failing, seal with the verified hash and route the finalize failure to your ingest path — do not seal against bytes you have not validated. Retry-safe: yes, as a repeat POST with the same or corrected content_sha256.

RECORDING_SHARE_NOT_SHAREABLE (400)

You called POST /recordings/{id}/share on a recording that is not a completed video-room recording. Only completed video-room artefacts are shareable; an in-progress room’s partial recording or a recording that never completed cannot take a share link, by design. Fix. Read the recording before you share. Fetch the recording (or list the room’s recordings) and check its status; mint the share link only once the recording is complete:
For triage of a room whose recording never completed, work from Video room lifecycle instead — the recovery there is to run the room to completion, not to force the share. Retry-safe: retrying without a completed recording returns the same 400. Share lifetimes clamp between 5 minutes and 30 days (7-day default) — that is separate from this gate and applies only to a shareable recording.
A 500 on the legal-hold surface means the write or read itself threw after the request passed validation, role checks, and scope checks — a platform-side fault (a tenant-schema or database error), not a bad request. SET covers PUT /recordings/{id}/legal-hold (place or release); FETCH covers the GET that reads current hold state, including the batched conversation-level read. Fix.
  1. Retry the same call once — the common cause is a transient database fault, and a single re-issue usually lands the hold. The operation is per-recording idempotent, so a retry cannot double a hold.
  2. If the retry also returns 500, open a ticket and stop retrying. A legal hold is an evidence-preservation control: if acknowledging it is failing, the risk is that retention sweeps proceed against a recording you believe is held. Do not blind-rebuild the hold with repeated PUTs — capture the failing request and escalate.
  3. Include the recording id, the error.code, and the meta.request_id from the response in the ticket.
Retry-safe: one retry, then ticket. Never treat a 500 here as a cue to keep hammering the endpoint.

RECORDING_QC_FETCH_FAILED and RECORDING_QC_RUN_FAILED (500)

  • RECORDING_QC_FETCH_FAILEDGET /recordings/{id}/qc could not load the stored QC verdict (a database or tenant-schema fault). This is distinct from a pending verdict, which is a normal 200 with an empty report.
  • RECORDING_QC_RUN_FAILEDPOST /recordings/{id}/qc/run threw while the synchronous scorer ran or while its verdict was persisted. A stuck pending verdict plus a rerun that keeps returning 500 means the scorer or its write path is failing, not that the recording failed a check.
Fix.
  1. Retry GET /recordings/{id}/qc once; a read-only fault usually clears on a second attempt.
  2. Re-run QC once more — the rerun is synchronous and idempotent, so a passed recording returns the same verdict and emits no webhook.
  3. If the rerun keeps failing, escalate with the recording id and the meta.request_id — the scorer is inside the post-call pipeline, and a persistent 500 is a platform fault your tenant cannot repair.
A verdict that comes back failed is not an error — triage it with the recording.qc_failed webhook flow in Recording lifecycle operations.

RECORDING_QC_INVALID_ID (400)

The :id path parameter on a QC endpoint failed validation — an empty, oversized, or malformed recording id before any lookup ran. Fix. Validate the id you interpolate into the URL: pull it from a recording list or the webhook payload rather than a hand-built string. Rerun with the corrected id. This code fires on QC-specific id checks; other recording surfaces respond with their own ..._INVALID_ID or a generic validation code — match the endpoint, not just the string.
Codes in the RECORDING_CONSENT_* family are a different failure class from everything above. A seal or legal-hold 500 is a platform fault you escalate; a consent error is your own gate doing its job:
  • RECORDING_CONSENT_REQUIRED — the call could not start recording because a two-party or two-tier consent gate you configured had no acknowledgement on record. The gate stops the recording before any media is captured.
  • RECORDING_CONSENT_INVALID — a consent receipt failed validation when you submitted it.
  • RECORDING_CONSENT_ACKNOWLEDGEMENT_REQUIRED — an acknowledgement step your configuration requires was skipped.
These are tenant-owned controls: Orbit evaluates the gate you chose; which jurisdiction rule you apply, and how you capture consent, is your decision. Do not treat a consent 4xx as a platform-recovery problem — the fix is configuration and evidence capture, not retry or escalation. Go to Call Recording Consent for the consent models and acknowledgement flows, not to any section above.

How a hash chain proves provenance

Sealing anchors one recording’s content hash into a tenant-wide chain: each seal stores a prev_hash link to the seal before it, so an auditor who walks the chain confirms nothing was inserted, removed, or reordered. Verification recomputes the digest over the recorded content hash, the previous seal, and the seal metadata, and compares it to the stored current_hash — a mismatch names exactly which seal broke. The GET /recordings/{id}/integrity/export-digest HMAC-signed bundle is how you ship that evidence to an auditor alongside exported media; the full model (including where the export digest sits relative to the other export families) is in Recording lifecycle operations § Integrity seals and The export families model.

Error samples for tickets

Each sample below is a paste-ready response — include the whole block (plus the meta.request_id) when you open a ticket. Seal before finalize released the hash:
Export digest on an unsealed recording:
Share minted on an incomplete video recording:
Legal-hold write that threw:
QC verdict read that threw:
Pre-recording consent gate (tenant control, not a platform fault):

Retry safety summary


When to escalate

Escalate to support when the retry above does not clear a 500, or when any legal-hold write fails on a second attempt. Include:
  • The recording id (rec_…) and the error.code from the response.
  • The meta.request_id from the failing response.
  • For a share or seal rejection, the recording’s status at the time of the call.
Legal holds and consent gates are tenant-owned controls: Orbit stores the hold and evaluates the gates you configure, but preservation policy, consent capture, and jurisdiction choice stay with you. Nothing on this page is a claim of GDPR, CCPA, or other compliance.