Skip to content

API Reference

GraphQL API for stream management, key rotation, analytics, and all platform operations.

All API interactions are performed through a single GraphQL endpoint. This allows you to fetch exactly the data you need in a single request.

https://bridge.frameworks.network/graphql

Subscriptions use WebSocket:

wss://bridge.frameworks.network/graphql/ws

All operations go through this single endpoint. Queries read data, mutations change it.

Most requests need a token in the Authorization header:

Authorization: Bearer YOUR_JWT_OR_API_TOKEN

Get your API token from Developer → Credentials in the dashboard.

An API token carries the scopes you grant it when you create it, in resource:action form. A resource’s write scope includes its read scope: a token with streams:write can do everything streams:read allows. A dashboard session is not scoped. The scope a request needs follows from the fields it selects, not only from its root field:

FieldsScope
Stream reads: streamsConnection, stream, and a Stream reached through another object (for example clip.stream)streams:read
Push target reads: Stream.pushTargetsstreams:read
Stream changes: createStream, updateStream, deleteStream, push target changes, VOD uploadstreams:write
Stream keys: Stream.streamKey, streamKeysConnection, refreshStreamKey, createStreamKey, deleteStreamKeystreams:write
Live state and analytics: Stream.metrics, everything under analyticsanalytics:read

A stream key lets whoever holds it publish to the stream, so it is a write credential: an API token reads, creates, rotates, lists, or deactivates keys only with streams:write. With streams:read alone the rest of a stream resolves, streamKey is null, and the response carries a FORBIDDEN error at its path, for example ["stream", "streamKey"].

Stream.metrics resolves from the analytics pipeline, so selecting it inside a stream query or mutation needs analytics:read as well. Without it the rest of the stream resolves, metrics is null, and the response carries a FORBIDDEN error at ["stream", "metrics"] (see Partial results).

The SDKs’ stream operations (ListStreams, GetStream, CreateStream, UpdateStream, RefreshStreamKey) do not select metrics. Of them, only CreateStream and RefreshStreamKey select streamKey. GetStreamMetrics and ListStreamMetrics read live state and need analytics:read; GetStreamKey reads one stream’s key and needs streams:write.

The dashboard may authenticate via httpOnly cookies.
Public allowlist (no auth required): the queries serverInfo, resolveViewerEndpoint, resolveIngestEndpoint, networkStatus, and the orchestrator queries, plus the mutations bootstrapEdge and walletLogin, which each carry their own per-call credential in the input.

Subscriptions send the token in the connection_init payload of the WebSocket connection:

{ "type": "connection_init", "payload": { "Authorization": "Bearer YOUR_JWT_OR_API_TOKEN" } }

A connection without an Authorization value is anonymous and can only run public operations. A connection whose Authorization value does not authenticate (expired, revoked, or malformed) is closed with code 4403 before the server acknowledges it; reconnecting with the same token fails the same way, so get a new token first. This holds even when the browser also sends a valid session cookie: a present Authorization value is never replaced by another credential. When the gateway cannot check the token because its authentication service is unavailable, it closes with 1013 (try again later); reconnect with backoff. Over HTTP the same case answers 503.

The SDKs for TypeScript, Go, and Python handle both, and read the token again for every reconnect.

The dashboard has a built-in GraphQL explorer. Go to Developer → Playground to run queries against your account.

Or use any GraphQL client:

Here are the most frequent operations you’ll likely need when building your integration.

query {
streamsConnection(page: { first: 20 }) {
edges {
node {
id
name
playbackId
createdAt
}
}
}
}

createStream creates a push-input live stream for RTMP/E-RTMP, SRT, or WHIP ingest. Most mutations return a union result. Always check __typename.

mutation {
createStream(input: { name: "My New Stream" }) {
__typename
... on Stream {
id
name
streamKey
playbackId
}
... on ValidationError {
message
field
}
... on AuthError {
message
}
}
}

The returned streamKey is secret and is used only for ingest. The returned playbackId is the public viewer identifier used by /play URLs and the FrameWorks Player.

For pull-input streams, set ingestMode: PULL and provide pullSource.sourceUri in CreateStreamInput; see Pull-input Streams.

query {
stream(id: "stream-global-id") {
id
name
streamKey
playbackId
record
}
}

Live state comes from analytics, so an API token needs analytics:read for it (the SDKs’ GetStreamMetrics; ListStreamMetrics reads a page of streams):

query {
stream(id: "stream-global-id") {
id
metrics {
status
isLive
currentViewers
startedAt
updatedAt
}
}
}
mutation {
createStreamKey(streamId: "stream-global-id", input: { name: "OBS Key" }) {
__typename
... on StreamKey {
id
keyValue
keyName
createdAt
}
... on ValidationError {
message
field
}
... on AuthError {
message
}
}
}

Most analytics queries take an optional timeRange; it is required on usageAggregates and streamAnalyticsSummariesConnection (and on the federation event, federation summary, and orchestrator performance series queries). Data is returned in aggregated form to help you visualize viewer engagement and usage.

query {
analytics {
usage {
streaming {
streamAnalyticsDailyConnection(
streamId: "stream-global-id"
timeRange: { start: "2024-01-01T00:00:00Z", end: "2024-01-07T00:00:00Z" }
page: { first: 30 }
) {
edges {
node {
day
totalViews
uniqueViewers
egressBytes
}
}
}
}
}
}
}

Note: analytics streamId filters use the Stream global ID (Stream.id). Responses return Relay IDs; use Stream.streamId for the public UUID used outside GraphQL.

When building a custom player, use the resolveViewerEndpoint query. This ensures your viewers are routed to the geographically closest and healthiest edge node.

query {
resolveViewerEndpoint(contentId: "a1b2c3d4e5f6g7h8") {
primary {
nodeId
baseUrl
protocol
url
geoDistance
loadScore
outputs
}
fallbacks {
nodeId
baseUrl
protocol
url
}
metadata {
status
isLive
viewers
title
description
contentId
contentType
}
}
}

Returns a primary endpoint (optimal for the viewer’s location) plus fallbacks. Use the primary endpoint first, fall back to others if it fails.

contentId is a playbackId for live streams, VOD uploads, clips, and DVR recordings (not the raw hash).

If the gateway can’t determine a viewer IP for GeoIP routing, the query still resolves: node selection falls back to scoring without the geographic component.

A request can fail in three places, and each has its own shape.

Before GraphQL runs, the gateway answers with an HTTP status and a JSON body that has error, often message and code, and no errors array:

StatusMeaning
401The token is invalid, expired, or revoked, or a payment header arrived without a credential.
402No credential on a non-public operation (with its x402 challenge), or the tenant needs credit or a payment method.
429Rate limited. Retry-After gives the seconds to wait.
5xxThe gateway or a service behind it failed. Queries are safe to retry.

While GraphQL runs, errors come in the errors array with extensions.code:

{
"data": { "stream": null },
"errors": [
{
"message": "resource not found",
"path": ["stream"],
"extensions": {
"code": "NOT_FOUND"
}
}
]
}
extensions.codeMeaning
UNAUTHORIZEDMissing or invalid credentials.
FORBIDDENThe token lacks the permission the operation needs.
NOT_FOUNDThe resource does not exist or belongs to another tenant.
VALIDATION_ERRORAn argument is invalid.
CONFLICTThe resource already exists, or a concurrent change won.
FAILED_PRECONDITIONThe resource is not in a state that allows the operation.
RATE_LIMITEDA service-side limit was reached.
UNAVAILABLEA service behind the gateway is unavailable or timed out; retry.
INTERNAL_ERRORAn unexpected failure.
GRAPHQL_VALIDATION_FAILED, GRAPHQL_PARSE_FAILEDThe document does not match this server’s schema (HTTP 422).

Some errors carry a more specific code, for example BILLING_PROFILE_REQUIRED with the missing fields in extensions.required_fields. Errors without a code are resolver messages, such as a missing required input.

resolveViewerEndpoint reports the state of a live stream that cannot be played yet:

extensions.codeMeaning
STREAM_STARTINGThe publisher is connected but the stream is not playable yet. Resolve again after extensions.retry_after_ms; the player keeps doing this for about 30 seconds.
STREAM_OFFLINENo publisher is live. Wait for the stream to go live instead of retrying immediately.

The /play endpoint reports the same states as HTTP 503 with code STREAM_STARTING and a Retry-After header, and HTTP 404 with code STREAM_OFFLINE.

GraphQL resolves each field on its own. When a nullable field fails, the server sets it to null, adds an error whose path names it, and returns the rest of the data. A mutation that returns such a response has run:

{
"data": { "createStream": { "__typename": "Stream", "id": "…", "metrics": null } },
"errors": [
{
"message": "insufficient permissions: requires analytics:read scope",
"path": ["createStream", "metrics"],
"extensions": { "code": "FORBIDDEN" }
}
]
}

The SDKs return such a response as success when every error has a path below a root field that came back and none is UNAUTHORIZED, RATE_LIMITED, or a document error. They hand the errors to a partial-errors handler (see SDKs). An error without a path, or one whose root field is null, fails the call.

As a result value, most mutations return a union of the result and typed errors. Select __typename and each error member; a ValidationError names the field, a NotFoundError the resourceType and resourceId, and a RateLimitError the seconds in retryAfter:

mutation {
createStream(input: { name: "" }) {
__typename
... on Stream {
id
}
... on ValidationError {
message
field
}
... on AuthError {
message
}
}
}

The SDKs turn each of these into one typed error class, the same in every language.

The fastest way to build queries for your integration:

  1. API Explorer — Go to Developer → Playground in the dashboard. The built-in GraphQL explorer has full schema docs, autocompletion, and lets you run queries against your account. Copy working queries directly into your code.
  2. Ask Skipper — Tell Skipper what you want to do (e.g. “create a stream from Python” or “list my clips with curl”) and it will generate a working snippet in your language, using the live schema.

Both tools stay in sync with the actual schema, so the queries they produce always work.

The examples below show the general pattern for calling the API from common languages. For the exact query fields you need, use the API Explorer or ask Skipper.

curl:

Terminal window
curl -X POST https://bridge.frameworks.network/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{"query": "{ streamsConnection(page: { first: 5 }) { edges { node { id name playbackId } } } }"}'

JavaScript (using graphql-request):

import { GraphQLClient, gql } from "graphql-request";
const client = new GraphQLClient("https://bridge.frameworks.network/graphql", {
headers: { Authorization: "Bearer YOUR_TOKEN" },
});
const data = await client.request(gql`
query {
streamsConnection(page: { first: 5 }) {
edges {
node {
id
name
playbackId
}
}
}
}
`);

Python (using httpx or requests):

import httpx
resp = httpx.post(
"https://bridge.frameworks.network/graphql",
headers={"Authorization": "Bearer YOUR_TOKEN"},
json={"query": "{ streamsConnection(page: { first: 5 }) { edges { node { id name playbackId } } } }"},
)
print(resp.json()["data"])

Go:

body, _ := json.Marshal(map[string]string{
"query": `{ streamsConnection(page: { first: 5 }) { edges { node { id name playbackId } } } }`,
})
req, _ := http.NewRequest("POST", "https://bridge.frameworks.network/graphql", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer YOUR_TOKEN")
resp, _ := http.DefaultClient.Do(req)

For TypeScript, Go, and Python, the SDKs ship a generated typed operation for every public root field that is not deprecated and every argument-taking field below Query. For other languages, use any GraphQL client or generate typed bindings from the public schema.

Browse the generated GraphQL schema reference for public entry points, arguments, return types, descriptions, and deprecations. It has one page per domain (for example Streams, Media and storage, and Analytics) plus a Shared types page, and lists the SDK operation names for each root field. Operator-only and service-token-only entry points are excluded.

The live schema is also available via introspection:

{
__schema {
types {
name
description
}
}
}

Or use the dashboard’s API Explorer (Developer → Playground) which shows docs inline.