# Which GraphQL API Does Bilistream Use for Twitch?

> Discover the Twitch GraphQL API bilistream uses. Learn how it checks for live streams using the StreamMetadata query at https://gql.twitch.tv/gql.

- Repository: [InitCool/bilistream](https://github.com/limitcool/bilistream)
- Tags: how-to-guide
- Published: 2026-03-06

---

**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`](https://github.com/limitcool/bilistream/blob/main/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`](https://github.com/limitcool/bilistream/blob/main/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`](https://github.com/limitcool/bilistream/blob/main/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:

```json
{
  "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`:

```rust
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:

```rust
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`:

```rust
#[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`](https://github.com/limitcool/bilistream/blob/main/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`](https://github.com/limitcool/bilistream/blob/main/src/plugins/twitch.rs) as part of the `Twitch` struct's live detection system.