Which GraphQL API Does Bilistream Use for Twitch?

Bilistream uses Twitch's public GraphQL endpoint https://gql.twitch.tv/gql with a persisted query named StreamMetadata to check if a channel is broadcasting.

The bilistream repository implements a live-status monitoring system that queries Twitch's internal GraphQL API to detect stream activity. Instead of using Twitch's documented REST API (Helix), the project leverages the same persisted queries that power Twitch's web interface, enabling lightweight status checks without OAuth tokens. This approach is implemented in the Twitch plugin located at src/plugins/twitch.rs.

Twitch GraphQL Endpoint Configuration

Bilistream communicates with Twitch's unofficial but publicly accessible GraphQL infrastructure using a specific set of headers and a pre-hashed query.

Endpoint URL and Authentication

The plugin sends POST requests to the following configuration:

  • Endpoint: https://gql.twitch.tv/gql
  • Client-ID header: kimne78kx3ncx6brgo4mv6wki5h1ko (Twitch's public web client identifier)
  • Content-Type: application/json

This Client-ID is the standard identifier used by Twitch's own web application, allowing anonymous access to basic stream metadata without requiring a personal access token.

Persisted Query Details

The implementation utilizes a persisted GraphQL query—a query that has been previously registered with Twitch's servers and is referenced by its SHA-256 hash rather than the full query string:

  • Operation name: StreamMetadata
  • SHA-256 hash: 1c719a40e481453e5c48d9bb585d971b8b372f8ebb105b17076722264dfa5b3e
  • Version: 1

Persisted queries reduce payload size and improve caching performance by sending only the hash instead of the full GraphQL syntax.

Implementation in src/plugins/twitch.rs

The core logic resides in the Twitch struct's get_status async method, which constructs the HTTP request and parses the response to determine live status.

The get_status Method

According to the source code in src/plugins/twitch.rs (lines 27-45), the method builds a JSON payload containing the operation name, variables, and persisted query hash. The method signature accepts a channel identifier and returns a boolean indicating live status by examining the GraphQL response structure.

The request payload structure follows this exact format:

{
  "operationName": "StreamMetadata",
  "variables": {
    "channelLogin": "<channel_name>"
  },
  "extensions": {
    "persistedQuery": {
      "version": 1,
      "sha256Hash": "1c719a40e481453e5c48d9bb585d971b8b372f8ebb105b17076722264dfa5b3e"
    }
  }
}

Request Payload Structure

The variables object accepts a single parameter channelLogin (the Twitch channel name in lowercase), while the extensions.persistedQuery object contains the version and SHA-256 hash required by Twitch's APQ (Automatic Persisted Queries) system.

Practical Code Examples

Building the GraphQL Request in Rust

The following Rust code demonstrates how bilistream constructs the request using serde_json and reqwest_middleware:

use serde_json::json;
use reqwest_middleware::ClientWithMiddleware;

// ClientWithMiddleware instance and channel name
let payload = json!({
    "operationName": "StreamMetadata",
    "variables": {
        "channelLogin": &room,
    },
    "extensions": {
        "persistedQuery": {
            "version": 1,
            "sha256Hash":
                "1c719a40e481453e5c48d9bb585d971b8b372f8ebb105b17076722264dfa5b3e"
        }
    }
});

let resp: serde_json::Value = client
    .post("https://gql.twitch.tv/gql")
    .header("Client-ID", "kimne78kx3ncx6brgo4mv6wki5h1ko")
    .json(&payload)
    .send()
    .await?
    .json()
    .await?;

Parsing the Live Status Response

After receiving the JSON response, bilistream checks the data.user.stream.type field to determine if the channel is broadcasting:

let is_live = resp["data"]["user"]["stream"]["type"] == "live";
println!("Channel {} is live: {}", room, is_live);

Minimal Standalone Example

For testing outside the bilistream context, this complete example requires tokio and reqwest-middleware:

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = reqwest_middleware::ClientBuilder::new(reqwest::Client::new())
        .build();

    let channel = "example_channel";
    let payload = json!({
        "operationName": "StreamMetadata",
        "variables": { "channelLogin": channel },
        "extensions": {
            "persistedQuery": {
                "version": 1,
                "sha256Hash":
                    "1c719a40e481453e5c48d9bb585d971b8b372f8ebb105b17076722264dfa5b3e"
            }
        }
    });

    let resp: serde_json::Value = client
        .post("https://gql.twitch.tv/gql")
        .header("Client-ID", "kimne78kx3ncx6brgo4mv6wki5h1ko")
        .json(&payload)
        .send()
        .await?
        .json()
        .await?;

    let is_live = resp["data"]["user"]["stream"]["type"] == "live";
    println!("Stream live status: {}", is_live);
    Ok(())
}

Response Handling and Live Detection

The GraphQL response returns a nested JSON structure where the stream object is null when offline. Bilistream specifically evaluates resp["data"]["user"]["stream"]["type"] against the string literal "live" to set the boolean live status. This check occurs within the get_status method implementation, allowing the plugin system to trigger recording or relay actions when the condition evaluates to true.

Summary

  • Bilistream queries https://gql.twitch.tv/gql using the public Client-ID kimne78kx3ncx6brgo4mv6wki5h1ko.
  • It uses the StreamMetadata persisted query with SHA-256 hash 1c719a40e481453e5c48d9bb585d971b8b372f8ebb105b17076722264dfa5b3e to minimize payload size.
  • The get_status method in src/plugins/twitch.rs constructs the request and parses data.user.stream.type to detect live broadcasts.
  • No OAuth tokens are required for this specific query, making it suitable for lightweight monitoring applications.

Frequently Asked Questions

What is the exact Twitch GraphQL endpoint URL used by bilistream?

Bilistream sends POST requests to https://gql.twitch.tv/gql, which is Twitch's public GraphQL gateway. This endpoint accepts persisted queries and standard GraphQL operations, though the project specifically uses the persisted query protocol for efficiency.

Why does bilistream use a persisted query instead of a full GraphQL query string?

Persisted queries reduce network overhead by transmitting only a SHA-256 hash rather than the complete query syntax. This approach also leverages Twitch's APQ (Automatic Persisted Queries) caching layer, resulting in faster response times and reduced bandwidth consumption compared to sending full query strings.

What Client-ID does bilistream use for Twitch API requests?

The implementation uses the Client-ID kimne78kx3ncx6brgo4mv6wki5h1ko, which is the public identifier used by Twitch's own web interface. This Client-ID allows unauthenticated access to basic stream metadata through the GraphQL endpoint without requiring an OAuth access token or application registration.

How does bilistream determine if a Twitch channel is currently live?

The get_status method examines the JSON path data.user.stream.type in the GraphQL response. When this field equals the string "live", the channel is broadcasting; if the field is null or any other value, the channel is considered offline. This logic is implemented in src/plugins/twitch.rs as part of the Twitch struct's live detection system.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →