tonhowtf/omniget · error

Access token not available for clip

Error message

Access token not available for clip: {}

What it means

Raised by fetch_access_token when the HTTP 200 GQL response does not contain [0]./data/clip/playbackAccessToken. The response either isn't the expected batched array, the persisted query was rejected, or the clip has no playback token (e.g. deleted or restricted).

Solutions

  1. Log the full JSON body to detect persisted-query 'PersistedQueryNotFound' errors
  2. Update TOKEN_HASH to the current hash
  3. Fall back to yt-dlp, which obtains playback URLs independently
  4. Retry once — token issuance can transiently fail

Example fix

// before
let token_obj = json.as_array().and_then(|arr| arr.first())
    .and_then(|r| r.pointer("/data/clip/playbackAccessToken"))
    .ok_or_else(|| anyhow!("Access token not available for clip: {}", slug))?;
// after
let err_msg = json.pointer("/0/errors/0/message").and_then(|v| v.as_str()).unwrap_or("missing playbackAccessToken");
let token_obj = json.as_array().and_then(|arr| arr.first())
    .and_then(|r| r.pointer("/data/clip/playbackAccessToken"))
    .ok_or_else(|| anyhow!("Access token unavailable for {}: {}", slug, err_msg))?;
Defensive patterns

Strategy: fallback

Validate before calling

// Detect persisted-query rejection in a 200 response body:
let pq_rejected = body.contains("PersistedQueryNotFound");

Type guard

fn has_playback_token(json: &serde_json::Value) -> bool {
    json.as_array().and_then(|a| a.first())
        .and_then(|r| r.pointer("/data/clip/playbackAccessToken"))
        .map(|t| t.get("value").and_then(|v| v.as_str()).is_some())
        .unwrap_or(false)
}

Try / catch

match downloader.get_media_info(url).await {
    Err(e) if e.to_string().contains("Access token not available") => fallback_ytdlp_info(url).await,
    other => other,
}

Prevention

When it happens

Trigger: GQL returns 200 with a persisted-query error envelope (not an array, or data.clip null), or the clip exists in metadata lookup but playback is denied so playbackAccessToken is absent/null.

Common situations: Stale TOKEN_HASH after a Twitch rotation producing a 200 error payload; deleted/restricted clip; schema change renaming playbackAccessToken.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12). Data as JSON: /api/errors/d0fc890cf55aeb6b. Report an issue: GitHub.

Appendix: source

Thrown at src-tauri/omniget-core/src/platforms/twitch.rs:232

            .header("client-id", CLIENT_ID)
            .json(&body)
            .send()
            .await?;

        if !response.status().is_success() {
            return Err(anyhow!(
                "Twitch GQL token retornou HTTP {}",
                response.status()
            ));
        }

        let json: serde_json::Value = response.json().await?;

        let token_obj = json
            .as_array()
            .and_then(|arr| arr.first())
            .and_then(|r| r.pointer("/data/clip/playbackAccessToken"))
            .ok_or_else(|| anyhow!("Access token not available for clip: {}", slug))?;

        let signature = token_obj
            .get("signature")
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow!("Token sem signature"))?
            .to_string();

        let value = token_obj
            .get("value")
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow!("Token sem value"))?
            .to_string();

        Ok(AccessToken { signature, value })
    }

    fn build_authenticated_url(source_url: &str, token: &AccessToken) -> String {
        format!(

View on GitHub (pinned to 8600b91f42)