tonhowtf/omniget · error · anyhow::Error

token_expired

token_expired

Error message

token_expired

What it means

request_tweet maps HTTP 403 (Forbidden) and 429 (Too Many Requests) from Twitter's GraphQL endpoint to the error code `token_expired`. The guest token is treated as stale/rejected whenever Twitter refuses the request with these statuses, signaling the caller to refresh the token and retry.

Solutions

  1. Call get_guest_token to obtain and cache a fresh guest token, then retry request_tweet
  2. Distinguish 429 from 403 and add exponential backoff with Retry-After for 429 instead of only refreshing the token
  3. Reduce request frequency / add throttling and caching of tweet lookups
  4. Consider authenticated (OAuth) access if guest-token requests keep getting 403

Example fix

// before
if status == reqwest::StatusCode::FORBIDDEN || status == reqwest::StatusCode::TOO_MANY_REQUESTS {
    return Err(anyhow!("token_expired"));
}
// after
match status {
    reqwest::StatusCode::TOO_MANY_REQUESTS => Err(anyhow!("rate_limited")),
    reqwest::StatusCode::FORBIDDEN => Err(anyhow!("token_expired")),
    _ => Ok(())
}
Defensive patterns

Strategy: retry

Try / catch

match request_tweet(id).await {
    Err(e) if e.to_string() == "token_expired" => {
        platform.refresh_guest_token().await?;
        request_tweet(id).await
    }
    other => other,
}

Prevention

When it happens

Trigger: GET to the TweetResultByRestId GraphQL endpoint returns 403 (guest token rejected/expired or auth challenge) or 429 (rate limited). Both are collapsed into the same `token_expired` error even though 429 is actually rate limiting.

Common situations: Guest token cached for too long and Twitter invalidated it; bursts of downloads hitting Twitter's per-token rate limit (429); Twitter tightening bot protection so 403 is returned even for fresh tokens.

Related errors


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

Appendix: source

Thrown at src-tauri/src/platforms/twitter/mod.rs:364

            .header("Accept-Language", "en")
            .header("Content-Type", "application/json")
            .header("Cookie", &cookie_val);
        if has_auth_token {
            request = request.header("x-twitter-auth-type", "OAuth2Session");
        }
        if let Some(ct0) = ct0 {
            request = request.header("x-csrf-token", ct0);
        }

        let response = request.send().await?;

        let status = response.status();
        tracing::debug!("[twitter] graphql tweet_id={} status={}", tweet_id, status);

        if status == reqwest::StatusCode::FORBIDDEN
            || status == reqwest::StatusCode::TOO_MANY_REQUESTS
        {
            return Err(anyhow!("token_expired"));
        }

        if status == reqwest::StatusCode::NOT_FOUND {
            return Err(anyhow!("Post not available"));
        }

        if !status.is_success() {
            return Err(anyhow!("Twitter API retornou HTTP {}", status));
        }

        response.json().await.map_err(Into::into)
    }

    fn calculate_syndication_token(id: &str) -> String {
        let num: f64 = id.parse().unwrap_or(0.0);
        let raw = (num / 1e15) * std::f64::consts::PI;
        let base36 = Self::f64_to_base36(raw);
        base36

View on GitHub (pinned to 8600b91f42)