# Frequently asked questions Source: https://docs.streampixel.io/frequently-asked-questions Answers to the most common questions about Streampixel — platform, hardware, regions, and troubleshooting. ## Platform Streampixel is a cloud-based 3D application streaming platform that runs Unreal Engine applications remotely with minimal latency. Unlike traditional remote-desktop solutions, Streampixel is optimized for high-performance interactive 3D experiences. Any Unreal Engine application with the Pixel Streaming plugin enabled. If your app is built with Unreal Engine, it can run on Streampixel without modifications. Both **Pixel Streaming** and **Pixel Streaming 2** are supported (we recommend Pixel Streaming) — but enable only one of them; with both enabled, the stream won't run. All processing happens in the cloud — users only need a stable internet connection and a device with a modern web browser. Yes. Touch input is supported on mobile and tablet devices. Applications can be customized to include touch-based interactions. Yes — embed the stream with an [iframe](/resources/iframe-integration/overview) for the simplest integration, or use the [Web SDK](/resources/web-sdk/getting-started/overview) for full programmatic control. Apple's browser security restrictions don't allow web pages to enter true fullscreen on iPhone — the Fullscreen API isn't available in iOS Safari. This affects every pixel-streamed experience on the web (Epic Games acknowledges the same limitation for Pixel Streaming), and no project or Streampixel setting changes it. iPads, desktop, and Android browsers are unaffected. See the [workaround](/resources/concepts/troubleshooting#known-limitations). ## Hardware and infrastructure Streampixel runs on **RTX 6000 ADA GPUs** (the enterprise equivalent of the RTX 4090) with **48 GB VRAM**, paired with **24-core CPUs** and **64 GB RAM** — enough for smooth, responsive, ultra-high-quality streaming under demanding workloads. Servers are deployed in **India, Germany, and the USA**, providing low-latency access for users across these regions. ## Server regions When creating a project, you select a **server region**. Every user accessing the project connects to that region, ensuring consistent performance. No. Users connect to the region selected during project setup. This guarantees predictable latency, consistent performance, and simpler compliance. Not directly. Submit a **migration request** by creating a ticket from your dashboard and our team will move the project for you. Pick the region **closest to your primary users** to minimize latency and maximize streaming quality. No. Pricing is identical across all regions. ## Troubleshooting This is usually a network restriction — typically a firewall blocking the WebRTC connection. Try the application from a different device or switch to a cellular network. If it works there, adjust your firewall settings to allow WebRTC traffic. Streampixel applications normally start within **10 seconds**. If yours is significantly slower it may be crashing on launch. Contact support — we can read the server logs and help identify the issue. For blurry streams, connection issues, audio problems, and stuck builds, see [Troubleshooting](/resources/concepts/troubleshooting). For connection error codes, see [Disconnect codes](/resources/quick-start-guide/disconnect-codes). For Web SDK-specific code issues, see [SDK troubleshooting](/resources/web-sdk/guides/troubleshooting). # Streampixel Docs Source: https://docs.streampixel.io/index Stream Unreal Engine projects to any browser. No installs, no plugins, no headaches.
Documentation

Run your Unreal Engine application in the cloud.

Streampixel hosts your packaged Unreal app on GPU infrastructure and delivers it as an interactive real-time stream to anyone with a URL — no installs, no native binaries.

Start the walkthrough → 10-minute shortcut
## Where do you want to start? Sign up, prepare your Unreal build, upload, share, embed. Every setting and feature in the web app. Drop a stream into your site with one iframe and `postMessage`. Build a custom JS player with full event and UI control. Automate uploads, distribution, and webhooks for CI/CD. Send and receive data between your page and Unreal. Connect your repo — Streampixel compiles in the cloud. Files your app loads at runtime, without a rebuild. ## Build something specific One Unreal app, many viewers, shared room. Quest, Vision Pro, and any WebXR headset. Touch input, codec choice, battery-aware defaults. Ship a build on every commit from your own pipeline. Lock down access, embeds, and credentials in production. Hit your latency, FPS, and bitrate targets. ## Stuck? What every error code means and how to recover. The most common questions, answered. Every term used in the docs. Press ⌘K (or Ctrl+K) anywhere in the dashboard to open the command palette and jump to any project, page, or action. # Hide / unhide touch controls Source: https://docs.streampixel.io/resources/additional-unreal-engine-features/hide-unhide-touch-controls Detect when a viewer is on a touch device and toggle your in-game touch UI accordingly. Streampixel automatically detects whether the connected viewer is on a touch device (mobile or tablet) and forwards a JSON message to your Unreal Engine app. Listen for that message to show or hide your touch controls. ## How it works When a session starts, the frontend sends a message to your UE project on the `pixelStreamingResponse` channel: ```json theme={"dark"} { "type": "isTouchDevice", "value": true } ``` `value` is `true` for mobile / tablet browsers and `false` for desktops. Use it to toggle your in-game touch HUD. ## Unreal blueprint setup On your `PlayerController` (or wherever your touch HUD lives), add a `PixelStreamingInputComponent`. Bind to its `OnPixelStreamingInputEvent` delegate. The delegate hands you a `Descriptor` string. Use the **JsonUtilities** plugin or a parse node to extract `type` and `value`. If `type == "isTouchDevice"`, set a Boolean variable on your HUD (e.g. `bShowTouchUI = value`). Drive the widget's `Visibility` property from `bShowTouchUI`. Touch viewers see the HUD; desktop viewers don't. See [JSON message communication](/resources/json-message-communication/unreal-side-sending-and-receiving-json) for the full blueprint walkthrough on parsing JSON messages from the frontend. ## Sending the message manually You can also force the touch HUD on or off from your own frontend code, regardless of device detection: ```javascript theme={"dark"} import { StreamPixelApplication } from 'streampixelsdk'; const { appStream } = await StreamPixelApplication({ appId: 'YOUR_PROJECT_ID', AutoConnect: true, }); // Force-show the touch HUD appStream.stream.emitUIInteraction({ type: 'isTouchDevice', value: true, }); ``` ```javascript theme={"dark"} const iframe = document.querySelector('iframe'); iframe.contentWindow.postMessage({ command: 'sendUIInteraction', data: { type: 'isTouchDevice', value: true }, }, '*'); ``` When you override device detection manually, the platform's automatic detection is still sent first. Send your override after the stream connects (listen for the `videoInitialized` event in the SDK or `loadingComplete` in the iframe). ## Common pitfalls The detection message arrives shortly after the stream connects. Hide the HUD by default and show it only when the message arrives. iPad Safari reports as macOS by default unless "Request Mobile Website" is set. Detect using `'ontouchstart' in window` from the frontend and override the message manually. Make sure `touchInput: true` is set in your SDK config. See [input controls](/resources/web-sdk/features/input-controls). ## Next steps The full blueprint pattern for receiving JSON from the frontend. Optimize codecs, bitrate, and UI for mobile viewers. # Overview Source: https://docs.streampixel.io/resources/additional-unreal-engine-features/overview Optional Unreal Engine setup for richer Streampixel experiences. Each guide is self-contained — apply only what you need. | | | | - | - | | [Playing media files](/resources/additional-unreal-engine-features/playing-media-files) | Stream video files from inside your UE project to the browser. | | [Hide / unhide touch controls](/resources/additional-unreal-engine-features/hide-unhide-touch-controls) | Programmatically toggle on-screen touch controls for mobile viewers. | | [Virtual keyboard for text input](/resources/additional-unreal-engine-features/virtual-keyboard) | How editable text fields trigger the on-screen keyboard in the browser (UE 5.3+, automatic). | # Playing media files Source: https://docs.streampixel.io/resources/additional-unreal-engine-features/playing-media-files Stream video files from inside an Unreal Engine project to the browser using a File Media Source and Media Plate actor. Pixel Streaming captures whatever is rendered in your Unreal viewport — including video playback. The fastest way to play a video inside your UE project is the **Media Plate** actor introduced in Unreal Engine 5.1. ## Setup Copy your video file (`.mp4`, `.mov`, etc.) into your project at `Content/Movies/`. Unreal expects this folder. In the Content Browser, right-click → **Media** → **File Media Source**. Point it at the video in `Content/Movies/`. Drag the **File Media Source** asset into your level. Unreal automatically creates a **Media Plate** actor that handles texture, material, and audio for you. Move and scale the Media Plate where the video should appear in your scene — on a wall, a screen, a floating panel, etc. ## Controlling playback from blueprints The Media Plate exposes Play, Pause, Stop, Seek, and SetLooping nodes. Bind them to triggers, UI buttons, or events forwarded from your Pixel Streaming frontend. To start playback when a viewer clicks a UI button on your frontend, listen for the corresponding [JSON message](/resources/json-message-communication/unreal-side-sending-and-receiving-json) and call `Play` on the Media Plate. ## Audio Media Plate plays the embedded audio track automatically. Pixel Streaming captures the audio along with the video frames and streams both to the browser. Browser autoplay policies may mute audio until the user interacts with the page. If your stream needs audio from frame zero, prompt the viewer to click the page first, or unmute via [UI control](/resources/web-sdk/api-reference/uicontrol) after a user gesture. ## Performance notes * Decoding video on the server costs CPU. For 1080p+ at 60fps, expect a noticeable load on the worker. * Re-encode source videos to H.264 with reasonable bitrate (≤ 8 Mbps for 1080p) before importing. * For very large videos, consider streaming from a CDN URL using the **Stream Media Source** instead of `File Media Source`. ## Older Unreal versions (5.0 and earlier) Media Plate is 5.1+. On older versions, use the manual pipeline: Media Player asset → Media Texture → Material with the texture → apply to a static mesh. The platform handles streaming the same way; only the in-engine setup differs. ## Next steps Trigger video playback from your frontend. Pick the right codec and bitrate for video-heavy scenes. # Virtual keyboard for text input Source: https://docs.streampixel.io/resources/additional-unreal-engine-features/virtual-keyboard How editable text fields in Unreal trigger the on-screen keyboard in the browser, automatically from UE 5.3+. If your Unreal project uses **editable text boxes** (UMG `EditableText`, `EditableTextBox`, or any widget that accepts text input), Streampixel can capture the keystrokes from a viewer's browser and forward them to your application — including the on-screen keyboard on touch devices. ## How it works (UE 5.3+) From **Unreal Engine 5.3 onwards**, this is automatic — no Blueprint or C++ work needed. When the viewer clicks an editable text field in the streamed scene: 1. The browser detects that the field is focused. 2. A small popup with an input box appears over the stream. 3. The viewer types into that input box; on touch devices, the on-screen keyboard opens at this point. 4. Each keystroke is forwarded to Unreal as a normal text input event. The popup exists because the browser cannot send keystrokes "into" the streamed pixels directly — it needs a real DOM input element to attach the keyboard to. Streampixel handles that bridge automatically. Streampixel's input popup that appears when the viewer clicks an editable text field in the streamed Unreal application. ## Older Unreal versions On UE 5.2 and below, this auto-bridging isn't available. If you're targeting an older engine version, upgrade if you can — otherwise text input from touch devices won't work without custom JavaScript-side handling. ## Troubleshooting Confirm you're on UE 5.3 or later — the auto-bridge ships with the Pixel Streaming plugin from that version. Also confirm the field is a UMG editable text widget (not a custom HUD draw), since only widgets that report focus to the engine trigger the bridge. Browser autoplay/autofocus policies on iOS and some Android browsers require a direct user gesture. Make sure the click on the text field is the first interaction — if your UI auto-focuses on load, the keyboard often won't show until the viewer taps somewhere first. Check that **Keyboard** input is enabled in **Project Settings → Input** ([Input & UI controls](/resources/quick-start-guide/frontend-settings)). If keyboard input is toggled off at the project level, the bridge has nowhere to send keystrokes. ## Next Toggle keyboard, mouse, touch, and other input devices. Programmatically show or hide on-screen touch HUDs. # API authentication Source: https://docs.streampixel.io/resources/api-reference/api-authentication API keys, the x-api-key header, and the scopes that decide what a key may do. Every REST endpoint authenticates with an **API key** sent in the `x-api-key` header. The key identifies your account; the endpoint then applies the same ownership and team-role checks the dashboard does, so a key can never reach another account's projects. ```bash theme={"dark"} curl https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d/files \ -H "x-api-key: $STREAMPIXEL_API_KEY" ``` Two endpoints are the exception. [Upload File](/resources/api-reference/upload-file-api) and [Distribute File](/resources/api-reference/distribute-file-api) predate the header and **require** the key as `apiKey` in the JSON body, together with `userId`, exactly as their examples show; the header alone returns `400 Missing required fields`. Both are `POST` requests. A `GET` with a JSON body is rejected at the edge with an HTML `400 Bad Request` page before it reaches the API. **Never put the key in a URL.** Query-string keys (`?apikey=`) are rejected with `400 API_KEY_IN_QUERY`, because URLs end up in access logs, browser history and `Referer` headers. If a key has ever been sent in a URL, treat it as compromised and rotate it. ## Scopes A key is issued with one or more scopes. They are fixed when the key is created and cannot be widened afterwards; a key on a route outside its scope gets `403 API_KEY_SCOPE` naming the scope it lacks. | Scope | Allows | Endpoints | | - | - | - | | `upload` | Publishing and re-distributing builds. | Upload File, Distribute File | | `read` | Reading projects, build pipeline state, sessions, telemetry, logs and analytics. | Everything under *Projects*, *Builds* (GET) and *Sessions and analytics* | | `control` | Switching projects on and off, ending sessions, choosing the active build, changing settings. | Everything under *Project control* | Keys issued before scopes existed carry `upload` and `read`, exactly what they could do before. Nothing already deployed gains the ability to switch a project off. Give a CI pipeline `upload` and `read`. Give `control` only to the automation that needs it, on its own account or team member where possible, because a leaked control key can take a project offline until it is rotated. ## Generate an API key Go to [dashboard.streampixel.io](https://dashboard.streampixel.io) and sign in. Click your **account name** in the top-right corner. Under **Settings → API Keys**, tick the scopes the key should have. `Upload` and `Read` are on by default; tick `Control` only for automation that must switch projects or change settings. Click **Request API Key**. The key is emailed to you and shown once, with its scopes. Your API key is shown **only once**, and requesting a new key **invalidates the previous one** immediately. ### Issuing a key programmatically The dashboard is the usual way. The same endpoint is available to a dashboard login (never to a key, so a key cannot widen itself): ```http theme={"dark"} POST /api/v1/users/{userId}/api-keys Authorization: Bearer Content-Type: application/json { "scopes": ["upload", "read", "control"] } ``` ```json 200 OK theme={"dark"} { "message": "API Key has been generated and stored securely.", "apiKey": "…", "scopes": ["upload", "read", "control"] } ``` ## Team members A key inherits the role of the team member it was issued to. Owners, admins and editors can use every scope on the projects they can see. An uploader's key can upload, read, and, when issued with the `control` scope, set the live build; it cannot switch a project on or off, end sessions or change settings. A viewer's key can only read; a finance member's key cannot use the API. Removing a member from the team invalidates what their key can reach at once. ## Authentication errors | Status | `code` / message | Cause | | - | - | - | | `400` | `API_KEY_IN_QUERY` | The key was sent as a URL query parameter. Move it to the header and rotate the key. | | `401` | `Authentication required` | Missing or invalid `x-api-key` header. | | `401` | `Unauthorized: Invalid API Key` | Wrong or rotated key on Upload File / Distribute File. | | `403` | `API_KEY_SCOPE` | The key was issued without the scope this endpoint needs. The response names it in `requiredScope`. | | `403` | `Access denied` | The project belongs to another account, or your team role does not allow the action. | ```json 403 API_KEY_SCOPE theme={"dark"} { "code": "API_KEY_SCOPE", "message": "This API key was issued without the \"control\" scope. Issue a new key from the dashboard (or POST /users/{id}/api-keys with scopes) that includes it.", "requiredScope": "control", "scopes": ["upload", "read"] } ``` ## Security best practices * **Never** expose your API key in frontend code, public repositories or client-side bundles. * Store it in environment variables or a secrets manager. * Issue the narrowest scopes the integration needs. * Rotate the key periodically, and immediately on any suspected exposure. * The API only accepts HTTPS; there is no HTTP fallback. ## Next steps Locate your `userId` and `projectId` in the dashboard. Limits, status codes and the error format. # Audience Breakdown Source: https://docs.streampixel.io/resources/api-reference/audience-breakdown GET https://platform.streampixel.io/api/v1/analytics/projects/{projectId}/analytics/breakdown Sessions by country, city, browser, operating system or device. Return the project's sessions in a window grouped by one dimension, as counts and percentages. Scope: `read`. Unlike the other analytics endpoints, `dimension`, `startDate` and `endDate` are all required here; there is no default dimension. ## Path parameters The project. ## Query parameters `country`, `city`, `browser`, `os` or `device`. Omitting it returns `400`. With `dimension=city`: restrict to one country code, e.g. `DE`. ISO 8601 start of the window. For API-key callers the window between `startDate` and `endDate` is capped at 92 days; move the window to read a longer period. ISO 8601 end of the window. ## Headers Your Streampixel API key. ## Response The dimension used. Sessions in the window with a value for the dimension. `value`, `count`, `percentage`; for cities also `country` and `countryCode`. ```bash cURL theme={"dark"} curl "https://platform.streampixel.io/api/v1/analytics/projects/664f1a2b3c4d5e6f7a8b9c0d/analytics/breakdown?dimension=browser&startDate=2026-09-19T00:00:00Z&endDate=2026-09-26T00:00:00Z" \ -H "x-api-key: $STREAMPIXEL_API_KEY" ``` ```javascript Node.js theme={"dark"} const u = new URL('https://platform.streampixel.io/api/v1/analytics/projects/664f1a2b3c4d5e6f7a8b9c0d/analytics/breakdown'); u.search = new URLSearchParams({ dimension: 'browser', startDate: '2026-09-19T00:00:00Z', endDate: '2026-09-26T00:00:00Z' }); const { breakdown } = await (await fetch(u, { headers: { 'x-api-key': process.env.STREAMPIXEL_API_KEY } })).json(); ``` ```python Python theme={"dark"} import os, requests r = requests.get( "https://platform.streampixel.io/api/v1/analytics/projects/664f1a2b3c4d5e6f7a8b9c0d/analytics/breakdown", params={"dimension": "browser", "startDate": "2026-09-19T00:00:00Z", "endDate": "2026-09-26T00:00:00Z"}, headers={"x-api-key": os.environ["STREAMPIXEL_API_KEY"]}, ) print(r.json()["breakdown"]) ``` ```json 200 OK theme={"dark"} { "success": true, "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "dimension": "browser", "total": 143, "count": 4, "breakdown": [ { "value": "Chrome", "count": 98, "percentage": 68.5 }, { "value": "Safari", "count": 27, "percentage": 18.9 }, { "value": "Edge", "count": 12, "percentage": 8.4 }, { "value": "Firefox", "count": 6, "percentage": 4.2 } ] } ``` ## Errors | Status | Cause | | - | - | | `400` | `dimension` missing or unknown (`"Invalid dimension. Must be one of: country, city, browser, device, os"`), or `startDate` / `endDate` missing (`"startDate and endDate are required (ISO format)"`). | | `403` | The key lacks `read`, or the project belongs to another account. | | `404` | No project with that id. | | `400` | `startDate`/`endDate` not ISO 8601, `startDate` after `endDate`, or a window wider than 92 days. API-key callers only. | | `429` | More than 300 reads in a minute (API-key callers). `Retry-After` is set. | # Build Status Source: https://docs.streampixel.io/resources/api-reference/build-status GET https://platform.streampixel.io/api/v1/projects/{projectId}/files/{buildId}/status Where one build is in the regional pipeline: its queue position, whether a build machine is on it, and a verdict on whether the wait is genuine or the build is stuck. Answer the question "my build has been distributing for an hour, is that normal?". The endpoint reads the region's build queue and the build machines' heartbeats and returns one `state`, a plain-language `verdict` and an `action`. Scope: `read`. Other customers' builds share the regional queue; they are **counted** in `ahead` and `totalInQueue` but never identified. ## How a build moves ```text theme={"dark"} pending → Downloading Files → Extracting & Scanning → Saving to Repository → Ready (auto-release off) or Distribute → Distributing to Servers → Approved ↘ Download Failed / Reject ``` Each region has one build queue, processed in order by that region's build machine. A build waits its turn, is downloaded, scanned and saved, and once set active is copied to every streaming server in the region. `Approved` means it is on every server and serving sessions. ## States | `state` | Meaning | `likelyStuck` | | - | - | - | | `queued` | In the region's queue with builds ahead of it and a build machine online. A genuine wait. | `false` | | `processing` | A build machine has taken it, or it is first in line and the machine is busy. | `false` | | `ready_for_activation` | Processed; auto-release is off, so it waits for [Set Active Build](/resources/api-reference/set-active-build). | `false` | | `stalled` | First in line but not picked up, or the status says in-flight while the queue no longer holds it. | usually `true` | | `no_builder_online` | No build machine in the region has reported for over three minutes. Nothing moves until it is back. | `true` | | `live` | Approved and serving. | `false` | | `failed` | Download failed; see `build.note`. | `false` | | `rejected` | Rejected by the pipeline or an operator; see `build.note`. | `false` | ## Path parameters The project the build belongs to. The build id (`uploadId` from Upload File, `id` from [List Builds](/resources/api-reference/list-builds)). ## Headers Your Streampixel API key. ## Response One of the states above. `true` when the wait is not a normal queue wait and something should be done. One or two sentences explaining `state`, written for the project owner. What to do, or `null` when nothing is needed. Raw pipeline status of the build. The status as a stable word (see [List Builds](/resources/api-reference/list-builds)). What the current status means. ISO 8601 time the status last changed. Minutes since then. Whether this build is the one currently served. The build entry, as in [List Builds](/resources/api-reference/list-builds). Whether the build's entry is still in the region's queue. 1-based position; `1` is being processed or next. Entries ahead of it, any customer's. A build machine has taken this entry. When this entry was queued. When the oldest entry ahead was queued: how long the queue has been backed up. All entries in the region's queue. `Download File` or `Distribute File`. The region's build machines from their heartbeats: `known`, `total`, `online` (reported within three minutes), `busy`, `idle`, `lastReportAt`, `redisTrouble`. When `known` is `false` no build machine in the region reports a heartbeat and the verdict rests on the queue alone; `no_builder_online` is never returned in that case. When this answer was computed. ```bash cURL theme={"dark"} curl https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d/files/66a0b1c2d3e4f5a6b7c8d9e0/status \ -H "x-api-key: $STREAMPIXEL_API_KEY" ``` ```javascript Node.js theme={"dark"} // Poll until the build is live or something needs attention. const url = `https://platform.streampixel.io/api/v1/projects/${projectId}/files/${buildId}/status`; for (;;) { const s = await (await fetch(url, { headers: { 'x-api-key': process.env.STREAMPIXEL_API_KEY } })).json(); console.log(s.state, '-', s.verdict); if (s.state === 'live') break; if (s.likelyStuck || ['failed', 'rejected'].includes(s.state)) throw new Error(`${s.state}: ${s.action ?? s.build.note}`); await new Promise((r) => setTimeout(r, 30_000)); } ``` ```python Python theme={"dark"} import os, time, requests url = f"https://platform.streampixel.io/api/v1/projects/{project_id}/files/{build_id}/status" while True: s = requests.get(url, headers={"x-api-key": os.environ["STREAMPIXEL_API_KEY"]}).json() print(s["state"], "-", s["verdict"]) if s["state"] == "live": break if s["likelyStuck"] or s["state"] in ("failed", "rejected"): raise SystemExit(f'{s["state"]}: {s.get("action") or s["build"]["note"]}') time.sleep(30) ``` ```json queued theme={"dark"} { "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "region": "Europe", "autoRelease": true, "build": { "id": "66a0b1c2d3e4f5a6b7c8d9e0", "status": "Distributing to Servers", "phase": "distributing", "uploadedAt": "2026-09-26T09:12:04.000Z", "statusUpdatedAt": "2026-09-26T09:31:40.000Z", "note": null, "unrealVersion": "5.4", "psVersion": 2, "appPath": null, "lastRetryError": null, "lastRetryAt": null }, "isLive": false, "status": "Distributing to Servers", "phase": "distributing", "meaning": "Being copied to every streaming server in the region. Sessions can start once it is Approved.", "inStateSince": "2026-09-26T09:31:40.000Z", "minutesInState": 14, "state": "queued", "likelyStuck": false, "verdict": "Genuinely queued: 1 build ahead of it in this region, the oldest queued 22 min ago, and the build machine is busy on one of them. It is processed in order.", "action": "Wait; nothing to do. If the queue has not moved in an hour, contact support.", "queue": { "inQueue": true, "position": 2, "ahead": 1, "processingNow": false, "queuedAt": "2026-09-26T09:31:40.000Z", "oldestAheadQueuedAt": "2026-09-26T09:23:10.000Z", "totalInQueue": 2, "entryType": "Distribute File" }, "builders": { "total": 1, "online": 1, "busy": 1, "idle": 0, "lastReportAt": "2026-09-26T09:45:12.000Z", "redisTrouble": false }, "checkedAt": "2026-09-26T09:45:40.000Z" } ``` ```json stalled theme={"dark"} { "state": "stalled", "likelyStuck": true, "status": "Distributing to Servers", "minutesInState": 47, "verdict": "Status has said \"Distributing to Servers\" for 47 minutes and the build is not in the region's queue any more. The pipeline lost track of it (a build machine restart mid-build is the usual cause).", "action": "Retry the build from the dashboard (re-queues the download); if it stalls again, contact support with the build id.", "queue": { "inQueue": false, "position": null, "ahead": null, "processingNow": false, "queuedAt": null, "oldestAheadQueuedAt": null, "totalInQueue": 0, "entryType": null }, "builders": { "total": 1, "online": 1, "busy": 0, "idle": 1, "lastReportAt": "2026-09-26T10:18:02.000Z", "redisTrouble": false } } ``` ## Errors | Status | `code` | Cause | | - | - | - | | `403` | `API_KEY_SCOPE` | The key lacks the `read` scope. | | `404` | `NOT_FOUND` | No such project, or no such build on it. | | `429` | `RATE_LIMITED` | More than 300 reads in a minute (API-key callers). `Retry-After` is set. | Poll every 30 to 60 seconds. Distribution of a multi-gigabyte build to a region takes tens of minutes; the verdict tells you when polling is pointless. # Disable Project Source: https://docs.streampixel.io/resources/api-reference/disable-project POST https://platform.streampixel.io/api/v1/projects/{projectId}/disable Switch a project off and end every live and queued session. Switch a project off. Every live and queued session of the project is ended: viewers receive disconnect code `4000` (Project inactive), the applications are stopped, and new viewers are refused until the project is [enabled](/resources/api-reference/enable-project) again. The call waits until the end-session signal has been sent and reports how many sessions it reached. That is the difference from the dashboard's switch, which fires and forgets: a script can tell "disabled, 3 viewers disconnected" from "disabled, but the region did not answer". Scope: `control`. Team members need the Editor role or above. Limited to 30 control calls per minute per account. This is immediate and has no undo for the viewers it disconnects. To clear sessions without taking the project offline, use [End Sessions](/resources/api-reference/end-sessions). ## Path parameters The project to switch off. ## Headers An API key issued with the `control` scope. ## Response Always `false` after this call. `false` if the project was already off. Sessions are signalled either way. Live and queued sessions told to end. `null` when `pending` is `true`. `false` when the region's session store could not be read. The project is off, but sessions may still be running; retry [End Sessions](/resources/api-reference/end-sessions). `true` when disconnecting the sessions is taking longer than the request budget and is finishing in the background. The project is already off and `sessionsSignalled` is `null`; poll [Live Stats](/resources/api-reference/project-live-stats) to confirm viewers have cleared. Only appears on busy regions. Only with `regionAnswered: false`: why. ```bash cURL theme={"dark"} curl -X POST https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d/disable \ -H "x-api-key: $STREAMPIXEL_API_KEY" ``` ```javascript Node.js theme={"dark"} const res = await fetch( 'https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d/disable', { method: 'POST', headers: { 'x-api-key': process.env.STREAMPIXEL_API_KEY } } ); const r = await res.json(); if (!r.regionAnswered) console.warn('project is off but sessions may still be running:', r.error); ``` ```python Python theme={"dark"} import os, requests r = requests.post( "https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d/disable", headers={"x-api-key": os.environ["STREAMPIXEL_API_KEY"]}, ).json() print(f'off; {r["sessionsSignalled"]} session(s) ended') ``` ```json 200 OK theme={"dark"} { "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "name": "Showroom", "region": "Europe", "enabled": false, "changed": true, "sessionsSignalled": 3, "regionAnswered": true } ``` ## Errors | Status | `code` | Cause | | - | - | - | | `403` | `API_KEY_SCOPE` | The key was issued without `control`. | | `403` | — | The project belongs to another account, or the team role cannot edit projects. | | `404` | `NOT_FOUND` | No project with that id. | | `429` | `RATE_LIMITED` | More than 30 control calls in a minute. `Retry-After` is set. | # Disconnect Codes Source: https://docs.streampixel.io/resources/api-reference/disconnect-codes-api GET https://platform.streampixel.io/api/v1/analytics/projects/{projectId}/disconnect-codes Every session ending in a window, with the close code that explains it. Return the disconnect-code entries recorded for a project in a window: one per session ending, with the code, the session it belongs to and when. Group by code to see *why* sessions end; the meaning of each code is in [Disconnect codes](/resources/quick-start-guide/disconnect-codes). Scope: `read`. Default `limit` is 1000, newest first. ## Path parameters The project. ## Query parameters ISO 8601 start of the window. For API-key callers the window between `startDate` and `endDate` is capped at 92 days; move the window to read a longer period. ISO 8601 end of the window. Entries to return. Offset. ## Headers Your Streampixel API key. ## Response Entries in the window. Entries returned. The close code, e.g. `1000`, `1006`, `4004`, `4006`. The session. The streamer instance. ISO 8601. ```bash cURL theme={"dark"} curl "https://platform.streampixel.io/api/v1/analytics/projects/664f1a2b3c4d5e6f7a8b9c0d/disconnect-codes?startDate=2026-09-25T00:00:00Z&endDate=2026-09-26T00:00:00Z" \ -H "x-api-key: $STREAMPIXEL_API_KEY" ``` ```javascript Node.js theme={"dark"} const u = new URL('https://platform.streampixel.io/api/v1/analytics/projects/664f1a2b3c4d5e6f7a8b9c0d/disconnect-codes'); u.search = new URLSearchParams({ startDate: '2026-09-25T00:00:00Z', endDate: '2026-09-26T00:00:00Z' }); const { disconnectCodes } = await (await fetch(u, { headers: { 'x-api-key': process.env.STREAMPIXEL_API_KEY } })).json(); const histogram = {}; for (const e of disconnectCodes) histogram[e.disconnectCode] = (histogram[e.disconnectCode] ?? 0) + 1; console.log(histogram); // e.g. { 1000: 118, 1006: 12, 4004: 9, 4006: 2 } ``` ```python Python theme={"dark"} import os, collections, requests r = requests.get( "https://platform.streampixel.io/api/v1/analytics/projects/664f1a2b3c4d5e6f7a8b9c0d/disconnect-codes", params={"startDate": "2026-09-25T00:00:00Z", "endDate": "2026-09-26T00:00:00Z"}, headers={"x-api-key": os.environ["STREAMPIXEL_API_KEY"]}, ) print(collections.Counter(e["disconnectCode"] for e in r.json()["disconnectCodes"])) ``` ```json 200 OK theme={"dark"} { "success": true, "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "totalDisconnectEntries": 141, "count": 141, "limit": 1000, "skip": 0, "disconnectCodes": [ { "disconnectCode": 1000, "sessionId": "session_1790294409382_3035", "streamingId": "st_7f3a", "timestamp": "2026-09-25T14:31:40.000Z", "logType": "Webrtc" }, { "disconnectCode": 4006, "sessionId": "session_1790293988120_2210", "streamingId": "st_6c11", "timestamp": "2026-09-25T13:58:02.000Z", "logType": "Webrtc" } ] } ``` ## Grouping codes | Group | Codes | Meaning | | - | - | - | | Normal | `1000`, `1001` | The viewer left. | | Transient | `1005`, `1006` | Network or a suspended tab; the SDK reconnects. | | Your build | `4002`, `4003`, `4006` | Missing, would not start, or crashed. Read the [log](/resources/api-reference/download-log). | | Your settings | `4000`, `4004` | Project off, or the runtime limit was reached. | | Capacity | `4001` | No free streaming server. | | Platform | `4005`, `4007`, `4008`, `4009` | Setup or reconnection failed. Contact support if it repeats. | ## Errors | Status | Cause | | - | - | | `403` | The key lacks `read`, or the project belongs to another account. | | `404` | No project with that id. | | `400` | `startDate`/`endDate` not ISO 8601, `startDate` after `endDate`, or a window wider than 92 days. API-key callers only. | | `429` | More than 300 reads in a minute (API-key callers). `Retry-After` is set. | # Distribute File API Source: https://docs.streampixel.io/resources/api-reference/distribute-file-api POST https://platform.streampixel.io/api/v1/projects/distribute-file Push an uploaded build to Streampixel's streaming servers. Only needed when autoRelease is false or for re-distribution. Push an uploaded build to Streampixel's streaming servers manually. If you uploaded with `autoRelease: true` (the default), builds are distributed automatically after approval — you **don't need this endpoint**. Use it only when `autoRelease` is `false` or when you want to re-distribute a previous build (e.g., rolling back to an older version). Distribution is **asynchronous**. A `200` response means the process has been triggered, not completed. Use [webhooks](/resources/api-reference/webhooks) to track when distribution finishes. ## Prerequisites * The build has been uploaded via the [Upload File API](/resources/api-reference/upload-file-api). * The build has finished processing and has a valid URL. * Your project is enabled (not disabled by an admin). * You have a valid [API key](/resources/api-reference/api-authentication). ## Request body Your Streampixel API key. The ID of the account that owns the project. The project the build belongs to. The `uploadId` returned by the Upload File API. Must belong to the specified `projectId`. ## Response `true` when distribution was triggered. Echoes the `uploadId` you sent — the build that was distributed. The project's configured region the build was routed to — `US-East-1`, `Europe`, or the Asia-Pacific default. Human-readable status — `"File distribution triggered successfully"`. ```bash cURL theme={"dark"} curl -X POST https://platform.streampixel.io/api/v1/projects/distribute-file \ -H "Content-Type: application/json" \ -d '{ "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "uploadId": "665a2b3c4d5e6f7a8b9c0e1f", "apiKey": "your-api-key", "userId": "663d0a1b2c3e4f5a6b7c8d9e" }' ``` ```javascript JavaScript theme={"dark"} const response = await fetch( 'https://platform.streampixel.io/api/v1/projects/distribute-file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ projectId: '664f1a2b3c4d5e6f7a8b9c0d', uploadId: '665a2b3c4d5e6f7a8b9c0e1f', apiKey: 'your-api-key', userId: '663d0a1b2c3e4f5a6b7c8d9e', }), } ); const data = await response.json(); console.log(data); ``` ```python Python theme={"dark"} import requests response = requests.post( 'https://platform.streampixel.io/api/v1/projects/distribute-file', json={ 'projectId': '664f1a2b3c4d5e6f7a8b9c0d', 'uploadId': '665a2b3c4d5e6f7a8b9c0e1f', 'apiKey': 'your-api-key', 'userId': '663d0a1b2c3e4f5a6b7c8d9e', } ) print(response.json()) ``` ```json 200 OK theme={"dark"} { "success": true, "uploadId": "665a2b3c4d5e6f7a8b9c0e1f", "region": "US-East-1", "message": "File distribution triggered successfully" } ``` ```json 401 Unauthorized theme={"dark"} { "message": "Unauthorized: Invalid API Key" } ``` ```json 404 Not Found theme={"dark"} { "message": "File not found in project" } ``` ```json 429 Too Many Requests theme={"dark"} { "message": "Rate limit exceeded. Try again in 117 seconds." } ``` ## How distribution works Streampixel verifies your API key, project ownership, and that the file exists. The build is routed to your project's configured region. The distribution task is added to the processing queue. The build is pushed to servers and becomes available for streaming. ### Supported regions | Region | Description | | - | - | | `US-East-1` | United States (East) | | `Europe` | Europe | | Asia-Pacific | Asia Pacific (default for any other region value) | The response echoes the project's stored `region` value. Routing is by exact match: `US-East-1` and `Europe` go to their regional queues; every other value falls through to Asia-Pacific. ## Rate limiting This endpoint is rate-limited to **1 request per 2 minutes per user** (not per project). If you manage multiple projects, the limit applies across all of them. ## Automated pipeline with webhooks Combine webhooks and this endpoint for a controlled CI/CD flow: Submit the build via the [Upload File API](/resources/api-reference/upload-file-api) with `autoRelease: false`. Subscribe to [webhook events](/resources/api-reference/webhooks). On approval, run any internal checks (smoke tests, asset scans, etc.). Distribute the build by calling `POST /projects/distribute-file`. Listen for `build.distributing` to confirm the rollout started. ```javascript theme={"dark"} const express = require('express'); const app = express(); app.use(express.json()); const API_KEY = process.env.STREAMPIXEL_API_KEY; const USER_ID = process.env.STREAMPIXEL_USER_ID; app.post('/webhooks/streampixel', async (req, res) => { const { event, projectId, uploadId } = req.body; if (event === 'build.approved') { console.log(`Build ${uploadId} approved. Distributing...`); await fetch( 'https://platform.streampixel.io/api/v1/projects/distribute-file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ projectId, uploadId, apiKey: API_KEY, userId: USER_ID }), } ); } res.status(200).json({ received: true }); }); app.listen(3000); ``` ```python theme={"dark"} from flask import Flask, request, jsonify import requests import os app = Flask(__name__) API_KEY = os.environ['STREAMPIXEL_API_KEY'] USER_ID = os.environ['STREAMPIXEL_USER_ID'] @app.route('/webhooks/streampixel', methods=['POST']) def handle_webhook(): payload = request.get_json() if payload.get('event') == 'build.approved': print(f"Build {payload['uploadId']} approved. Distributing...") requests.post( 'https://platform.streampixel.io/api/v1/projects/distribute-file', json={ 'projectId': payload['projectId'], 'uploadId': payload['uploadId'], 'apiKey': API_KEY, 'userId': USER_ID, } ) return jsonify({"received": True}), 200 if __name__ == '__main__': app.run(port=3000) ``` ## Troubleshooting | Problem | Solution | | - | - | | `Unauthorized: Invalid API Key` | Regenerate your API key from the dashboard Settings page. | | `Access denied` | Verify you own the project and it isn't disabled. | | `File not found in project` | Double-check the `uploadId` — it must belong to the specified project. | | `File has no URL` | The upload is still processing. Wait for it to complete before distributing. | | `Rate limit exceeded` | Wait the indicated seconds before retrying (limit: 1 req / 2 min / user). | | Triggered but not live | Distribution is async. Use [webhooks](/resources/api-reference/webhooks) to track status. | ## Next steps Track distribution events in real time. The endpoint that produces the `uploadId` you pass here. # Download Log Source: https://docs.streampixel.io/resources/api-reference/download-log GET https://platform.streampixel.io/api/v1/analytics/log-files/{logFileId}/download Stream an Unreal Engine log file as plain text. Return the log's full text, decompressed, as a streamed `text/plain` response. There is no size cap: a session whose application logged the same error thousands of times a minute can produce a log of tens of megabytes, and those are exactly the ones you need. Read it as a stream. Scope: `read`. The sibling `GET /analytics/log-files/{logFileId}?projectId=…` returns the file's metadata plus a short-lived `downloadUrl` if you prefer to fetch from storage directly. ## Path parameters The `_id` from [Session Logs](/resources/api-reference/session-logs). ## Query parameters The project the log belongs to. ## Headers Your Streampixel API key. ## Response `200` with `Content-Type: text/plain` and the log lines, e.g. ```text theme={"dark"} [2026.09.25-14.02.20:113][ 0]LogInit: Engine Version: 5.4.4-35576357+++UE5+Release-5.4 [2026.09.25-14.02.21:002][ 0]LogD3D12RHI: Display: Adapter Name: NVIDIA RTX A5000 [2026.09.25-14.02.24:410][ 1]LogPixelStreaming2: Display: Connected to signalling server … ``` ```bash cURL theme={"dark"} curl "https://platform.streampixel.io/api/v1/analytics/log-files/66b1c2d3e4f5a6b7c8d9e0f1/download?projectId=664f1a2b3c4d5e6f7a8b9c0d" \ -H "x-api-key: $STREAMPIXEL_API_KEY" -o Showroom.log grep -n -E "Fatal error|=== Critical error|GPU Crash" Showroom.log | head ``` ```javascript Node.js theme={"dark"} import { createWriteStream } from 'node:fs'; import { Readable } from 'node:stream'; import { pipeline } from 'node:stream/promises'; const u = new URL('https://platform.streampixel.io/api/v1/analytics/log-files/66b1c2d3e4f5a6b7c8d9e0f1/download'); u.searchParams.set('projectId', '664f1a2b3c4d5e6f7a8b9c0d'); const res = await fetch(u, { headers: { 'x-api-key': process.env.STREAMPIXEL_API_KEY } }); await pipeline(Readable.fromWeb(res.body), createWriteStream('Showroom.log')); ``` ```python Python theme={"dark"} import os, re, requests with requests.get( "https://platform.streampixel.io/api/v1/analytics/log-files/66b1c2d3e4f5a6b7c8d9e0f1/download", params={"projectId": "664f1a2b3c4d5e6f7a8b9c0d"}, headers={"x-api-key": os.environ["STREAMPIXEL_API_KEY"]}, stream=True, ) as r: for line in r.iter_lines(decode_unicode=True): if re.search(r"Fatal error|=== Critical error|GPU Crash", line): print(line) ``` ## What to look for | Pattern | Meaning | | - | - | | `GPU Crash` | The graphics device was lost: driver or shader fault, or video memory exhausted. | | `Fatal error:`, `=== Critical error: ===`, `Unhandled Exception`, `EXCEPTION_ACCESS_VIOLATION` | The application crashed. The lines just before name the cause. | | `FPlatformMisc::RequestExit` with nothing above | A clean, requested exit: the platform stopped the app when the viewer left. Not a crash. | | No `Connected to signalling server` line | The Pixel Streaming plugin never connected; sessions end with code `4005`. | ## Errors | Status | Cause | | - | - | | `403` | `projectId` missing, or the log belongs to a different project or account. | | `404` | No log file with that id. | | `429` | More than 300 reads in a minute (API-key callers). `Retry-After` is set. | # Enable Project Source: https://docs.streampixel.io/resources/api-reference/enable-project POST https://platform.streampixel.io/api/v1/projects/{projectId}/enable Switch a project on so viewers are admitted. Switch a project on. Viewers are admitted from the moment the call returns. Idempotent: calling it on a project that is already on succeeds with `changed: false`. Scope: `control`. Team members need the Editor role or above. Limited to 30 control calls per minute per account. Enabling a project with no approved build does not fail, but viewers will be refused with disconnect code `4009` until a build is approved. The response carries a warning when that is the case. ## Path parameters The project to switch on. ## Headers An API key issued with the `control` scope. ## Response The project id. Project name. Project region. Always `true` after this call. `false` if the project was already on. Empty, or one line per condition that will stop viewers even though the project is on. ```bash cURL theme={"dark"} curl -X POST https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d/enable \ -H "x-api-key: $STREAMPIXEL_API_KEY" ``` ```javascript Node.js theme={"dark"} const res = await fetch( 'https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d/enable', { method: 'POST', headers: { 'x-api-key': process.env.STREAMPIXEL_API_KEY } } ); const { enabled, warnings } = await res.json(); ``` ```python Python theme={"dark"} import os, requests r = requests.post( "https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d/enable", headers={"x-api-key": os.environ["STREAMPIXEL_API_KEY"]}, ) print(r.json()) ``` ```json 200 OK theme={"dark"} { "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "name": "Showroom", "region": "Europe", "enabled": true, "changed": true, "warnings": [] } ``` ```json 200 OK (no approved build) theme={"dark"} { "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "name": "Showroom", "region": "Europe", "enabled": true, "changed": true, "warnings": [ "No approved build on this project: viewers will be refused with code 4009 until one is set active and approved." ] } ``` ## Errors | Status | `code` | Cause | | - | - | - | | `403` | `API_KEY_SCOPE` | The key was issued without `control`. | | `403` | — | The project belongs to another account, or the team role cannot edit projects. | | `404` | `NOT_FOUND` | No project with that id. | | `429` | `RATE_LIMITED` | More than 30 control calls in a minute. `Retry-After` is set. | ## Next steps Switch it off and end live sessions. Choose which build viewers get. # End Sessions Source: https://docs.streampixel.io/resources/api-reference/end-sessions POST https://platform.streampixel.io/api/v1/projects/{projectId}/sessions/end End every live and queued session of a project while keeping it on. End every live and queued session of a project without switching it off. Viewers receive disconnect code `4000` and the player shows *This stream has been turned off by the project owner*, the same message as [Disable Project](/resources/api-reference/disable-project); new viewers can join straight away. Use it to clear stuck viewers, or right before switching the active build so everyone lands on the new one. Scope: `control`. Team members need the Editor role or above. Limited to 30 control calls per minute per account. ## Path parameters The project whose sessions to end. ## Headers An API key issued with the `control` scope. ## Response The project's on/off state, unchanged by this call. Live and queued sessions told to end. `0` when there were none, `null` when `pending` is `true`. `false` when the region's session store could not be read; nothing was ended. Retry. `true` when the disconnect is taking longer than the request budget and is finishing in the background; `sessionsSignalled` is then `null`. Poll [Live Stats](/resources/api-reference/project-live-stats) to confirm. Only appears on busy regions. ```bash cURL theme={"dark"} curl -X POST https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d/sessions/end \ -H "x-api-key: $STREAMPIXEL_API_KEY" ``` ```javascript Node.js theme={"dark"} const res = await fetch( 'https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d/sessions/end', { method: 'POST', headers: { 'x-api-key': process.env.STREAMPIXEL_API_KEY } } ); console.log(await res.json()); ``` ```python Python theme={"dark"} import os, requests r = requests.post( "https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d/sessions/end", headers={"x-api-key": os.environ["STREAMPIXEL_API_KEY"]}, ) print(r.json()) ``` ```json 200 OK theme={"dark"} { "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "name": "Showroom", "region": "Europe", "enabled": true, "sessionsSignalled": 2, "regionAnswered": true } ``` ## Errors | Status | `code` | Cause | | - | - | - | | `403` | `API_KEY_SCOPE` | The key was issued without `control`. | | `403` | — | The project belongs to another account, or the team role cannot edit projects. | | `404` | `NOT_FOUND` | No project with that id. | | `429` | `RATE_LIMITED` | More than 30 control calls in a minute. `Retry-After` is set. | # Finding your user and project IDs Source: https://docs.streampixel.io/resources/api-reference/finding-your-user-and-project-ids Locate the userId and projectId values you'll pass on every API request. Every API request requires your `userId` and `projectId`. Here's where to find them. ## User ID The `userId` uniquely identifies your Streampixel account. It never changes — save it once and reuse across every API call. Sign in at [dashboard.streampixel.io](https://dashboard.streampixel.io). Click your **account name** in the top-right corner. Click **Copy User ID**. The value is now on your clipboard. ## Project ID Each project has its own `projectId`. You'll need it for project-scoped calls like uploading a build or distributing a file. From the dashboard, navigate to the project's detail page. Click the **Project ID** button. You can also read it from the URL: ```text theme={"dark"} https://dashboard.streampixel.io/projectdetail/67944b8a3f323be4b89a2272 └─────── projectId ───────┘ ``` Mismatched IDs cause `401 Invalid User` or `404 Project not found`. Double-check that the `userId` matches the account that owns `projectId`. ## Using both in a request Both IDs go in the JSON request body alongside your API key: ```json theme={"dark"} { "apiKey": "your-api-key", "userId": "663d0a1b2c3e4f5a6b7c8d9e", "projectId": "67944b8a3f323be4b89a2272", "fileUrl": "https://storage.example.com/build.zip" } ``` ## Next steps Generate the API key that pairs with these IDs. Use these IDs in your first upload request. # Get Project Source: https://docs.streampixel.io/resources/api-reference/get-project GET https://platform.streampixel.io/api/v1/projects/{projectId} One project's settings, builds and state. Return one project in full: streaming settings, session rules, access mode, allowed domains, webhook configuration and the list of uploaded builds. Use [List Builds](/resources/api-reference/list-builds) when you only need the builds, and [Update Project Settings](/resources/api-reference/update-project-settings) to change what you read here. Scope: `read`. This endpoint predates the current API and still requires your own `userId` as a query parameter. Any other value is refused with `403`. ## Path parameters The project to fetch. ## Query parameters Your account's user id. See [Finding your IDs](/resources/api-reference/finding-your-user-and-project-ids). ## Headers Your Streampixel API key. ## Response The project document. The fields below are the ones integrations rely on; others may appear. Payment identifiers and internal infrastructure fields (`subId`, `subscriptionStatus`, `isFreeTrial`, `cdn`, `reservedIp`, and similar) are omitted from the response for API-key and owner callers. Project id. Project name. `US-East-1`, `Europe`, `Asia Pacific` or `Australia`. `true` when the project is on and admitting viewers. Concurrent viewers the project may run. Path of the build currently served on the streaming servers; equals the live build's `appPath` in [List Builds](/resources/api-reference/list-builds). Render resolution. Video codecs. kbps. Maximum session length in minutes. Inactivity timeout in seconds. `public`, `password`, `sso` or `programmatic`. Domains allowed to embed the player. Empty means any. Whether processed builds are distributed automatically. Webhook configuration. Uploaded builds: `_id`, `status`, `date`, `url`, `appPath`, `unrealVersion`, `psVersion`, `objection`, `statusUpdatedAt`. ISO 8601. ```bash cURL theme={"dark"} curl "https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d?userId=663d0a1b2c3e4f5a6b7c8d9e" \ -H "x-api-key: $STREAMPIXEL_API_KEY" ``` ```javascript Node.js theme={"dark"} const url = new URL('https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d'); url.searchParams.set('userId', process.env.STREAMPIXEL_USER_ID); const project = await (await fetch(url, { headers: { 'x-api-key': process.env.STREAMPIXEL_API_KEY } })).json(); console.log(project.name, project.status ? 'on' : 'off', project.region); ``` ```python Python theme={"dark"} import os, requests r = requests.get( "https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d", params={"userId": os.environ["STREAMPIXEL_USER_ID"]}, headers={"x-api-key": os.environ["STREAMPIXEL_API_KEY"]}, ) project = r.json() ``` ```json 200 OK (abridged) theme={"dark"} { "_id": "664f1a2b3c4d5e6f7a8b9c0d", "name": "Showroom", "region": "Europe", "status": true, "allowedUsers": 4, "appName": "C:\\ServerPixelStream\\665a2b3c4d5e6f7a8b9c0e1f\\Windows\\Showroom\\Binaries\\Win64\\Showroom.exe", "resX": 1920, "resY": 1080, "primaryCodec": "H264", "fallbaCodec": "VP8", "minBitrate": 2000, "startBitrate": 6000, "maxBitrate": 12000, "maxRunTime": 30, "afktimeout": 120, "accessMode": "public", "validPathUrl": ["app.example.com"], "autoRelease": true, "webhookUrl": "https://ci.example.com/hooks/streampixel", "webhookEvents": ["build.approved", "build.rejected"], "files": [ { "_id": "665a2b3c4d5e6f7a8b9c0e1f", "status": "Approved", "date": "2026-09-20T15:02:11.000Z", "appPath": "665a2b3c4d5e6f7a8b9c0e1f", "unrealVersion": "5.4", "psVersion": 2 } ], "createdAt": "2026-05-02T10:14:52.000Z" } ``` ## Errors | Status | Cause | | - | - | | `400` | `userId` missing. | | `403` | `userId` is not the key's account, or the project belongs to another account. | | `404` | No project with that id. | | `429` | More than 300 reads in a minute (API-key callers). `Retry-After` is set. | # List Builds Source: https://docs.streampixel.io/resources/api-reference/list-builds GET https://platform.streampixel.io/api/v1/projects/{projectId}/files Every build uploaded to a project with its pipeline phase, and for builds still in flight, their place in the region's queue. Return every build on a project, newest first, with where each one is in the pipeline. Builds that are still being processed also carry their queue position and a verdict, so a CI job can poll this one endpoint after [Upload File](/resources/api-reference/upload-file-api) instead of guessing. Scope: `read`. A build is called a *file* in the API because that is what the pipeline receives: a `.zip`. `uploadId` from Upload File, `fileId` here and `buildId` on the control endpoints are the same value. ## Prerequisites | Requirement | Where to get it | | - | - | | Project ID | [Finding your IDs](/resources/api-reference/finding-your-user-and-project-ids) or [List Projects](/resources/api-reference/list-projects) | | API key with `read` | [API authentication](/resources/api-reference/api-authentication) | ## Path parameters The project whose builds to list. Must belong to the key's account. ## Headers Your Streampixel API key. ## Response The project id. The project's region: `US-East-1`, `Europe`, `Asia Pacific` or `Australia`. Builds are distributed to this region's streaming servers. When `false`, processed builds stop at `Ready` until you set one active. The build currently served to viewers, or `null` if none is approved. Number of builds on the project. One entry per build, newest first. The build id. Use it for [Build Status](/resources/api-reference/build-status) and [Set Active Build](/resources/api-reference/set-active-build). Raw pipeline status: `pending`, `Downloading Files`, `Extracting & Scanning`, `Saving to Repository`, `Ready`, `Distribute`, `Distributing to Servers`, `Approved`, `Download Failed`, `Reject`. The status as a stable word: `waiting`, `downloading`, `scanning`, `saving`, `ready`, `queued`, `distributing`, `live`, `failed`, `rejected`. ISO 8601 upload time. ISO 8601 time the status last changed. `null` on builds uploaded before this field existed. The pipeline's or an operator's reason when a build failed or was rejected. Engine version detected in the build. Pixel Streaming plugin generation detected: `1` or `2`. Present only while the build is in flight: `inQueue`, `position`, `ahead` (builds ahead of it in the region), `processingNow`. Present only while in flight. The verdict from [Build Status](/resources/api-reference/build-status): `queued`, `processing`, `stalled`, `no_builder_online`, `ready_for_activation`. Present only while in flight. One sentence explaining `state`. Present only while in flight. `true` when the wait is not a normal queue wait. Present when any build is in flight: `region`, `totalInQueue` (all entries in the region's queue, including other customers', counted only), and `builders` (`known`, `total`, `online`, `busy`, `idle`, `lastReportAt`; `known: false` means no heartbeat data for the region). ```bash cURL theme={"dark"} curl https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d/files \ -H "x-api-key: $STREAMPIXEL_API_KEY" ``` ```javascript Node.js theme={"dark"} const res = await fetch( 'https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d/files', { headers: { 'x-api-key': process.env.STREAMPIXEL_API_KEY } } ); const { files, liveBuildId } = await res.json(); const inFlight = files.filter((f) => f.state); ``` ```python Python theme={"dark"} import os, requests r = requests.get( "https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d/files", headers={"x-api-key": os.environ["STREAMPIXEL_API_KEY"]}, ) data = r.json() in_flight = [f for f in data["files"] if "state" in f] ``` ```json 200 OK theme={"dark"} { "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "region": "Europe", "autoRelease": true, "liveBuildId": "665a2b3c4d5e6f7a8b9c0e1f", "total": 2, "files": [ { "id": "66a0b1c2d3e4f5a6b7c8d9e0", "status": "Distributing to Servers", "phase": "distributing", "uploadedAt": "2026-09-26T09:12:04.000Z", "statusUpdatedAt": "2026-09-26T09:31:40.000Z", "note": null, "unrealVersion": "5.4", "psVersion": 2, "appPath": null, "lastRetryError": null, "lastRetryAt": null, "queue": { "inQueue": true, "position": 2, "ahead": 1, "processingNow": false }, "state": "queued", "verdict": "Genuinely queued: 1 build ahead of it in this region, the oldest queued 14 min ago, and the build machine is busy on one of them. It is processed in order.", "likelyStuck": false }, { "id": "665a2b3c4d5e6f7a8b9c0e1f", "status": "Approved", "phase": "live", "uploadedAt": "2026-09-20T15:02:11.000Z", "statusUpdatedAt": "2026-09-20T15:41:03.000Z", "note": null, "unrealVersion": "5.4", "psVersion": 2, "appPath": "C:\\ServerPixelStream\\665a2b3c4d5e6f7a8b9c0e1f\\Windows\\Showroom\\Binaries\\Win64\\Showroom.exe", "lastRetryError": null, "lastRetryAt": null } ], "queue": { "region": "Europe", "totalInQueue": 2, "builders": { "total": 1, "online": 1, "busy": 1, "idle": 0, "lastReportAt": "2026-09-26T09:45:12.000Z", "redisTrouble": false } } } ``` ## Errors | Status | `code` | Cause | | - | - | - | | `403` | `API_KEY_SCOPE` | The key lacks the `read` scope. | | `403` | — | The project belongs to another account. | | `404` | `NOT_FOUND` | No project with that id. | | `429` | `RATE_LIMITED` | More than 300 reads in a minute (API-key callers). `Retry-After` is set. | ## Next steps One build's queue position and a verdict on whether the wait is genuine. Make a processed build the one viewers get. # List Projects Source: https://docs.streampixel.io/resources/api-reference/list-projects GET https://platform.streampixel.io/api/v1/projects/projects-status Retrieve every project owned by your account, authenticated with your API key in the x-api-key header. Return a list of all projects owned by the authenticated account, with the basic status of each. Use this to enumerate your projects from a server-side script, CI/CD pipeline, or monitoring job before fanning out to per-project endpoints. ## Typical workflow Send your API key in the **`x-api-key` request header**. The account is identified by the key — no IDs needed. Call `GET /projects/projects-status` to retrieve every project you own. Take each project's `_id` and pass it anywhere a `projectId` is required — for example, [Project User Stats](/resources/api-reference/project-user-stats). ## Prerequisites | Requirement | Where to get it | | - | - | | API Key | [API authentication](/resources/api-reference/api-authentication) | **Do not put the API key in the URL.** Query-string keys (`?apikey=`) are rejected with `400 API_KEY_IN_QUERY` — URLs are recorded in access logs, browser history, and `Referer` headers. A key that has ever been sent in a URL should be rotated. Send it in the `x-api-key` header instead. ## Headers Your Streampixel API key. Identifies your account — the project list is scoped to the key's owner. ## Query parameters Optional, for backward compatibility. If provided, it must be the ID of the authenticated account (the key's owner) — any other value returns `403 Access denied`. Omit it in new integrations. ## Response The number of projects returned. Array of project objects. Each item contains: Unique project identifier. Use this anywhere a `projectId` is required. Display name of the project. Whether the project is currently active. The project's current subscription status. ```bash cURL theme={"dark"} curl https://platform.streampixel.io/api/v1/projects/projects-status \ -H "x-api-key: [YOUR_API_KEY]" ``` ```javascript Node.js theme={"dark"} const axios = require('axios'); const { data } = await axios.get( 'https://platform.streampixel.io/api/v1/projects/projects-status', { headers: { 'x-api-key': '[YOUR_API_KEY]' } } ); console.log(`${data.totalProjects} projects:`, data.projects); ``` ```python Python theme={"dark"} import requests response = requests.get( "https://platform.streampixel.io/api/v1/projects/projects-status", headers={"x-api-key": "[YOUR_API_KEY]"}, ) print("Projects:", response.json()["projects"]) ``` ```go Go theme={"dark"} package main import ( "fmt" "io" "net/http" ) func main() { req, _ := http.NewRequest("GET", "https://platform.streampixel.io/api/v1/projects/projects-status", nil) req.Header.Set("x-api-key", "[YOUR_API_KEY]") resp, err := http.DefaultClient.Do(req) if err != nil { fmt.Println("Request failed:", err) return } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println("Response:", string(body)) } ``` ```java Java theme={"dark"} import okhttp3.*; public class ListProjects { public static void main(String[] args) throws Exception { OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url("https://platform.streampixel.io/api/v1/projects/projects-status") .header("x-api-key", "[YOUR_API_KEY]") .get() .build(); Response response = client.newCall(request).execute(); System.out.println(response.body().string()); } } ``` ```json 200 OK theme={"dark"} { "totalProjects": 2, "projects": [ { "_id": "664f1a2b3c4d5e6f7a8b9c0d", "name": "Showroom Demo", "status": true, "subscriptionStatus": "active" }, { "_id": "664f1a2b3c4d5e6f7a8b9c0e", "name": "Configurator EU", "status": false, "subscriptionStatus": "active" } ] } ``` ```json 400 Bad Request theme={"dark"} { "code": "API_KEY_IN_QUERY", "message": "API keys are no longer accepted in the query string..." } ``` ```json 401 Unauthorized theme={"dark"} { "message": "Authentication required" } ``` ```json 403 Forbidden theme={"dark"} { "message": "Access denied" } ``` ```json 500 Internal Server Error theme={"dark"} { "message": "Internal server error" } ``` ## Error reference | Status | Message | Cause | | - | - | - | | `400` | `API_KEY_IN_QUERY` | The key was sent as a query parameter. Move it to the `x-api-key` header and rotate the key. | | `401` | `Authentication required` | The `x-api-key` header is missing, or the key is invalid/rotated. | | `403` | `Access denied` | A `userId` query parameter was supplied that doesn't match the key's account. | | `500` | `Internal server error` | Unexpected failure. Retry; if it persists, contact support. | Cache the results for a few minutes if you call this repeatedly. Project membership rarely changes minute-to-minute, and avoiding redundant calls keeps you well under the 300-reads-per-minute limit. ## Next Get live and queued user counts for a project. Submit builds to a project via the API. ## Errors | Status | `code` | Cause | | - | - | - | | `429` | `RATE_LIMITED` | More than 300 reads in a minute (API-key callers). `Retry-After` is set. | # List Sessions Source: https://docs.streampixel.io/resources/api-reference/list-sessions GET https://platform.streampixel.io/api/v1/analytics/projects/{projectId}/sessions Sessions of a project in a time window, with status, duration, disconnect code and crash flag. Return the project's sessions in a window, newest first, with everything needed to spot trouble without opening each one: status, region, start and end, duration, the disconnect code it ended with, and whether a crash was detected in its Unreal log. The viewer's browser, OS, device and location are in [Session Telemetry](/resources/api-reference/session-telemetry). Scope: `read`. Default window: the last 7 days. Page size is capped at 100; the `summary` covers the whole window regardless of paging. ## Path parameters The project. ## Query parameters ISO 8601 start of the window. Default: 7 days ago. For API-key callers the window between `startDate` and `endDate` is capped at 92 days; move the window to read a longer period. ISO 8601 end of the window. Default: now. `all`, `live`, `terminated` or `queued`. Page size, at most 100. Offset for paging. `startTime`, `endTime` or `durationMinutes`. `asc` or `desc`. ## Headers Your Streampixel API key. ## Response Session id, e.g. `session_1790294409382_3035`. Use it with [Session Detail](/resources/api-reference/session-detail), [Session Telemetry](/resources/api-reference/session-telemetry) and [Session Logs](/resources/api-reference/session-logs). The streamer instance id. Last recorded status: `Enqueued` (admitted, waiting for a slot), `Queued` (waiting for a free streaming server), `Starting` (application launching), `Started` (streaming), `Terminating`, `Terminated`. `true` when the record never received a final status and is reported as ended at the runtime cap. Its duration is an upper bound. Region the session ran in. ISO 8601. ISO 8601. `null` while the session is still open (queued, starting or streaming). Whole minutes between start and end; for an open session, minutes elapsed so far. The close code the session ended with. See [Disconnect codes](/resources/quick-start-guide/disconnect-codes). `GPU`, `LowLevelFatal` or `General` when the Unreal log matched a crash pattern. `General` is usually a clean engine exit, not a crash. `total`, `skip`, `limit`, `hasMore`. For the whole window: `totalSessions`, `liveSessions`, `terminatedSessions`, `sessionsWithIssues`, `sessionsWithCrashes`. ```bash cURL theme={"dark"} curl "https://platform.streampixel.io/api/v1/analytics/projects/664f1a2b3c4d5e6f7a8b9c0d/sessions?startDate=2026-09-25T00:00:00Z&endDate=2026-09-26T00:00:00Z&limit=100" \ -H "x-api-key: $STREAMPIXEL_API_KEY" ``` ```javascript Node.js theme={"dark"} // Walk every page of yesterday's sessions. const base = 'https://platform.streampixel.io/api/v1/analytics/projects/664f1a2b3c4d5e6f7a8b9c0d/sessions'; const all = []; for (let skip = 0; ; skip += 100) { const u = new URL(base); u.search = new URLSearchParams({ startDate: '2026-09-25T00:00:00Z', endDate: '2026-09-26T00:00:00Z', limit: '100', skip: String(skip) }); const { data } = await (await fetch(u, { headers: { 'x-api-key': process.env.STREAMPIXEL_API_KEY } })).json(); all.push(...data.sessions); if (!data.pagination.hasMore) break; } const failed = all.filter((s) => s.crashType && s.crashType !== 'General' || [4002, 4003, 4005, 4006].includes(s.disconnectCode)); ``` ```python Python theme={"dark"} import os, requests r = requests.get( "https://platform.streampixel.io/api/v1/analytics/projects/664f1a2b3c4d5e6f7a8b9c0d/sessions", params={"startDate": "2026-09-25T00:00:00Z", "endDate": "2026-09-26T00:00:00Z", "limit": 100}, headers={"x-api-key": os.environ["STREAMPIXEL_API_KEY"]}, ) data = r.json()["data"] print(data["summary"]) ``` ```json 200 OK theme={"dark"} { "success": true, "data": { "sessions": [ { "sessionId": "session_1790294409382_3035", "streamingId": "st_7f3a", "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "status": "Terminated", "statusInferred": false, "region": "Europe", "startTime": "2026-09-25T14:02:11.000Z", "endTime": "2026-09-25T14:31:40.000Z", "durationMinutes": 29.5, "disconnectCode": 1000, "crashType": null } ], "pagination": { "total": 143, "skip": 0, "limit": 100, "hasMore": true }, "summary": { "totalSessions": 143, "liveSessions": 2, "terminatedSessions": 141, "sessionsWithIssues": 6, "sessionsWithCrashes": 1 } } } ``` ## Errors | Status | Cause | | - | - | | `403` | The key lacks `read`, or the project belongs to another account. | | `404` | No project with that id. | | `400` | `startDate`/`endDate` not ISO 8601, `startDate` after `endDate`, or a window wider than 92 days. API-key callers only. | | `429` | More than 300 reads in a minute (API-key callers). `Retry-After` is set. | # AI assistants (MCP) Source: https://docs.streampixel.io/resources/api-reference/mcp-assistants Connect Claude, ChatGPT, Cursor or Codex to your Streampixel account and ask it about your projects, sessions and builds in plain language. Streampixel hosts a [Model Context Protocol](https://modelcontextprotocol.io) server. Connect an AI assistant to it and you can ask things like *"why did session X fail"*, *"how did the showroom do yesterday"* or *"is my build stuck"*, and get an answer built from the same data the REST API exposes: sessions, viewer telemetry, Unreal logs, build pipeline state, plan details. ``` https://mcp.streampixel.io/mcp ``` The assistant signs in with **your Streampixel login**, not an API key. It can only see and do what you can in the dashboard, and it never sees other accounts. ## Connect **Web or desktop:** Settings → Connectors → Add custom connector → paste the URL above. Sign in when prompted. **Claude Code:** ```bash theme={"dark"} claude mcp add --transport http streampixel https://mcp.streampixel.io/mcp ``` then run `/mcp` and sign in. Settings → Connectors (Developer mode) → Create → MCP server URL above, authentication **OAuth**. Sign in when prompted. Add to `~/.cursor/mcp.json`: ```json theme={"dark"} { "mcpServers": { "streampixel": { "url": "https://mcp.streampixel.io/mcp" } } } ``` The sign-in prompt appears on first use. ```bash theme={"dark"} codex mcp add streampixel --url https://mcp.streampixel.io/mcp ``` Sign in to the [dashboard](https://dashboard.streampixel.io) at least once before connecting, so your account exists. ## What it can do | Ask about | Tools the assistant uses | | - | - | | Your account and plan | `whoami`, `billing_summary`, `usage_report`, `account_overview` | | Projects and builds | `list_projects`, `get_project`, `get_live_status`, `get_build_status` | | Sessions | `list_sessions`, `find_problem_sessions`, `diagnose_session`, `get_session_timeline`, `get_session_metrics` | | Unreal logs | `list_session_logs`, `get_unreal_log_summary`, `search_unreal_log`, `get_unreal_log_excerpt` | | Reports | `project_health_report`, `disconnect_code_breakdown`, `queue_report`, `audience_breakdown` | | Reference | `explain_disconnect_code`, `explain_platform_concepts` | | Changes | `set_project_enabled`, `end_sessions`, `set_active_build`, `update_project_settings` | `diagnose_session` is the one to know: give it a session id and it reconstructs what happened from the platform's records, the viewer's telemetry, the disconnect code and the Unreal log, then says what went wrong, whose side it is on and what to do. ## Changes need your confirmation The last row are the only tools that change anything, and they do exactly what the [project control endpoints](/resources/api-reference/enable-project) do. They are marked as writes, so assistants that ask before acting will ask, and the server itself instructs the assistant to confirm with you first. Turning a project off ends its live sessions immediately. ## Example questions * "Give me yesterday's health report for Showroom and diagnose the worst three sessions." * "Session session\_1790294409382\_3035 failed for a customer. What happened and what do I tell them?" * "My build has been distributing for an hour. Is it stuck?" * "How many viewers queued this week and how long did they wait?" * "Which browsers do my viewers use?" * "When is my next billing date?" ## Limits * Windows are capped at 31 days per question. * Each account has a budget of tool calls per minute and per day, and of log scans per hour, so a runaway conversation cannot overload the analytics store. The assistant is told when to wait. * Invoices and payment history are not available through the assistant; they are in the dashboard's Billing page. # Overview Source: https://docs.streampixel.io/resources/api-reference/overview Automate builds, control projects, and read sessions and analytics through the REST API. The REST API gives scripts, CI pipelines and internal tools the same control over your projects that the dashboard has: publish builds and watch them move through the pipeline, switch projects on and off, change streaming settings, and read sessions, viewer telemetry, Unreal logs and analytics. ## Base URL ``` https://platform.streampixel.io/api/v1 ``` All endpoints are HTTPS only and speak JSON. Timestamps are ISO 8601 in UTC. Ids are 24-character hexadecimal strings, except session ids, which look like `session_1790294409382_3035`. ## Authentication in one line Send your API key in the `x-api-key` header on every request. Keys are issued per account with **scopes** that say what they may do: `upload`, `read`, `control`. See [API authentication](/resources/api-reference/api-authentication). ```bash theme={"dark"} curl https://platform.streampixel.io/api/v1/projects/projects-status \ -H "x-api-key: $STREAMPIXEL_API_KEY" ``` ## Endpoints ### Projects | Method | Endpoint | Scope | What it does | | - | - | - | - | | `GET` | [/projects/projects-status](/resources/api-reference/list-projects) | `read` | Every project on the account with its on/off state. | | `GET` | [/projects/\{projectId}](/resources/api-reference/get-project) | `read` | One project's settings and builds. | | `GET` | [/projects/\{projectId}/live-stats](/resources/api-reference/project-live-stats) | `read` | Viewers live and queued right now. | | `GET` | [/projects/\{projectId}/user-stats](/resources/api-reference/project-user-stats) | `read` | Same counts, legacy path. | ### Builds | Method | Endpoint | Scope | What it does | | - | - | - | - | | `POST` | [/projects/upload-file](/resources/api-reference/upload-file-api) | `upload` | Publish a build from a URL. | | `POST` | [/projects/distribute-file](/resources/api-reference/distribute-file-api) | `upload` | Re-distribute an uploaded build. | | `GET` | [/projects/\{projectId}/files](/resources/api-reference/list-builds) | `read` | Every build with its pipeline phase and queue position. | | `GET` | [/projects/\{projectId}/files/\{buildId}/status](/resources/api-reference/build-status) | `read` | Is this build genuinely queued, processing, or stuck. | ### Project control | Method | Endpoint | Scope | What it does | | - | - | - | - | | `POST` | [/projects/\{projectId}/enable](/resources/api-reference/enable-project) | `control` | Switch the project on. | | `POST` | [/projects/\{projectId}/disable](/resources/api-reference/disable-project) | `control` | Switch it off and end live sessions. | | `POST` | [/projects/\{projectId}/sessions/end](/resources/api-reference/end-sessions) | `control` | End live sessions, keep the project on. | | `POST` | [/projects/\{projectId}/active-build](/resources/api-reference/set-active-build) | `control` | Choose the build viewers get. | | `PATCH` | [/projects/\{projectId}/settings](/resources/api-reference/update-project-settings) | `control` | Change streaming and player settings. | ### Sessions and analytics | Method | Endpoint | Scope | What it does | | - | - | - | - | | `GET` | [/analytics/projects/\{projectId}/sessions](/resources/api-reference/list-sessions) | `read` | Sessions in a window with duration, code, crash flag, client. | | `GET` | [/analytics/sessions/\{sessionId}](/resources/api-reference/session-detail) | `read` | One session's status timeline. | | `GET` | [/analytics/frontend-streaming-logs](/resources/api-reference/session-telemetry) | `read` | The viewer's WebRTC events and stream statistics. | | `GET` | [/analytics/log-files/session/\{sessionId}](/resources/api-reference/session-logs) | `read` | Unreal log files of a session. | | `GET` | [/analytics/log-files/\{logFileId}/download](/resources/api-reference/download-log) | `read` | The log, streamed as text. | | `GET` | [/analytics/projects/\{projectId}/disconnect-codes](/resources/api-reference/disconnect-codes-api) | `read` | Why sessions ended. | | `GET` | [/analytics/projects/\{projectId}/analytics/queue](/resources/api-reference/queue-analytics) | `read` | Queue waits. | | `GET` | [/analytics/projects/\{projectId}/analytics/breakdown](/resources/api-reference/audience-breakdown) | `read` | Audience by country, browser, OS, device. | ### Webhooks and assistants | | | | - | - | | [Webhooks](/resources/api-reference/webhooks) | HTTP notifications for build pipeline events. | | [Webhook events](/resources/api-reference/webhook-events) | Event names and payloads. | | [AI assistants (MCP)](/resources/api-reference/mcp-assistants) | Connect Claude, ChatGPT, Cursor or Codex to your account. | ## Typical workflows [Upload File](/resources/api-reference/upload-file-api) with `autoRelease: true`; keep the `uploadId`. Poll [Build Status](/resources/api-reference/build-status) every 30 seconds until `state` is `live`, or listen for `build.approved` on your [webhook](/resources/api-reference/webhooks). The verdict tells you if a wait is genuine or the build is stuck. Optionally [End Sessions](/resources/api-reference/end-sessions) so everyone reconnects onto the new build. [Enable Project](/resources/api-reference/enable-project) from a scheduler at opening time. Poll [Live Stats](/resources/api-reference/project-live-stats) for a status page; alert when `queueUser` stays above zero. [Disable Project](/resources/api-reference/disable-project) at closing time. Live sessions end with code `4000`. [List Sessions](/resources/api-reference/list-sessions) for the time the viewer reported, filter by `disconnectCode` or `crashType`. [Session Detail](/resources/api-reference/session-detail): queue wait, start-up, which server, when it ended. [Session Telemetry](/resources/api-reference/session-telemetry): did frames decode, what codec, what latency. [Download Log](/resources/api-reference/download-log) and search for the fatal line. ## Versioning and compatibility * Everything lives under `/api/v1`. Fields are added, never removed or renamed, within v1; treat unknown fields as ignorable. * The legacy prefix `/pixelStripeApi` still routes to the same handlers but answers with a `Deprecation: true` header. Use `/api/v1` in new integrations. * The `code` field on error responses is stable and safe to branch on; `message` is for humans and may change. ## OpenAPI A machine-readable description of every endpoint on this page is in the docs repository as [`api-reference-openapi.json`](https://github.com/infinity-void-metaverse/streampixel-documentation/blob/main/api-reference-openapi.json). Import it into Postman, Insomnia or an SDK generator. # Project Live Stats Source: https://docs.streampixel.io/resources/api-reference/project-live-stats GET https://platform.streampixel.io/api/v1/projects/{projectId}/live-stats Viewers streaming and viewers queued right now. Real-time counts of viewers in a live session and viewers waiting for a free streaming server. Same numbers as [Project User Stats](/resources/api-reference/project-user-stats), on the REST path; both are read from the region's live session store and are cheap to poll every few seconds. Scope: `read`. ## Path parameters The project. ## Headers Your Streampixel API key. ## Response Viewers in an active session. Viewers waiting for a streaming server. ```bash cURL theme={"dark"} curl https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d/live-stats \ -H "x-api-key: $STREAMPIXEL_API_KEY" ``` ```javascript Node.js theme={"dark"} const { liveUser, queueUser } = await (await fetch( 'https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d/live-stats', { headers: { 'x-api-key': process.env.STREAMPIXEL_API_KEY } } )).json(); ``` ```python Python theme={"dark"} import os, requests r = requests.get( "https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d/live-stats", headers={"x-api-key": os.environ["STREAMPIXEL_API_KEY"]}, ) print(r.json()) ``` ```json 200 OK theme={"dark"} { "liveUser": 3, "queueUser": 1 } ``` A non-zero `queueUser` for more than a minute means demand exceeds the project's concurrent-user allowance. [Queue Analytics](/resources/api-reference/queue-analytics) shows how often that happens and how long viewers wait. ## Errors | Status | `code` | Cause | | - | - | - | | `403` | — | The project belongs to another account. | | `429` | `RATE_LIMITED` | More than 300 reads in a minute (API-key callers). `Retry-After` is set. | # Project User Stats Source: https://docs.streampixel.io/resources/api-reference/project-user-stats GET https://platform.streampixel.io/api/v1/projects/{projectId}/user-stats Get a live snapshot of how many users are actively streaming and how many are waiting in queue for a project. Return real-time counts of **live users** (active streaming sessions) and **queued users** (waiting for a worker) for a single project. Use this to power live status indicators, capacity dashboards, and autoscaling triggers without pulling the full session list. Live and queue state is read from Redis, so this endpoint is fast enough to poll on a short interval. A few seconds between polls is plenty — the counts update in near real time. The legacy path `GET /projects/userStats/{projectId}` still works and returns the same response, but is deprecated — use `GET /projects/{projectId}/user-stats` in new integrations. ## Typical workflow Send your API key in the **`x-api-key` request header**. The project must belong to the key's account. Call `GET /projects/{projectId}/user-stats` for the project you want to inspect. Use `liveUser` and `queueUser` in your dashboard, or compare against thresholds to trigger alerts. ## Prerequisites | Requirement | Where to get it | | - | - | | Project ID | [Finding your IDs](/resources/api-reference/finding-your-user-and-project-ids) or [List Projects](/resources/api-reference/list-projects) | | API Key | [API authentication](/resources/api-reference/api-authentication) | **Do not put the API key in the URL.** Query-string keys (`?apikey=`) are rejected with `400 API_KEY_IN_QUERY`. Send the key in the `x-api-key` header. ## Path parameters The ID of the project to fetch live and queue user counts for. Must belong to the authenticated account. ## Headers Your Streampixel API key. ## Query parameters Optional, for backward compatibility. If provided, it must be the ID of the authenticated account — any other value returns `403 Access denied`. Omit it in new integrations. ## Response Number of users currently in an active streaming session for this project. Sessions in `Terminated` or `Terminating` state are excluded. Number of users waiting in the project's queue for a worker to become available. ```bash cURL theme={"dark"} curl https://platform.streampixel.io/api/v1/projects/[PROJECT_ID]/user-stats \ -H "x-api-key: [YOUR_API_KEY]" ``` ```javascript Node.js theme={"dark"} const axios = require('axios'); const { data } = await axios.get( `https://platform.streampixel.io/api/v1/projects/${projectId}/user-stats`, { headers: { 'x-api-key': '[YOUR_API_KEY]' } } ); console.log(`Live: ${data.liveUser}, Queue: ${data.queueUser}`); ``` ```python Python theme={"dark"} import requests response = requests.get( f"https://platform.streampixel.io/api/v1/projects/{project_id}/user-stats", headers={"x-api-key": "[YOUR_API_KEY]"}, ) print(response.json()) ``` ```go Go theme={"dark"} package main import ( "fmt" "io" "net/http" ) func main() { url := "https://platform.streampixel.io/api/v1/projects/[PROJECT_ID]/user-stats" req, _ := http.NewRequest("GET", url, nil) req.Header.Set("x-api-key", "[YOUR_API_KEY]") resp, err := http.DefaultClient.Do(req) if err != nil { fmt.Println("Request failed:", err) return } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println("Response:", string(body)) } ``` ```java Java theme={"dark"} import okhttp3.*; public class ProjectUserStats { public static void main(String[] args) throws Exception { OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url("https://platform.streampixel.io/api/v1/projects/[PROJECT_ID]/user-stats") .header("x-api-key", "[YOUR_API_KEY]") .get() .build(); Response response = client.newCall(request).execute(); System.out.println(response.body().string()); } } ``` ```json 200 OK theme={"dark"} { "liveUser": 7, "queueUser": 2 } ``` ```json 400 Bad Request theme={"dark"} { "message": "projectId is required" } ``` ```json 401 Unauthorized theme={"dark"} { "message": "Authentication required" } ``` ```json 403 Forbidden theme={"dark"} { "message": "Access denied" } ``` ```json 404 Not Found theme={"dark"} { "message": "Project not found" } ``` ```json 500 Internal Server Error theme={"dark"} { "message": "Internal server error" } ``` ## Error reference | Status | Message | Cause | | - | - | - | | `400` | `API_KEY_IN_QUERY` | The key was sent as a query parameter. Move it to the `x-api-key` header and rotate the key. | | `401` | `Authentication required` | The `x-api-key` header is missing, or the key is invalid/rotated. | | `403` | `Access denied` | The project isn't owned by the key's account (or a mismatched `userId` was supplied). | | `404` | `Project not found` | No project with the supplied `projectId`. | | `500` | `Internal server error` | Unexpected failure. Retry; if it persists, contact support. | ## Polling pattern For a live dashboard, poll on a short interval and stop the timer when the page is hidden. This keeps the UI responsive without hammering the API. ```javascript Node.js / Browser theme={"dark"} async function fetchStats(projectId, apiKey) { const res = await fetch( `https://platform.streampixel.io/api/v1/projects/${projectId}/user-stats`, { headers: { 'x-api-key': apiKey } } ); if (!res.ok) throw new Error(`stats failed: ${res.status}`); return res.json(); } let timer = setInterval(async () => { try { const { liveUser, queueUser } = await fetchStats('[PROJECT_ID]', '[YOUR_API_KEY]'); render({ liveUser, queueUser }); } catch (err) { console.error(err); } }, 5000); ``` Call this endpoint **server-side only** — shipping your API key to a browser exposes it to anyone who opens DevTools. If browsers need these counts, proxy the call through your backend and push updates over WebSocket or SSE. ## Next Enumerate every project so you can fetch stats for each. Limits per endpoint and the standard error response format. ## Errors | Status | `code` | Cause | | - | - | - | | `403` | — | The project belongs to another account. | | `429` | `RATE_LIMITED` | More than 300 reads in a minute (API-key callers). `Retry-After` is set. | # Queue Analytics Source: https://docs.streampixel.io/resources/api-reference/queue-analytics GET https://platform.streampixel.io/api/v1/analytics/projects/{projectId}/analytics/queue How often viewers waited for a streaming server and how long. Return queueing statistics for a window: how many viewers queued, average, median and 95th-percentile wait, the peak number waiting at once and when, plus an hourly or daily series. A non-zero queue means demand exceeded the project's concurrent-user allowance at that time. Scope: `read`. Default window: the last 7 days, in daily buckets. ## Path parameters The project. ## Query parameters ISO 8601 start of the window. For API-key callers the window between `startDate` and `endDate` is capped at 92 days; move the window to read a longer period. ISO 8601 end of the window. `daily` or `hourly`. ## Headers Your Streampixel API key. ## Response `totalQueued`, `avgWaitMs`, `medianWaitMs`, `p95WaitMs`, `peakConcurrent`, `peakBucket` (the bucket label of the peak, `null` when nobody queued). Per bucket: `bucket` (`YYYY-MM-DD` for daily, `YYYY-MM-DDTHH:00:00Z` for hourly), `queuedCount` (viewers who waited in that bucket), `peakConcurrentQueued` (most waiting at the same moment), `avgWaitMs`, `peakWaitMs`. ```bash cURL theme={"dark"} curl "https://platform.streampixel.io/api/v1/analytics/projects/664f1a2b3c4d5e6f7a8b9c0d/analytics/queue?startDate=2026-09-19T00:00:00Z&endDate=2026-09-26T00:00:00Z&granularity=daily" \ -H "x-api-key: $STREAMPIXEL_API_KEY" ``` ```javascript Node.js theme={"dark"} const u = new URL('https://platform.streampixel.io/api/v1/analytics/projects/664f1a2b3c4d5e6f7a8b9c0d/analytics/queue'); u.search = new URLSearchParams({ startDate: '2026-09-19T00:00:00Z', endDate: '2026-09-26T00:00:00Z', granularity: 'daily' }); const { data } = await (await fetch(u, { headers: { 'x-api-key': process.env.STREAMPIXEL_API_KEY } })).json(); if (data.summary.p95WaitMs > 60_000) console.warn('viewers waited over a minute at p95; consider more capacity'); ``` ```python Python theme={"dark"} import os, requests r = requests.get( "https://platform.streampixel.io/api/v1/analytics/projects/664f1a2b3c4d5e6f7a8b9c0d/analytics/queue", params={"startDate": "2026-09-19T00:00:00Z", "endDate": "2026-09-26T00:00:00Z", "granularity": "daily"}, headers={"x-api-key": os.environ["STREAMPIXEL_API_KEY"]}, ) print(r.json()["data"]["summary"]) ``` ```json 200 OK theme={"dark"} { "success": true, "data": { "summary": { "totalQueued": 37, "avgWaitMs": 41200, "medianWaitMs": 28000, "p95WaitMs": 131000, "peakConcurrent": 4, "peakBucket": "2026-09-23" }, "timeseries": [ { "bucket": "2026-09-22", "queuedCount": 9, "peakConcurrentQueued": 2, "avgWaitMs": 30500, "peakWaitMs": 88000 }, { "bucket": "2026-09-23", "queuedCount": 21, "peakConcurrentQueued": 4, "avgWaitMs": 52100, "peakWaitMs": 131000 } ] } } ``` ## Errors | Status | Cause | | - | - | | `403` | The key lacks `read`, or the project belongs to another account. | | `404` | No project with that id. | | `400` | `granularity` not `daily` or `hourly`; `startDate`/`endDate` not ISO 8601, `startDate` after `endDate`, or a window wider than 92 days (API-key callers only). | | `429` | More than 300 reads in a minute (API-key callers). `Retry-After` is set. | # Rate limits & error codes Source: https://docs.streampixel.io/resources/api-reference/rate-limits-and-errors Rate limit windows, HTTP status codes, stable error codes and the error response format used across all Streampixel endpoints. Every REST endpoint shares the same authentication model, error format and rate-limit rules. This page is the reference for what happens when a request is refused and how to handle it in a production client. ## Rate limits | Endpoints | Limit | Scope | | - | - | - | | `POST /projects/upload-file`, `POST /projects/distribute-file` | **1 request per 2 minutes** | Per account | | Project control (`enable`, `disable`, `sessions/end`, `active-build`, `settings`) | **30 requests per minute**, all five combined | Per account | | Reads (every `GET` endpoint) | **300 requests per minute**, API-key callers | Per account | | Analytics date windows | **At most 92 days** between `startDate` and `endDate`, API-key callers | Per request | "No fixed limit" still means you must behave well. Aggressive polling or parallel fan-out against the analytics endpoints can be throttled at the edge. Poll build status every 30 to 60 seconds and live stats every few seconds at most; if you need more, contact support. When you exceed a limit the API returns `429 Too Many Requests` with a `Retry-After` header (seconds). The read limit and the date-window ceiling apply to API-key callers. They are abuse brakes: 300 reads a minute is far above what an integration needs, and a 92-day window covers any report. To read a longer period, move the window and page through it. A wider window is refused up front with `400 VALIDATION_FAILED` naming `startDate` and `maxDays`, before anything is queried. ```json Upload / Distribute theme={"dark"} { "message": "Rate limit exceeded. Try again in 117 seconds." } ``` ```json Project control theme={"dark"} { "code": "RATE_LIMITED", "message": "Too many control calls; at most 30 per minute per account. Retry in 12s.", "retryAfter": 12 } ``` ```json Reads theme={"dark"} { "code": "RATE_LIMITED", "message": "Too many read calls; at most 300 per minute per account. Retry in 4s.", "retryAfter": 4 } ``` ```json Date window too wide theme={"dark"} { "code": "VALIDATION_FAILED", "message": "Date range too wide: at most 92 days between startDate and endDate (requested 240). Move the window to page through longer periods.", "field": "startDate", "maxDays": 92 } ``` ## Error response format Every error is JSON with a human-readable `message`. Newer endpoints add a stable `code` you can branch on, and where useful a field naming what was wrong: ```json theme={"dark"} { "code": "VALIDATION_FAILED", "message": "Field(s) not settable here: allowedUsers, region", "unknown": ["allowedUsers", "region"] } ``` **Always read `message`.** Branch on `code` where present; treat it as optional on the older endpoints. There is no `errors[]` array or nested envelope. ### Stable codes | `code` | Status | Meaning | | - | - | - | | `API_KEY_IN_QUERY` | `400` | The key was sent in the URL. Move it to the `x-api-key` header and rotate it. | | `VALIDATION_FAILED` | `400` | A field is missing, unknown, out of range or inconsistent. The response names it (`field`, `unknown`, `invalid` or `inconsistent`). | | `BAD_REQUEST` | `400` | The request could not be understood. | | `UNAUTHENTICATED` | `401` | No valid credential. | | `API_KEY_SCOPE` | `403` | The key lacks the scope the endpoint needs (`requiredScope`). | | `FORBIDDEN` | `403` | Authenticated, but not allowed on this resource. | | `NOT_FOUND` | `404` | No such project, build, session or log. | | `CONFLICT` | `409` | The action does not fit the resource's current state, e.g. activating a build that is still processing. | | `RATE_LIMITED` | `429` | Over the limit; see `retryAfter` and the `Retry-After` header. | | `INTERNAL_ERROR` | `500` | Something failed on our side; safe to retry once. | ## HTTP status codes | Status | Meaning | Typical example | | - | - | - | | `200 OK` | Request succeeded with a response body. | Any read; control actions return the resulting state. | | `400 Bad Request` | Malformed request or invalid field. | `"maxRunTime must be an integer between 1 and 1440"` | | `401 Unauthorized` | Key missing or invalid. | `"Authentication required"` | | `403 Forbidden` | Wrong account, insufficient team role, or missing scope. | `"API_KEY_SCOPE"` | | `404 Not Found` | The resource does not exist. | `"Build not found on this project"` | | `409 Conflict` | Wrong state for the action. | Build still `Extracting & Scanning`. | | `415 Unsupported Media Type` | `Content-Type` is not `application/json` on a body request. | | | `429 Too Many Requests` | Rate limit. | See above. | | `500 Internal Server Error` | Unexpected failure. | `"Something went wrong on our side."` | ### Common causes * Required field omitted (`fileUrl`, `projectId`, `buildId`). * Wrong type or out-of-range value; enums are case-sensitive (`H264`, not `h264`). * A settings field that is not on the allow-list, such as `region` or `allowedUsers`. * Cross-field conflict: `minBitrate` above the stored `maxBitrate`, odd `resX`. * Webhook URL pointing at a private address. * `x-api-key` header missing. * Key rotated or revoked. * Key from a different environment. * Key issued without the scope (`API_KEY_SCOPE`). * Project belongs to another account, or the session id does not belong to the given `projectId`. * Team role cannot edit projects (control endpoints) or upload builds. * `projectId`, `buildId`, `sessionId` or `logFileId` does not exist. * The session has no Unreal log (its application never started, or it is still running). * Set Active Build on a build that is still downloading or scanning, or that failed. * Two upload or distribute calls inside 2 minutes. * More than 30 control calls in a minute. * Transient infrastructure issue: retry once after a short delay. * Persistent 500s: report to support with the endpoint, the time and the ids involved. ## Retry strategy * **Retry** on `429` (after `Retry-After` or the seconds in the message) and on `5xx`, with exponential backoff and jitter. * **Do not retry** other `4xx` responses; the request is wrong and retrying will not fix it. * Control actions are idempotent: repeating `enable`, `disable` or `sessions/end` after a timeout is safe. Repeating `active-build` re-queues distribution, which is harmless but wasteful. Repeating `PATCH /settings` with the same body is safe. # Session Detail Source: https://docs.streampixel.io/resources/api-reference/session-detail GET https://platform.streampixel.io/api/v1/analytics/sessions/{sessionId} Every status snapshot the platform recorded for one session: queueing, placement, which streaming server, and when it ended. Return the platform's own record of one session as the ordered list of status snapshots it wrote: when the viewer queued, when a streaming server was reserved and which one, when the application was running, and when the session ended. Reading them in order gives queue wait, start-up time and lifetime. Scope: `read`. `projectId` is required and must be the project the session belongs to; a session id from another project returns `403`. ## Path parameters The session id from [List Sessions](/resources/api-reference/list-sessions). ## Query parameters The project the session belongs to. ## Headers Your Streampixel API key. ## Response The session id. How many snapshots were recorded. Oldest first. Snapshot id. ISO 8601 time the snapshot was recorded. `Enqueued`, `Queued`, `Starting`, `Started`, `Terminating` or `Terminated`. Region. The streamer instance id. The viewer's player id. The account that owns the project. Render resolution the session was launched with. Runtime cap in minutes at the time. Path of the build that was launched; equals the build's `appPath` in [List Builds](/resources/api-reference/list-builds). `Dynamic` or `Fixed` concurrency. Which streaming server ran the session, and the fields the runtime records for its own use (addresses, GPU, the launch command line), are not returned. ```bash cURL theme={"dark"} curl "https://platform.streampixel.io/api/v1/analytics/sessions/session_1790294409382_3035?projectId=664f1a2b3c4d5e6f7a8b9c0d" \ -H "x-api-key: $STREAMPIXEL_API_KEY" ``` ```javascript Node.js theme={"dark"} const u = new URL('https://platform.streampixel.io/api/v1/analytics/sessions/session_1790294409382_3035'); u.searchParams.set('projectId', '664f1a2b3c4d5e6f7a8b9c0d'); const { snapshots } = await (await fetch(u, { headers: { 'x-api-key': process.env.STREAMPIXEL_API_KEY } })).json(); const at = (s) => snapshots.find((x) => x.data.status === s)?.receivedAt; console.log('queue wait s:', (new Date(at('Starting')) - new Date(at('Enqueued'))) / 1000); ``` ```python Python theme={"dark"} import os, requests r = requests.get( "https://platform.streampixel.io/api/v1/analytics/sessions/session_1790294409382_3035", params={"projectId": "664f1a2b3c4d5e6f7a8b9c0d"}, headers={"x-api-key": os.environ["STREAMPIXEL_API_KEY"]}, ) for s in r.json()["snapshots"]: print(s["receivedAt"], s["data"]["status"]) ``` ```json 200 OK theme={"dark"} { "sessionId": "session_1790294409382_3035", "snapshots": [ { "id": "…", "receivedAt": "2026-09-25T14:02:11.000Z", "data": { "sessionId": "session_1790294409382_3035", "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "userId": "663d0a1b2c3e4f5a6b7c8d9e", "status": "Enqueued", "region": "Europe", "projectType": "Dynamic", "playerId": "Player632", "streamingId": "st_7f3a", "resX": 1920, "resY": 1080 } }, { "id": "…", "receivedAt": "2026-09-25T14:02:16.000Z", "data": { "sessionId": "session_1790294409382_3035", "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "status": "Starting", "region": "Europe", "streamingId": "st_7f3a", "maxRunTime": 30, "appName": "C:\\ServerPixelStream\\665a2b3c4d5e6f7a8b9c0e1f\\Windows\\Showroom\\Binaries\\Win64\\Showroom.exe" } }, { "id": "…", "receivedAt": "2026-09-25T14:02:31.000Z", "data": { "sessionId": "session_1790294409382_3035", "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "status": "Started", "region": "Europe" } }, { "id": "…", "receivedAt": "2026-09-25T14:31:40.000Z", "data": { "sessionId": "session_1790294409382_3035", "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "status": "Terminated", "region": "Europe" } } ], "totalSnapshots": 4 } ``` ## Errors | Status | Cause | | - | - | | `403` | `projectId` missing, or the session belongs to a different project or account. | | `404` | No project with that id, or no session with that id. | | `429` | More than 300 reads in a minute (API-key callers). `Retry-After` is set. | # Session Logs Source: https://docs.streampixel.io/resources/api-reference/session-logs GET https://platform.streampixel.io/api/v1/analytics/log-files/session/{sessionId} The Unreal Engine log files captured for a session. List the Unreal Engine log files captured from the streaming server when a session's application exited. Usually one per session. Fetch the content with [Download Log](/resources/api-reference/download-log). Scope: `read`. A session whose application never started has no log; a session that is still running has not uploaded one yet. ## Path parameters The session. ## Query parameters The project the session belongs to. Files to return. Offset. ## Headers Your Streampixel API key. ## Response Log files for the session. Log file id, for [Download Log](/resources/api-reference/download-log). Original file name. Uncompressed size in bytes. Stored (compressed) size. `text/plain`. ISO 8601. Where the file is stored is not part of the response; [Download Log](/resources/api-reference/download-log) fetches it by `_id`. ```bash cURL theme={"dark"} curl "https://platform.streampixel.io/api/v1/analytics/log-files/session/session_1790294409382_3035?projectId=664f1a2b3c4d5e6f7a8b9c0d" \ -H "x-api-key: $STREAMPIXEL_API_KEY" ``` ```javascript Node.js theme={"dark"} const u = new URL('https://platform.streampixel.io/api/v1/analytics/log-files/session/session_1790294409382_3035'); u.searchParams.set('projectId', '664f1a2b3c4d5e6f7a8b9c0d'); const { logFiles } = await (await fetch(u, { headers: { 'x-api-key': process.env.STREAMPIXEL_API_KEY } })).json(); ``` ```python Python theme={"dark"} import os, requests r = requests.get( "https://platform.streampixel.io/api/v1/analytics/log-files/session/session_1790294409382_3035", params={"projectId": "664f1a2b3c4d5e6f7a8b9c0d"}, headers={"x-api-key": os.environ["STREAMPIXEL_API_KEY"]}, ) print(r.json()["logFiles"]) ``` ```json 200 OK theme={"dark"} { "success": true, "sessionId": "session_1790294409382_3035", "total": 1, "count": 1, "logFiles": [ { "_id": "66b1c2d3e4f5a6b7c8d9e0f1", "sessionId": "session_1790294409382_3035", "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "fileName": "Showroom.log", "fileSize": 1843210, "gzSize": 122087, "mimeType": "text/plain", "uploadedAt": "2026-09-25T14:31:52.000Z" } ] } ``` ## Errors | Status | Cause | | - | - | | `403` | `projectId` missing, or the session belongs to a different project or account. | | `404` | No project with that id. | | `429` | More than 300 reads in a minute (API-key callers). `Retry-After` is set. | # Session Telemetry Source: https://docs.streampixel.io/resources/api-reference/session-telemetry GET https://platform.streampixel.io/api/v1/analytics/frontend-streaming-logs What the viewer's browser reported during a session: WebRTC events and periodic stream statistics. Return the rows the player sent for one session. Two kinds share the list: **event rows** (`logType: "Webrtc"`) mark the WebRTC handshake and disconnects, and **statistics rows** (no `logType`) arrive every few seconds with FPS, decoded frames, bitrate, latency and packet loss. Together they answer "did this viewer actually see video, and how well". Scope: `read`. Rows come newest first; pass `logType=Webrtc` to get only the events. `projectId` is required and must match the session. ## Query parameters The session. The project the session belongs to. `Webrtc` to return event rows only. ISO 8601. For API-key callers the window between `startDate` and `endDate` is capped at 92 days; move the window to read a longer period. ISO 8601. Rows to return. A 30-minute session produces several hundred statistics rows; use `1000` to get them all. Offset. ## Headers Your Streampixel API key. ## Response Rows matching the query. Rows returned. `sdpOffer`, `sdpAnswer`, `connected`, `videoInitialized`, `disconnected`, `visibilitychange`, … ISO 8601. Present on the events they describe. Client details, on the first rows. ISO 8601. Frames per second decoded. Cumulative decoded frames. A session whose maximum stays `0` never showed video. Receive bitrate as reported by the player. Round-trip time in milliseconds. Cumulative counters. Negotiated codec and current resolution. ```bash cURL theme={"dark"} curl "https://platform.streampixel.io/api/v1/analytics/frontend-streaming-logs?sessionId=session_1790294409382_3035&projectId=664f1a2b3c4d5e6f7a8b9c0d&limit=1000" \ -H "x-api-key: $STREAMPIXEL_API_KEY" ``` ```javascript Node.js theme={"dark"} const u = new URL('https://platform.streampixel.io/api/v1/analytics/frontend-streaming-logs'); u.search = new URLSearchParams({ sessionId: 'session_1790294409382_3035', projectId: '664f1a2b3c4d5e6f7a8b9c0d', limit: '1000' }); const { logs } = await (await fetch(u, { headers: { 'x-api-key': process.env.STREAMPIXEL_API_KEY } })).json(); const stats = logs.filter((r) => !r.logType && r.loadingText == null); const maxFrames = Math.max(0, ...stats.map((r) => Number(r.framesDecoded) || 0)); console.log(maxFrames > 0 ? 'video delivered' : 'no video decoded'); ``` ```python Python theme={"dark"} import os, requests r = requests.get( "https://platform.streampixel.io/api/v1/analytics/frontend-streaming-logs", params={"sessionId": "session_1790294409382_3035", "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "limit": 1000}, headers={"x-api-key": os.environ["STREAMPIXEL_API_KEY"]}, ) rows = r.json()["logs"] events = [x["event"] for x in rows if x.get("logType") == "Webrtc"] ``` ```json 200 OK (abridged) theme={"dark"} { "success": true, "total": 412, "count": 412, "logs": [ { "sessionId": "session_1790294409382_3035", "timestamp": "2026-09-25T14:31:40.000Z", "logType": "Webrtc", "event": "disconnected", "code": 1000 }, { "sessionId": "session_1790294409382_3035", "timestamp": "2026-09-25T14:31:35.000Z", "FPS": "60", "framesDecoded": "104812", "bitrate": "8120", "latency": "38", "packetLoss": "0", "videoCodec": "H264", "videoResolution": "1920x1080" }, { "sessionId": "session_1790294409382_3035", "timestamp": "2026-09-25T14:02:33.000Z", "logType": "Webrtc", "event": "videoInitialized" }, { "sessionId": "session_1790294409382_3035", "timestamp": "2026-09-25T14:02:32.000Z", "logType": "Webrtc", "event": "connected", "iceConnectionState": "connected" }, { "sessionId": "session_1790294409382_3035", "timestamp": "2026-09-25T14:02:31.000Z", "logType": "Webrtc", "event": "sdpAnswer" }, { "sessionId": "session_1790294409382_3035", "timestamp": "2026-09-25T14:02:30.000Z", "logType": "Webrtc", "event": "sdpOffer", "browser": "Chrome", "os": "Windows", "deviceType": "desktop", "geoData": { "country": "DE", "city": "Berlin" } } ] } ``` ## Reading it | Pattern | Meaning | | - | - | | No rows at all | The player never reported: the tab was frozen while the app started, or an embedded player without telemetry. | | Events but `framesDecoded` never above `0` | Connected but no video: usually a codec the browser cannot decode, or the app never rendered. | | `sdpAnswer` but no `connected` | The media connection never came up: the viewer's network blocks WebRTC. | | `framesDecoded` rising and `FPS` above `0` | Video delivered. | ## Errors | Status | Cause | | - | - | | `403` | `projectId` missing, or the session belongs to a different project or account. | | `404` | No project with that id. | | `400` | `startDate`/`endDate` not ISO 8601, `startDate` after `endDate`, or a window wider than 92 days. API-key callers only. | | `429` | More than 300 reads in a minute (API-key callers). `Retry-After` is set. | # Set Active Build Source: https://docs.streampixel.io/resources/api-reference/set-active-build POST https://platform.streampixel.io/api/v1/projects/{projectId}/active-build Make a processed build the one viewers get: the API equivalent of Set Active in the dashboard. Choose which build the project serves. The build is marked `Ready`, the project is switched on, and distribution to the region's streaming servers is queued. Viewers get the new build once it reaches `Approved`; poll [Build Status](/resources/api-reference/build-status) for that. Scope: `control`. Team members need the Uploader role or above: deploying a build is a build job, not a project change. Limited to 30 control calls per minute per account. Only a build the pipeline has finished processing can be set active: status `Ready`, `Approved`, `Distribute` or `Distributing to Servers`. A build still downloading or scanning is refused with `409`; a failed or rejected build needs a new upload. Idempotent. Posting the build that is already live and approved changes nothing: the response has `changed: false` and `queued: false`, and the project is switched on if it was off. Send `redistribute: true` to copy that build to the region's streaming servers again, for example after a server was rebuilt. ## Path parameters The project. ## Headers An API key issued with the `control` scope. ## Request body The build to activate: `uploadId` from Upload File, or `id` from [List Builds](/resources/api-reference/list-builds). Copy the build to the region's streaming servers again even when it is already the live, approved build. ## Response The build now being distributed. `false` when the build was already live and the project already on. `true`: distribution was queued. `false`: the build is already live and nothing was copied. The build's status before this call. Always `true`: setting a build active switches the project on. What to do next. ```bash cURL theme={"dark"} curl -X POST https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d/active-build \ -H "x-api-key: $STREAMPIXEL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"buildId": "66a0b1c2d3e4f5a6b7c8d9e0"}' ``` ```javascript Node.js theme={"dark"} const res = await fetch( 'https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d/active-build', { method: 'POST', headers: { 'x-api-key': process.env.STREAMPIXEL_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ buildId: '66a0b1c2d3e4f5a6b7c8d9e0' }), } ); if (res.status === 409) console.log('not processed yet:', (await res.json()).message); ``` ```python Python theme={"dark"} import os, requests r = requests.post( "https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d/active-build", headers={"x-api-key": os.environ["STREAMPIXEL_API_KEY"]}, json={"buildId": "66a0b1c2d3e4f5a6b7c8d9e0"}, ) print(r.status_code, r.json()) ``` ```json 200 OK theme={"dark"} { "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "name": "Showroom", "region": "Europe", "enabled": true, "buildId": "66a0b1c2d3e4f5a6b7c8d9e0", "changed": true, "queued": true, "previousBuildStatus": "Ready", "note": "Distribution to the region's streaming servers is queued; poll GET /projects/{id}/files/{fileId}/status until it is Approved." } ``` ```json 200 OK (already the live build) theme={"dark"} { "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "name": "Showroom", "region": "Europe", "enabled": true, "buildId": "665a2b3c4d5e6f7a8b9c0e1f", "changed": false, "queued": false, "previousBuildStatus": "Approved", "note": "This build is already active and approved; nothing was changed. Send {\"redistribute\": true} to copy it to the region's streaming servers again." } ``` ```json 409 Conflict theme={"dark"} { "code": "CONFLICT", "message": "Build is \"Extracting & Scanning\" and cannot be set active yet. It must have been processed (Ready or Approved); failed or rejected builds need a new upload.", "status": "Extracting & Scanning" } ``` ## Errors | Status | `code` | Cause | | - | - | - | | `400` | `VALIDATION_FAILED` | `buildId` missing or not a 24-character id. | | `403` | `API_KEY_SCOPE` | The key was issued without `control`. | | `403` | — | The project belongs to another account, or the team role cannot edit projects. | | `404` | `NOT_FOUND` | No such project, or no such build on it. | | `409` | `CONFLICT` | The build has not been processed yet, or failed. | | `429` | `RATE_LIMITED` | More than 30 control calls in a minute. | ## A full release from CI [Upload File](/resources/api-reference/upload-file-api) with `autoRelease: false`; keep the `uploadId`. Poll [Build Status](/resources/api-reference/build-status) until `state` is `ready_for_activation`, or listen for the `build.saving` webhook. Call this endpoint with the `uploadId`. Poll Build Status until `state` is `live`. Optionally [End Sessions](/resources/api-reference/end-sessions) so current viewers reconnect onto the new build. # Update Project Settings Source: https://docs.streampixel.io/resources/api-reference/update-project-settings PATCH https://platform.streampixel.io/api/v1/projects/{projectId}/settings Change a project's streaming and player settings from an allow-list. Anything else is refused by name. Partial update of a project's streaming and player settings. Send only the fields you want to change. Validation is all-or-nothing: if any field is unknown, out of range or inconsistent with the stored values, nothing is written and the response names the field. Scope: `control`. Team members need the Editor role or above. Limited to 30 control calls per minute per account. Unreal launch arguments (`addLaunchArg`) are **not** settable through the API — set them in the dashboard. Plan, region, concurrent-user allowance, the project's on/off state and build moderation are also **not** settable here and are refused with `400`. Use the dashboard for those, and [Enable](/resources/api-reference/enable-project) / [Disable](/resources/api-reference/disable-project) for on/off. ## Path parameters The project to update. ## Headers An API key issued with the `control` scope. ## Request body Any subset of the fields below. ### Stream Render width, 320 to 7680, even. Render height, 240 to 4320, even. `AV1`, `H264`, `VP8` or `VP9`. Codec used when the browser cannot decode the primary one. Same values. (The field name is spelled `fallbaCodec`.) kbps, 100 to 200000. Must not exceed `maxBitrate`. kbps, between `minBitrate` and `maxBitrate`. kbps, 100 to 200000. Relay all media through TURN. For audiences behind strict corporate firewalls. Serve the player assets from the CDN. ### Session rules Maximum session length in minutes, 1 to 1440. Sessions end with code `4004` when reached. Inactivity timeout in seconds, 0 to 86400. `0` disables it; idle viewers then hold a GPU slot indefinitely. `public` or `programmatic`. Password and SSO access modes need companion settings and are configured in the dashboard. Comma-separated list of domains allowed to embed the player, e.g. `"app.example.com, www.example.com"`. Schemes and paths are stripped. An empty list allows any domain. Distribute processed builds automatically. When `false`, builds stop at `Ready` until [Set Active Build](/resources/api-reference/set-active-build). ### Resolution and quality presets `Dynamic Resolution Mode`, `Fixed Resolution Mode` or `Crop on Resize Mode`. Desktop start resolution: `360p (640x360)`, `480p (854x480)`, `720p (1280x720)`, `1080p (1920x1080)`, `1440p (2560x1440)` or `4K (3840x2160)`. Tablet start resolution, same values. Mobile start resolution, same values. Upper bound viewers may pick, same values. ### Player and input Project name, up to 120 characters. Milliseconds, 0 to 60000. Loading screen line 1, up to 200 characters. Loading screen line 2. Loading screen line 3. Mouse hover without click. Forward keyboard input. Forward mouse input. Forward touch input. Forward gamepad input. Forward XR input. Translate touch to mouse events. Show the player's control bar. Show the resolution picker. Show the volume control. Show the info button. Show the network indicator. Show the microphone control. Show the camera control. Warn viewers whose network blocks UDP. ### Webhooks Public `https://` endpoint for [webhooks](/resources/api-reference/webhooks). Private and cluster-internal addresses are refused. Send `""` to remove. Events to deliver, e.g. `["build.approved", "build.rejected"]`. Up to 20. ## Response The project id. The fields written. Those fields with their stored values. ```bash cURL theme={"dark"} curl -X PATCH https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d/settings \ -H "x-api-key: $STREAMPIXEL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"maxRunTime": 60, "primaryCodec": "H264", "resX": 1920, "resY": 1080}' ``` ```javascript Node.js theme={"dark"} const res = await fetch( 'https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d/settings', { method: 'PATCH', headers: { 'x-api-key': process.env.STREAMPIXEL_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ maxRunTime: 60, primaryCodec: 'H264', resX: 1920, resY: 1080 }), } ); const body = await res.json(); if (!res.ok) console.error(body.code, body.message, body.unknown ?? body.invalid ?? body.inconsistent); ``` ```python Python theme={"dark"} import os, requests r = requests.patch( "https://platform.streampixel.io/api/v1/projects/664f1a2b3c4d5e6f7a8b9c0d/settings", headers={"x-api-key": os.environ["STREAMPIXEL_API_KEY"]}, json={"maxRunTime": 60, "primaryCodec": "H264", "resX": 1920, "resY": 1080}, ) print(r.status_code, r.json()) ``` ```json 200 OK theme={"dark"} { "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "updated": ["maxRunTime", "primaryCodec", "resX", "resY"], "settings": { "maxRunTime": 60, "primaryCodec": "H264", "resX": 1920, "resY": 1080 } } ``` ```json 400 unknown field theme={"dark"} { "code": "VALIDATION_FAILED", "message": "Field(s) not settable here: allowedUsers, region", "unknown": ["allowedUsers", "region"] } ``` ```json 400 invalid value theme={"dark"} { "code": "VALIDATION_FAILED", "message": "primaryCodec must be one of AV1, H264, VP8, VP9; maxRunTime must be an integer between 1 and 1440", "invalid": [ { "field": "primaryCodec", "reason": "must be one of AV1, H264, VP8, VP9" }, { "field": "maxRunTime", "reason": "must be an integer between 1 and 1440" } ] } ``` ```json 400 inconsistent theme={"dark"} { "code": "VALIDATION_FAILED", "message": "minBitrate (2000) must not exceed maxBitrate (500)", "inconsistent": ["minBitrate (2000) must not exceed maxBitrate (500)"] } ``` ## Errors | Status | `code` | Cause | | - | - | - | | `400` | `VALIDATION_FAILED` | Unknown field (`unknown`), out-of-range value (`invalid`), or a cross-field conflict with stored values (`inconsistent`). Nothing was written. | | `400` | — | `Invalid webhook URL: …` when the URL points at a private address. | | `403` | `API_KEY_SCOPE` | The key was issued without `control`. | | `403` | — | The project belongs to another account, or the team role cannot edit projects. | | `404` | `NOT_FOUND` | No project with that id. | | `429` | `RATE_LIMITED` | More than 30 control calls in a minute. | Changes apply to sessions that start after the call. Running sessions keep the settings they launched with; call [End Sessions](/resources/api-reference/end-sessions) if everyone must pick up the new values now. # Upload File API Source: https://docs.streampixel.io/resources/api-reference/upload-file-api POST https://platform.streampixel.io/api/v1/projects/upload-file Submit a packaged Unreal Engine build to a Streampixel project by providing a public download URL. Upload a packaged Unreal Engine build (`.zip`) to your Streampixel project by providing a publicly accessible download URL. This is the endpoint used by CI/CD pipelines, automation tools, and the official Unreal plugin. ## Typical workflow Compile your Unreal Engine project. Package the build into a `.zip` file. Upload the `.zip` to your own cloud storage (S3, GCS, etc.) and obtain a public download link. Submit the public link to Streampixel via this endpoint. ## Prerequisites | Requirement | Where to get it | | - | - | | API Key | [API authentication](/resources/api-reference/api-authentication) | | User ID | [Finding your IDs](/resources/api-reference/finding-your-user-and-project-ids) | | Project ID | [Finding your IDs](/resources/api-reference/finding-your-user-and-project-ids) | | File URL | Your cloud storage (see requirements below) | ## File URL requirements The `fileUrl` must be a **direct download link**. The moment Streampixel accesses it, the `.zip` download must begin — no login pages, redirects, HTML wrappers, or download buttons. **Supported hosting:** * AWS S3 (signed or public URL) * Google Cloud Storage * DigitalOcean Spaces * Any HTTP-accessible file server | | URL | | - | - | | ✅ Valid | `https://your-bucket.s3.amazonaws.com/MyBuild.zip` | | ❌ Invalid | `https://drive.google.com/file/d/abc123/view` | ## Request body Your Streampixel API key. See [API authentication](/resources/api-reference/api-authentication). The ID of the account that owns the project. The project to upload the build to. Public direct-download URL of the `.zip` build file. When `true`, the build is automatically distributed to streaming servers after approval. Set to `false` if you want to call the [Distribute File API](/resources/api-reference/distribute-file-api) manually instead. ## Response `true` when the build was accepted and registered. Unique identifier of the uploaded build (the build's `_id`). Pass it to the [Distribute File API](/resources/api-reference/distribute-file-api) or use it to correlate webhook events. Human-readable status — `"File uploaded successfully"` on success. ```bash cURL theme={"dark"} curl -X POST https://platform.streampixel.io/api/v1/projects/upload-file \ -H "Content-Type: application/json" \ -d '{ "apiKey": "[YOUR_API_KEY]", "userId": "[YOUR_USER_ID]", "fileUrl": "[PUBLIC_FILE_URL]", "projectId": "[YOUR_PROJECT_ID]", "autoRelease": true }' ``` ```javascript Node.js theme={"dark"} const axios = require('axios'); const response = await axios.post( 'https://platform.streampixel.io/api/v1/projects/upload-file', { apiKey: '[YOUR_API_KEY]', userId: '[YOUR_USER_ID]', fileUrl: '[PUBLIC_FILE_URL]', projectId: '[YOUR_PROJECT_ID]', autoRelease: false, } ); console.log('Upload successful:', response.data); ``` ```python Python theme={"dark"} import requests response = requests.post( "https://platform.streampixel.io/api/v1/projects/upload-file", json={ "apiKey": "[YOUR_API_KEY]", "userId": "[YOUR_USER_ID]", "fileUrl": "[PUBLIC_FILE_URL]", "projectId": "[YOUR_PROJECT_ID]", "autoRelease": False, } ) print("Upload successful:", response.json()) ``` ```go Go theme={"dark"} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { url := "https://platform.streampixel.io/api/v1/projects/upload-file" data := map[string]interface{}{ "apiKey": "[YOUR_API_KEY]", "userId": "[YOUR_USER_ID]", "fileUrl": "[PUBLIC_FILE_URL]", "projectId": "[YOUR_PROJECT_ID]", "autoRelease": false, } body, _ := json.Marshal(data) resp, err := http.Post(url, "application/json", bytes.NewBuffer(body)) if err != nil { fmt.Println("Request failed:", err) return } defer resp.Body.Close() fmt.Println("Response status:", resp.Status) } ``` ```java Java theme={"dark"} import okhttp3.*; public class UploadFile { public static void main(String[] args) throws Exception { OkHttpClient client = new OkHttpClient(); MediaType mediaType = MediaType.parse("application/json"); String jsonBody = "{" + "\"apiKey\":\"[YOUR_API_KEY]\"," + "\"userId\":\"[YOUR_USER_ID]\"," + "\"fileUrl\":\"[PUBLIC_FILE_URL]\"," + "\"projectId\":\"[YOUR_PROJECT_ID]\"," + "\"autoRelease\":false" + "}"; RequestBody body = RequestBody.create(mediaType, jsonBody); Request request = new Request.Builder() .url("https://platform.streampixel.io/api/v1/projects/upload-file") .post(body) .addHeader("Content-Type", "application/json") .build(); Response response = client.newCall(request).execute(); System.out.println(response.body().string()); } } ``` ```json 200 OK theme={"dark"} { "success": true, "uploadId": "665a2b3c4d5e6f7a8b9c0e1f", "message": "File uploaded successfully" } ``` ```json 400 Bad Request theme={"dark"} { "message": "Missing required fields" } ``` ```json 401 Unauthorized theme={"dark"} { "message": "Unauthorized: Invalid API Key" } ``` ```json 409 Conflict theme={"dark"} { "message": "This build is already registered" } ``` ```json 415 Unsupported Media Type theme={"dark"} { "message": "Unsupported Media Type" } ``` ## Error reference | Status | Message | Cause | | - | - | - | | `400` | `Missing required fields` | `fileUrl`, `projectId`, or `apiKey` was omitted from the body. | | `400` | `❌ Invalid content type` | `fileUrl` isn't a direct `https://…/name.zip` link (or carries a query string). | | `400` | `File too large` | The file exceeds your project's upload limit. | | `400` | `That host looks like a Streampixel build host but is not one we serve` | The URL points at a Streampixel-style host we don't serve. Use your own storage. | | `401` | `Invalid User` | `userId` doesn't match any account. | | `401` | `Unauthorized: Invalid API Key` | `apiKey` is wrong or has been invalidated. | | `403` | `Access denied` | The authenticated account doesn't own the project (or it's disabled by an admin). | | `404` | `Project not found` | No project with the supplied `projectId`. | | `404` | `No object exists at that location — the upload may not have completed` | The `fileUrl` returned nothing; finish the upload to your storage first. | | `409` | `This build is already registered` | The same `fileUrl` was already submitted — retries are de-duplicated. | | `415` | `Unsupported Media Type` | The file at `fileUrl` is zero-length. | | `500` | `Internal Server Error` | Unexpected failure. Retry after a short delay. | The `409 Conflict` guard means a retried upload of the **same** `fileUrl` won't create a duplicate build — it's safe to retry a call that timed out. ## What happens after upload Streampixel pulls the `.zip` from your `fileUrl`. The system verifies the archive is accessible and properly structured. On success, the build is attached to your project and becomes ready to distribute. Track each stage of the pipeline via [webhooks](/resources/api-reference/webhooks) — listen for `build.downloading`, `build.extracting`, `build.saving`, and `build.approved` events. ## Confirming success * Sign in to the [dashboard](https://dashboard.streampixel.io). * Open your project's detail page. * The new build appears in the build list with its current processing status. ## Next steps Push an approved build to streaming servers (only needed when `autoRelease: false`). Get notified when builds are approved, rejected, or distributed. # Webhook event reference Source: https://docs.streampixel.io/resources/api-reference/webhook-events Complete reference for every webhook event Streampixel sends — names, payloads, and the build state that triggers each. This page is the canonical reference for every webhook event Streampixel sends. For setup (where to enter your URL, how to choose events, retry behavior, handler best practices), see the main [Webhooks](/resources/api-reference/webhooks) page. ## Quick links Configure your URL, choose events, and follow handler best practices. The endpoint that kicks off the build pipeline and the events below. ## Pipeline overview Every uploaded build moves through a fixed sequence of pipeline stages. Each stage transition fires a webhook event (subject to your subscription filters). ```mermaid theme={"dark"} flowchart TD Upload[POST /projects/upload-file] --> P[pending] P -->|build.uploaded| DL[Downloading Files] DL -->|build.downloading| EX[Extracting & Scanning] EX -->|build.extracting| SV[Saving to Repository] SV -->|build.saving| DI[Distribute] DI -->|build.distributing| AP[Approved] DI -.->|build.rejected| RJ[Reject] AP -->|build.approved| Done[Ready to stream] ``` The dashed edge to `build.rejected` indicates that a build can be rejected at the distribute stage instead of progressing to `Approved`. Once rejected, no further events fire for that build. ## Common payload fields Every webhook payload (build events and the test event) shares the same envelope. | Field | Type | Description | | - | - | - | | `event` | `string` | The event name (e.g., `build.approved`). | | `projectId` | `string` | The Streampixel project the event belongs to. | | `uploadId` | `string \| null` | The build identifier. `null` for events that fire before an upload exists. | | `fileUrl` | `string \| null` | The build's source URL. May be `null` for early-pipeline events. | | `status` | `string` | Raw pipeline status string for the build. | | `objection` | `string \| null` | Reason for rejection. Only populated on `build.rejected`. | | `timestamp` | `string` | ISO 8601 timestamp of when the event occurred. | All deliveries are `HTTP POST` with `Content-Type: application/json` and a 10-second timeout. There are no retries — see the [Webhooks](/resources/api-reference/webhooks) page for handler best practices. ## Events ### `build.uploaded` **Triggered by:** A new build has been accepted by `POST /projects/upload-file` and queued for processing. This is the very first event in the pipeline. **Status at trigger:** `pending`. ```json theme={"dark"} { "event": "build.uploaded", "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "uploadId": "665a2b3c4d5e6f7a8b9c0e1f", "fileUrl": null, "status": "pending", "objection": null, "timestamp": "2026-03-29T13:00:00.000Z" } ``` **Notes:** `fileUrl` may be `null` at this stage because the upload manager has not yet resolved the source. Use `uploadId` as your correlation key going forward. *** ### `build.downloading` **Triggered by:** The build manager has begun downloading the `.zip` from the URL you submitted. **Status at trigger:** `Downloading Files`. ```json theme={"dark"} { "event": "build.downloading", "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "uploadId": "665a2b3c4d5e6f7a8b9c0e1f", "fileUrl": "https://your-bucket.s3.amazonaws.com/builds/v1.2.0.zip", "status": "Downloading Files", "objection": null, "timestamp": "2026-03-29T13:00:12.000Z" } ``` **Notes:** A long-running `build.downloading` without progressing to `build.extracting` usually means the source URL is slow or unreachable. *** ### `build.extracting` **Triggered by:** The downloaded archive is being extracted and scanned for required files. **Status at trigger:** `Extracting & Scanning`. ```json theme={"dark"} { "event": "build.extracting", "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "uploadId": "665a2b3c4d5e6f7a8b9c0e1f", "fileUrl": "https://your-bucket.s3.amazonaws.com/builds/v1.2.0.zip", "status": "Extracting & Scanning", "objection": null, "timestamp": "2026-03-29T13:01:30.000Z" } ``` **Notes:** If the archive is malformed or missing the expected Unreal binary, the build skips to `build.rejected` with an `objection` describing the failure. *** ### `build.saving` **Triggered by:** The validated build is being persisted to the project repository. **Status at trigger:** `Saving to Repository`. ```json theme={"dark"} { "event": "build.saving", "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "uploadId": "665a2b3c4d5e6f7a8b9c0e1f", "fileUrl": "https://your-bucket.s3.amazonaws.com/builds/v1.2.0.zip", "status": "Saving to Repository", "objection": null, "timestamp": "2026-03-29T13:03:00.000Z" } ``` **Notes:** This stage is typically short. After `build.saving`, the build moves to `Distribute` (and, if `autoRelease` is `true` in the upload, on to `build.distributing` automatically). *** ### `build.distributing` **Triggered by:** The build is being copied to streaming servers and made ready to serve traffic. Fires when `autoRelease` was `true` on upload, or when [Distribute File API](/resources/api-reference/distribute-file-api) was called manually. **Status at trigger:** `Distribute`. ```json theme={"dark"} { "event": "build.distributing", "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "uploadId": "665a2b3c4d5e6f7a8b9c0e1f", "fileUrl": "https://your-bucket.s3.amazonaws.com/builds/v1.2.0.zip", "status": "Distribute", "objection": null, "timestamp": "2026-03-29T13:05:00.000Z" } ``` **Notes:** Subscribe to this event if you want to know when streaming servers are warming up — the build is almost ready, but not yet live. *** ### `build.approved` **Triggered by:** Distribution finished successfully and the build is now the live build for the project. This is the event most CI/CD pipelines key off of. **Status at trigger:** `Approved`. ```json theme={"dark"} { "event": "build.approved", "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "uploadId": "665a2b3c4d5e6f7a8b9c0e1f", "fileUrl": "https://your-bucket.s3.amazonaws.com/builds/v1.2.0.zip", "status": "Approved", "objection": null, "timestamp": "2026-03-29T13:08:00.000Z" } ``` **Notes:** Treat this as the "go-live" signal — kick off downstream deploys, smoke tests, or release announcements here. *** ### `build.rejected` **Triggered by:** The build failed validation at any stage of the pipeline. Once rejected, no further events fire for this `uploadId`. **Status at trigger:** `Reject`. ```json theme={"dark"} { "event": "build.rejected", "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "uploadId": "665a2b3c4d5e6f7a8b9c0e1f", "fileUrl": "https://your-bucket.s3.amazonaws.com/builds/v1.2.0.zip", "status": "Reject", "objection": "Build contains unsupported file formats.", "timestamp": "2026-03-29T13:04:30.000Z" } ``` **Notes:** Always read `objection` to learn why the build was rejected. Surface it in your team's CI logs or chat so the developer who pushed the build can act on it. *** ### `webhook.test` **Triggered by:** Manually clicking the **Test** button in **Project Settings**. Never fired during real build operations. **Status at trigger:** `test`. ```json theme={"dark"} { "event": "webhook.test", "projectId": "test_project_id", "uploadId": "test_upload_id", "fileUrl": "https://example.com/test-build.zip", "status": "test", "objection": null, "timestamp": "2026-03-29T14:30:00.000Z" } ``` **Notes:** All ID fields are placeholder strings — they do not correspond to real records. Your handler should detect this event and short-circuit any state-machine logic that would otherwise act on it. ## Event timing summary | Event | Fires when | Typical delay from upload | | - | - | - | | `build.uploaded` | API accepts the upload | Immediate | | `build.downloading` | Download begins | Seconds | | `build.extracting` | Extraction begins | Tens of seconds | | `build.saving` | Persist begins | A minute or two | | `build.distributing` | Distribution begins | A minute or two after `saving` | | `build.approved` | Build is live | Variable — depends on build size | | `build.rejected` | Validation fails | Any time during the pipeline | | `webhook.test` | Manual or API trigger | Immediate | Times above are typical, not guaranteed. Larger builds, busy regions, and slow source URLs can all extend pipeline duration significantly. ## Handling events safely A few rules that apply across every event: * **Use `uploadId` + `event` as your idempotency key.** In rare cases the same event may arrive more than once; deduplicate on your side. * **Acknowledge fast.** Return `200 OK` within 10 seconds, then process in the background. Streampixel does not retry failed deliveries — see the [Webhooks](/resources/api-reference/webhooks) page for full guidance. * **Tolerate unknown events.** New events may be added in the future; your handler should treat unfamiliar `event` strings as a no-op rather than crashing. * **Validate the project.** Confirm `projectId` matches a project you expect — never act on a webhook for an unknown project. ## Next steps Configure URLs, choose events, and follow handler best practices. The endpoint that triggers the build pipeline events documented above. # Overview Source: https://docs.streampixel.io/resources/api-reference/webhooks Receive real-time HTTP notifications when builds are uploaded, approved, rejected, or distributed. Webhooks push HTTP notifications to your endpoint whenever a build event happens — uploaded, approved, rejected, distributed. Use them to trigger deploys, post alerts, kick off tests, or mirror pipeline state into your monitoring stack. ## Set up a webhook From the dashboard, navigate to your project and open **Project Settings**. Provide a publicly accessible HTTPS endpoint in the **Webhook URL** field. Tick the checkboxes for the build events you want to receive. At least one event must be selected while a webhook URL is set. Click **Save**. Once saved, click **Test Webhook** to send a sample payload to your endpoint. (Save first — the **Test** button won't fire on unsaved changes.) Your endpoint must be reachable from the internet and able to accept HTTP POST requests with a JSON body. ## Events Streampixel emits a webhook at each stage of the build pipeline. | Event | Description | | - | - | | `build.uploaded` | A new build file has been uploaded to the project. | | `build.downloading` | The build file is being downloaded for processing. | | `build.extracting` | The build file is being extracted and scanned. | | `build.saving` | The build is being saved to the repository. | | `build.distributing` | The build is being distributed to streaming servers. | | `build.approved` | The build has been reviewed and approved. | | `build.rejected` | The build has been reviewed and rejected. | Events fire in roughly the order listed, following the natural progression of the pipeline. Not every build triggers every event — for example, a rejected build never fires `build.distributing`. ## Payload Every webhook is an `HTTP POST` request with `Content-Type: application/json`. The event type (e.g., `build.approved`). See the events table above. The Streampixel project ID. The unique identifier of the uploaded build. The URL of the build file, when available. Raw pipeline status (e.g., `Approved`, `Reject`, `Distribute`). The reason for rejection. Only populated when `event` is `build.rejected`. ISO 8601 timestamp of when the event occurred. ### Examples ```json build.approved theme={"dark"} { "event": "build.approved", "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "uploadId": "665a2b3c4d5e6f7a8b9c0e1f", "fileUrl": "https://cdn.streampixel.io/builds/my-build.zip", "status": "Approved", "objection": null, "timestamp": "2026-03-29T14:30:00.000Z" } ``` ```json build.rejected theme={"dark"} { "event": "build.rejected", "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "uploadId": "665a2b3c4d5e6f7a8b9c0e1f", "fileUrl": "https://cdn.streampixel.io/builds/my-build.zip", "status": "Reject", "objection": "Build contains unsupported file formats.", "timestamp": "2026-03-29T15:00:00.000Z" } ``` ```json build.uploaded theme={"dark"} { "event": "build.uploaded", "projectId": "664f1a2b3c4d5e6f7a8b9c0d", "uploadId": "665a2b3c4d5e6f7a8b9c0e1f", "fileUrl": null, "status": "pending", "objection": null, "timestamp": "2026-03-29T13:00:00.000Z" } ``` ```json webhook.test theme={"dark"} { "event": "webhook.test", "projectId": "test_project_id", "uploadId": "test_upload_id", "fileUrl": "https://example.com/test-build.zip", "status": "test", "objection": null, "timestamp": "2026-03-29T14:30:00.000Z" } ``` ## Testing your endpoint Save a webhook URL in Project Settings, then click the **Test Webhook** button to send a `webhook.test` payload to that URL. The dashboard reports the result: * **Success** — *"Webhook delivered successfully."* Your endpoint responded with a `2xx` status. * **Failure** — An error toast. Verify the URL is correct, publicly accessible over HTTPS, and returns a `2xx` response. The `webhook.test` event is only sent when you click the **Test Webhook** button. It is never fired during normal build operations. ## Filtering events Choose exactly the events you care about with the checkboxes in Project Settings. At least one event must be selected while a webhook URL is set — there is no "all" default, so a new webhook receives nothing until you tick the events you want: * **Only care about final outcomes?** Select just `build.approved` and `build.rejected`. * **Want full pipeline visibility?** Tick all seven events. * **Monitoring distribution?** Select `build.distributing` and `build.approved`. If an event occurs that you have not subscribed to, no request is sent. ## Best practices Streampixel does **not retry** failed webhook deliveries. If your endpoint is unreachable or returns a non-`2xx` status, the event is lost. Make sure your endpoint has high availability. * **Respond quickly** — return `200 OK` as fast as possible. Streampixel waits up to **10 seconds** before timing out. Move heavy processing to a background job. * **Use HTTPS** — always use an HTTPS URL so payloads are encrypted in transit. * **Be idempotent** — in rare cases the same event may arrive more than once. Use `uploadId` + `event` as an idempotency key. * **Log everything** — log all incoming payloads. Logs are invaluable for debugging integration issues. * **Validate the project** — confirm `projectId` matches the project you expect. * **Monitor uptime** — there are no retries, so any downtime means missed events. Set up uptime monitoring for your endpoint. ## Receiving webhooks ```javascript theme={"dark"} const express = require('express'); const app = express(); app.use(express.json()); app.post('/webhooks/streampixel', (req, res) => { const { event, projectId, uploadId, objection } = req.body; console.log(`Received event: ${event} for project: ${projectId}`); switch (event) { case 'build.approved': console.log(`Build ${uploadId} approved. Triggering deployment...`); // Start your deployment pipeline break; case 'build.rejected': console.log(`Build ${uploadId} rejected. Reason: ${objection}`); // Notify your team break; case 'webhook.test': console.log('Test webhook received successfully!'); break; default: console.log(`Event: ${event}`); } // Always respond with 200 quickly res.status(200).json({ received: true }); }); app.listen(3000, () => { console.log('Webhook server running on port 3000'); }); ``` ```python theme={"dark"} from flask import Flask, request, jsonify app = Flask(__name__) @app.route('/webhooks/streampixel', methods=['POST']) def handle_webhook(): payload = request.get_json() event = payload.get('event') project_id = payload.get('projectId') upload_id = payload.get('uploadId') objection = payload.get('objection') print(f"Received event: {event} for project: {project_id}") if event == 'build.approved': print(f"Build {upload_id} approved. Triggering deployment...") # Start your deployment pipeline elif event == 'build.rejected': print(f"Build {upload_id} rejected. Reason: {objection}") # Notify your team elif event == 'webhook.test': print("Test webhook received successfully!") # Always respond with 200 quickly return jsonify({"received": True}), 200 if __name__ == '__main__': app.run(port=3000) ``` ## Troubleshooting | Problem | Solution | | - | - | | Test webhook fails | Ensure your URL is publicly accessible (not `localhost`). For local development, use a tool like [ngrok](https://ngrok.com). | | Not receiving webhooks | Verify the correct events are checked in Project Settings and that your webhook URL is saved. | | Receiving timeouts | Your endpoint must respond within **10 seconds**. Move heavy work to a background job. | | Missing events | Streampixel does not retry failed deliveries. If your server was down when the event fired, the notification is lost. Check your server logs. | | Unexpected payload format | Ensure your server parses the `Content-Type: application/json` body correctly. | | Duplicate events | Use `uploadId` + `event` as an idempotency key to skip duplicates. | ## Quick reference | Detail | Value | | - | - | | HTTP method | `POST` | | Content type | `application/json` | | Timeout | 10 seconds | | Retries | None | | Events available | 7 build pipeline events + 1 test event | ## Next steps Combine `build.approved` events with the Distribute API for a controlled CI/CD flow. The endpoint that triggers the build pipeline events documented above. # Build configurations Source: https://docs.streampixel.io/resources/cloud-builds/build-configurations Define how builds are produced from your connected repository — build type, branch, and triggers. A **build configuration** tells Streampixel how to produce a build from your connected source: which branch or stream, which build type, and what kicks the build off. Set one up once and reuse it on every commit. A project can have several — for example, a Development build on every push to `develop` plus a nightly Shipping build from `main`. Find them on your project's **Builds** page under **Build History → Build configurations**, and click **New configuration**. ## Configuration fields | Field | Options | Notes | | - | - | - | | Platform | `win64` | More platforms coming soon. | | Build type | Development, Shipping | Development builds faster and keeps debug features; Shipping is what you put in front of users. | | Branch *(GitHub)* | Any branch | Picked from your repo's branch list; defaults to `main`. | | Stream *(Perforce)* | Any buildable stream | See stream rules below. | | Trigger | Manual, Push, Schedule | See [Triggers](#triggers). | | Notify on completion | On / Off *(default off)* | Emails the project owner when the build succeeds. | | Notify on failure | On / Off *(default on)* | Emails the project owner when the build fails. | | Auto-deploy | On / Off *(default off)* | Sets the new build live on success — see below. | ### Perforce stream rules The stream picker shows your depot's stream tree with each stream's type. Two types need care: * **Virtual streams** can't be selected — they're filtered views, not buildable source. * **Task streams** work, but they're temporary; the configuration breaks when the stream is removed. ## Triggers Nothing runs automatically. Start a build with the **Run** button on the configuration's row whenever you want one. Every push to the configured branch starts a build automatically — commit, and a few minutes later there's a fresh build in Build History. Pair with **Auto-deploy** for a full commit-to-stream pipeline. Builds run on a recurring schedule — hourly, daily, weekly, or monthly, at the time and timezone you pick. The schedule builder previews the next runs so you can confirm before saving. Typical use: a nightly build from `main` regardless of commit activity. Any configuration can also be run manually with **Run**, whatever its trigger. ## Auto-deploy With **Auto-deploy** on, a successful build is deployed automatically: it enters your project's deployment pipeline and goes live once processed — the same flow as clicking **Deploy** by hand. Leave it off if you want to inspect or download builds first and deploy selectively. ## Edit or delete a configuration Use the row actions on the **Build configurations** table. Deleting a configuration doesn't touch existing builds — they stay in Build History — but nothing new can be triggered from it, and any schedule stops. ## Next Stages, statuses, live logs, and what to do when a build fails. Not connected yet? Start here. # Connect your repository Source: https://docs.streampixel.io/resources/cloud-builds/connect-your-repository Link a GitHub repository or Perforce depot to your Streampixel project for cloud builds. Cloud builds pull your project source from a repository connected to your Streampixel project. You connect one source per project — a **GitHub** repository or a **Perforce** depot — and every build configuration on that project builds from it. Open your project's **Builds** page and click **Connect Repository** (top right), then pick your provider. ## Connect GitHub GitHub access works through a **GitHub App installation**: you install the Streampixel app on your GitHub account or organization and choose which repositories it can see. Streampixel never gets your GitHub password, and you can narrow or revoke access on GitHub at any time. In the connect dialog, choose **GitHub**. If you haven't connected an account yet, click **Connect GitHub** — a GitHub window opens where you pick the account or organization and select which repositories to grant. Back in the dialog, select the GitHub account, then search and select the repository that contains your Unreal project. Private repositories work — they're marked **private** in the list. Streampixel links the repo to the project and prepares a workspace for it, so your first build doesn't start from a cold clone. Repository missing from the list? It wasn't granted to the installation. Open the connect dialog's GitHub step again and use **Configure** on GitHub to add it, or install the app on the organization that owns it. You can connect multiple GitHub accounts and organizations. ## Connect Perforce Perforce connections use a stored server credential: Streampixel signs in to your Perforce server with a user you provide and syncs from a depot. In the connect dialog, choose **Perforce**, then **Connect Perforce**. Enter the **server URL** (e.g. `ssl:perforce.example.com:1666`), a **username**, and either a **password** or a **P4 ticket**. Select the Perforce account, then the **depot** your project lives in. The first depot listing can take around 30 seconds while Streampixel talks to your server. The depot applies to the whole project — individual build configurations then pick a **stream** within it. Use a dedicated build user with read access rather than a personal account. Passwords are stored encrypted, and you can update or remove accounts under **Settings → Integrations**. ## The `.uproject` requirement Streampixel locates your `.uproject` file automatically — it doesn't need to be at the repository root. Two things must hold: * **It must exist.** If no `.uproject` is found in the repo or stream, connecting fails with `No .uproject file found`. * **It must be valid JSON.** The engine version for your builds is read from its `EngineAssociation` field, so a malformed file blocks detection. ## Manage connected accounts Source-control accounts live at the account level under **Settings → Integrations** — the same GitHub installation or Perforce account can serve several projects. From there you can add accounts, open an installation on GitHub, or disconnect one. A Perforce account that's still assigned to projects can't be deleted — reassign those projects to another account first. ## Change or disconnect the source Once connected, the **Connect Repository** button becomes a source chip showing your repo or depot. Its menu offers **Change source…** and **Disconnect repository**. Disconnecting **permanently deletes all build configurations** on the project, including scheduled builds. Your build history is kept, but you'll need to reconnect a repository and re-create configurations to run new builds. ## Next Define what gets built, from where, and when. Follow a build from source sync to artifact. # Cloud builds overview Source: https://docs.streampixel.io/resources/cloud-builds/overview Connect your source control and every commit builds itself — compiled, cooked, and packaged on Streampixel's build infrastructure. Stop uploading builds by hand. Connect your repository once, and every commit builds itself — compiled, cooked, and packaged in the cloud, in minutes instead of hours of local packaging and upload. Then do what you want with the result: **stream it in the browser**, or **download the build and ship it anywhere**. No build machine to maintain, no packaging scripts, no 20 GB `.zip` crawling up your connection. Set it up once — source, build type, trigger — and it runs on every commit. Cloud builds are powered by **Unreal Engine Horde** running on Streampixel's build fleet, which distributes the compile, shader, and cook work across many machines in parallel — the reason a build that ties up your workstation for hours finishes in minutes. ## Watch it in action ``` **Key attributes** * **`src`**: Replace `project-id` with the actual ID of your Streampixel project. * **`allow="autoplay; fullscreen"`**: Enables autoplay and fullscreen features. * **`allowfullscreen`**: Ensures the stream can enter fullscreen mode. * **`style`**: Customize the iframe's appearance, such as width and height. Always include `allow="autoplay; fullscreen"` to enable autoplay and fullscreen functionality. To ensure the stream is fully interactive, handle input focus properly for keyboard and mouse inputs. **Keyboard input** When users interact with the iframe, ensure it gains focus to capture keyboard input: ```javascript theme={"dark"} const streamIframe = document.getElementById('streamIframe'); streamIframe.addEventListener('click', () => { streamIframe.focus(); // Pass keyboard focus to the iframe on user interaction }); ``` **Mouse input** Mouse interactions typically work out of the box. Ensure the iframe is styled appropriately to cover the desired area. If your frontend includes buttons or controls outside the iframe (e.g., Mute, Unmute, or custom settings), clicking those controls shifts focus away from the iframe. To maintain seamless interaction with the stream, you must restore focus to the iframe after handling button clicks. **Example: custom UI with focus restoration** ```html theme={"dark"} ``` Ensure the iframe regains focus after users interact with external buttons or controls. Use `iframe.focus()` after handling button clicks. ## Next steps Send and receive messages between your frontend and the embedded stream. Control resolution, audio, and session state from your frontend. # Frontend and application communication Source: https://docs.streampixel.io/resources/iframe-integration/frontend-and-application-communication Send and receive messages between your frontend, the Streampixel iframe, and the Unreal Engine application using postMessage. ## Overview Streampixel enables seamless two-way communication between your custom frontend, the iframe, and the Unreal Engine application running inside it. Using JavaScript methods like `postMessage` and `addEventListener`, you can: 1. **Receive messages**: * Listen for updates from the Unreal Engine application or iframe (e.g., stream states, custom events). 2. **Send commands**: * Send commands to the Unreal Engine application or iframe for controlling audio, resolution, session state, or custom features. This messaging framework is essential for integrating custom UIs, managing stream controls, and building tailored interactions. *** ## Listening for messages To capture messages sent from the iframe or Unreal Engine application, use the `window.addEventListener` method. **Example: listening for messages** ```javascript theme={"dark"} window.addEventListener('message', (event) => { // Validate the message source for security if (event.origin === 'https://share.streampixel.io') { console.log('Message received:', event.data); // Example: Handle stream-state updates if (event.data.type === 'stream-state') { console.log('Stream state:', event.data.value); // e.g., 'loadingComplete' } } }); ``` *** ## Sending messages To interact with the iframe or Unreal Engine application, send messages using the `postMessage` method. This allows you to control the stream or trigger specific Unreal Engine events. **Example: sending commands** ```javascript theme={"dark"} const sendMessageToIframe = (message) => { const iframe = document.getElementById('streamPixelPlayer'); if (iframe) { iframe.contentWindow.postMessage(message, 'https://share.streampixel.io'); } }; // Example: Send the muteAudio command sendMessageToIframe({ message: 'muteAudio' }); ``` *** ## Configuring Unreal to send and receive messages The frontend code above only handles the browser side. Unreal also needs Blueprint setup to actually receive what your page sends and to send messages back. ### Receiving from your page (UE side) Add a **Pixel Streaming Input** component to your `PlayerController`, bind to its `OnInputEvent` delegate, and parse the JSON `Descriptor` string with **Get JSON String Value**. Unreal Engine blueprint receiving Pixel Streaming JSON messages ### Sending from UE to your page Use the **Send Pixel Streaming Response** node. The string in the **Descriptor** field is delivered to your page as the `event.data` payload of a `message` event. Send Pixel Streaming Response node in an Unreal blueprint For the full Blueprint walkthrough (every node, in order), see [Unreal-side messaging](/resources/json-message-communication/unreal-side-sending-and-receiving-json). *** ## Security best practices 1. **Validate incoming messages**: * Always validate `event.origin` to ensure the message is coming from the StreamPixel iframe: ```javascript theme={"dark"} if (event.origin === 'https://share.streampixel.io') { // Safe to process the message } ``` 2. **Restrict outgoing messages**: * Replace `'*'` with the exact origin (`https://share.streampixel.io`) when sending messages via `postMessage` to enhance security. ## Next steps Listen for real-time stream state updates and metadata. Reference of all commands you can send to the iframe. # Overview Source: https://docs.streampixel.io/resources/iframe-integration/overview Embed a Streampixel stream into your site with a single iframe. The fastest way to embed a Streampixel stream — drop in an iframe, configure a few attributes, and use `postMessage` to drive the stream from your frontend. No SDK required. ## Pages in this section | | What it covers | | - | - | | [Basic embed](/resources/iframe-integration/basic-iframe-integration) | The minimum iframe markup, autoplay, fullscreen, and input handling. | | [Stream states](/resources/iframe-integration/stream-states) | Lifecycle events (`authenticating`, `loadingComplete`, etc.) for driving your UI. | | [Stream control commands](/resources/iframe-integration/stream-control-commands) | Mute, change resolution, take a screenshot — programmatic stream control. | | [Frontend & app communication](/resources/iframe-integration/frontend-and-application-communication) | Send and receive messages between your page and the iframe via `postMessage`. | | [Sample code (HTML + React)](/resources/iframe-integration/sample-code-for-html-and-react) | Drop-in examples. | ## Iframe vs. Web SDK Use the **iframe** to embed quickly with minimal code, or when your frontend isn't JavaScript-based. Use the [Web SDK](/resources/web-sdk/getting-started/overview) when you need fine-grained control — custom UI, voice chat, programmatic events, or full lifecycle hooks. # Sample code for HTML and React Source: https://docs.streampixel.io/resources/iframe-integration/sample-code-for-html-and-react Copy-paste HTML/JavaScript and React examples that embed the Streampixel iframe with a loading screen and stream controls. These end-to-end samples show how to embed the Streampixel iframe with a fullscreen loading screen and standard control buttons (mute, unmute, terminate, send custom messages). Use them as a starting point for your own integration. ## 1. HTML with JavaScript example ```html theme={"dark"} StreamPixel Full-Screen Stream with Loading Screen
Loading...
``` *** ## 2. React example ```javascript theme={"dark"} import React, { useEffect, useRef, useState } from 'react'; const StreamPixel = () => { const iframeRef = useRef(null); // Reference to the iframe const [loading, setLoading] = useState(true); // State to manage the loading screen // Function to send a test message to the iframe const sendCommand = () => { const message = { message: 'test' }; if (iframeRef.current) { iframeRef.current.contentWindow.postMessage(message, '*'); } }; // Function to mute audio const muteAudio = () => { const message = { message: 'muteAudio' }; if (iframeRef.current) { iframeRef.current.contentWindow.postMessage(message, '*'); } }; // Function to unmute audio const unMuteAudio = () => { const message = { message: 'unMuteAudio' }; if (iframeRef.current) { iframeRef.current.contentWindow.postMessage(message, '*'); } }; // Function to terminate the session const terminateSession = () => { const message = { message: 'terminateSession' }; if (iframeRef.current) { iframeRef.current.contentWindow.postMessage(message, '*'); } }; // Listen for messages from the iframe useEffect(() => { const handleMessage = (event) => { console.log('Message received:', event.data); if (event.data === 'loadingComplete') { // Remove the loading screen when loading is complete setLoading(false); console.log('Stream loading complete'); } }; window.addEventListener('message', handleMessage); // Cleanup the event listener on unmount return () => { window.removeEventListener('message', handleMessage); }; }, []); // Fallback: Remove the loading screen if iframe takes too long to respond useEffect(() => { const timeout = setTimeout(() => { if (loading) { setLoading(false); console.warn('Fallback: Loading screen removed due to timeout.'); } }, 15000); // 15 seconds timeout return () => clearTimeout(timeout); // Clear timeout on unmount }, [loading]); return (
{/* Loading Screen */} {loading && (
Loading...
)} {/* Button Controls */}
{/* Fullscreen Iframe */}
); }; // Button styling const buttonStyle = { margin: '0 10px', padding: '10px 20px', fontSize: '14px', cursor: 'pointer', border: 'none', backgroundColor: '#007bff', color: 'white', borderRadius: '5px', transition: 'background-color 0.3s', }; export default StreamPixel; ``` ## Next steps Reference of every command you can wire to your buttons. Learn the postMessage patterns used in these samples. # Stream control commands Source: https://docs.streampixel.io/resources/iframe-integration/stream-control-commands Manage your Streampixel stream dynamically with programmatic controls for resolution, audio, session, chat, cursor, screenshots, and custom messages. ## Overview Streampixel allows you to control and interact with the stream dynamically by sending predefined commands to the iframe using the `postMessage` API. These commands let you manage actions like muting, unmuting, disconnecting, adjusting the stream resolution and sending pixel streaming messages to the Unreal Engine application. *** ### How postMessage works The `postMessage` API enables communication between your frontend application and the Streampixel iframe. By sending specific commands to the iframe, you can control various aspects of the stream programmatically. *** ## Detailed command descriptions Here's a breakdown of the commands and what they do: **1. Set stream resolution** * **Description**: Sends the desired resolution for the stream. The actual changes will depend on the **Re** * **Behavior**: * Sending a resolution higher than the **Max Stream Quality** will have no effect. * The resolution must be one of the supported values; unsupported resolutions will be ignored. * **Parameters**: * `value`: The desired resolution as a string (e.g., `720p (1280x720)`). * **Supported resolutions**: * `360p (640x360)` * `480p (854x480)` * `720p (1280x720)` * `1080p (1920x1080)` * `1440p (2560x1440)` * `4K (3840x2160)` * **Message example**: ```javascript theme={"dark"} { message: {value: '720p (1280x720)',type:"setResolution" }} ``` - If the requested resolution exceeds the **Max Stream Quality** set in your project settings, the resolution will not change. - Ensure the resolution matches one of the supported values to avoid issues. *** **2. Mute stream audio** * **Description**: Mutes the audio for the stream. * **Behavior**: * The video continues to play, but the audio is turned off for the user. * It won't change the mute/unmute UI within iframe. * **Message example**: ```javascript theme={"dark"} { message: 'muteAudio' } ``` *** **3. Unmute stream audio** * **Description**: Unmutes the audio for the stream. * **Behavior**: * Restores audio playback for the stream. * It won't change the mute/unmute UI within iframe. * **Message example**: ```javascript theme={"dark"} { message: 'unMuteAudio' } ``` Auto unmute may not work out of the box due to browser policies, such as those outlined by [Google's autoplay policies](https://developer.chrome.com/blog/autoplay). Additional configuration may be required to enable this functionality. *** **4. Terminate session** * **Description**: Ends the current session for the stream. * **Behavior**: * Stops the stream and disconnects the WebRTC connection to the server. * **Message example**: ```javascript theme={"dark"} { message : 'terminateSession' } ``` *** **5. Send heartbeat** * **Description**: Prevents the Unreal Engine application from timing out due to inactivity. * **Behavior**: * Keeps the session alive by sending periodic heartbeat messages to the server. * Recommended for applications where inactivity timeout isn't required. * **Message example**: ```javascript theme={"dark"} { message: 'heartbeat' } ``` *** **6. Custom messages to Unreal Engine app** * **Description**: Sends custom messages or data directly to the Unreal Engine application running in the stream. * **Behavior**: * Allows you to trigger specific logic or interactions in your Unreal Engine app. * You can send custom fields without the `message` key if required. * **Message example** ```javascript theme={"dark"} { customField: 'customValue' } ``` *** **7. Connect to chat** * **Description**: Starts the built-in voice and/or text chat system for the user. * **Behavior**: * Users are connected to the chat and assigned a name, avatar, and optionally a room based on the variables you send * If `roomId` is not provided, all users in the same project session are placed into a default shared room. * **Message example** ```javascript theme={"dark"} { "message": { "type": "comms", "value": { "name": "Alice", "pfpUrl": "https://example.com/avatar.jpg", "roomId": "room-123" } } } ``` Make sure that `Communication Startup Behavior` is set to `Start when triggered by App` in the communication tab present on the project details page. For more info check [Built-in voice and text chat](/resources/quick-start-guide/built-in-voice-and-text-chat). *** **8. Disconnect from chat** * **Description:** Terminates the active communication session for the user. * **Behavior:** * Immediately stops both voice and text chat. * Removes the user from the communication room and UI. * **Message example** ```json theme={"dark"} { "message": { "type": "comms", "value": "disconnect" } } ``` Make sure that `Communication Startup Behavior` is set to `Start when triggered by App` in the communication tab present on the project details page. For more info check [Built-in voice and text chat](/resources/quick-start-guide/built-in-voice-and-text-chat). *** **9. Toggle browser cursor visibility via iframe** * **Description:** Allows the parent webpage to programmatically toggle the visibility of the browser-based mouse cursor inside the embedded Pixel Streaming iframe. * **Behavior:** * Sends a command from the parent page to the iframe to hide or show the cursor. * Enables seamless switching between immersive gameplay (hidden cursor) and UI interaction (visible cursor). * **Message example** Turn on mouse visibility: ```javascript theme={"dark"} {"message": {"type": "togglehoveringmouse", "value": true}} ``` Turn off mouse visibility: ```javascript theme={"dark"} {"message": {"type": "togglehoveringmouse", "value": false}} ``` *** **10. Screenshot message** * **Description:** Allows the application to trigger a screenshot of the stream (without any frontend UI) and automatically download it to the user's device. * **Behavior:** * Sends a command from the parent page to the iframe to capture screenshot. * The frontend captures the current video frame and saves it as an image file locally. * **Message example** ```javascript theme={"dark"} {message : 'requestScreenshot'} ``` *** ## How to send commands Use the `postMessage` API to send these commands to the iframe. **Basic example** ```javascript theme={"dark"} // Reference the iframe const streamIframe = document.getElementById('streamIframe'); // Example Commands streamIframe.contentWindow.postMessage({ message: { value: '720p (1280x720)', type: 'setResolution' } }, '*'); // Set resolution streamIframe.contentWindow.postMessage({ message: 'muteAudio' }, '*'); // Mute the stream streamIframe.contentWindow.postMessage({ message: 'unMuteAudio' }, '*'); // Unmute the stream streamIframe.contentWindow.postMessage({ message: 'terminateSession' }, '*'); // Terminate the session streamIframe.contentWindow.postMessage({ message: 'heartbeat' }, '*'); // Send a heartbeat streamIframe.contentWindow.postMessage({ customField: 'customValue' }, '*'); // Send custom data ``` ## Next steps Listen for state updates triggered by your commands. Copy-paste examples that issue these commands. # Stream states Source: https://docs.streampixel.io/resources/iframe-integration/stream-states Intercept real-time stream state and metadata messages from the Streampixel iframe to power custom loading UIs and analytics. ## Overview Streampixel enables you to monitor the progress of the stream state process by sending real-time structured messages to your frontend. These messages contain detailed information about the current state of the stream, allowing you to design and implement custom loading UIs tailored to your needs. ### Stream state messages The messages are sent as structured objects with the following format: ```javascript theme={"dark"} { "type": "stream-state", "value": "state_value" } ``` **Fields:** * **type**: Identifies the type of the message. For stream state updates, this will always be `stream-state`. * **value**: Represents the current state of the stream. Possible values are: | Value | Description | | - | - | | `authenticating` | Request is sent to the backend to authenticate access to the stream. Progress: 10% | | `connecting` | Establishing a connection with the signaling server and finding an available compute resource. Progress: 20% | | `finalising` | Loading WebRTC components. Progress: 80% | | `loadingComplete` | The stream is ready to be played. Progress: 100% | | `reconnecting` | Attempting to restore the stream after a temporary network interruption. | | `disconnected` | Disconnected due to inactivity, project crash, or another reason. | | `restricted` | Access is restricted due to project inactivity, domain blacklisting, or user permissions. | | `afkWarning` | System detected inactivity. The stream will automatically close if no activity is detected within 10 seconds. | | `afkAbort` | This status occurs when a user successfully interacts with the stream during an AFK warning period, effectively cancelling the automatic closure of the stream. It confirms that the user is still active, preventing the session from ending prematurely. | | `queue-1` | Indicates the user is currently queued for access to the stream due to full capacity. The number following "queue-" shows the user's position in the queue, which dynamically changes as the queue progresses. | | `showPassword` | This message is sent from the iframe when the stream is password protected and a password input field needs to be shown. It indicates that the host page should remove or reposition any loading overlays that might block the password textbox, allowing the user to see and interact with it. | | `hidePassword` | This message is sent once the user has entered a valid password and it has been authenticated. It signals the host page to hide the password UI and optionally restore any loading overlays or transition to the next step in the user flow. | ### How to intercept stream state messages You can use the `window.addEventListener` method to listen for `message` events sent by the Streampixel iframe. Here's an example: ```javascript theme={"dark"} window.addEventListener('message', (event) => { if (event.origin === 'https://share.streampixel.io' && event.data.type === 'stream-state') { console.log('Stream State:', event.data.value); // Logs the current stream state // Add your custom logic here to handle the stream state (e.g., update UI) } }); ``` *** ## Stream metadata In addition to state updates, the iframe sends a one-time **`stream-metadata`** message at the start of every session. This message contains the identifiers that uniquely describe the running stream — the same `sessionId` and `streamerId` you can see for that session in your StreamPixel **Analytics dashboard**. ### Message format ```javascript theme={"dark"} { "type": "stream-metadata", "sessionId": "abc123-session-uuid", "streamerId": "xyz789-streamer-uuid" } ``` **Fields:** * **type**: Always `stream-metadata`. * **sessionId**: A unique identifier for the current streaming session. Each new session (page load / reconnect to a fresh streamer) generates a new value. * **streamerId**: The identifier of the underlying streamer (compute instance) serving this session. ### Why this is useful Because these are the exact same IDs surfaced in the StreamPixel Analytics dashboard, capturing them on your frontend lets you **link a stream session to your own user**. For example, when a logged-in user starts a stream you can store a mapping of `{ yourUserId → sessionId, streamerId }` in your own database. Later, when you look at session analytics in the StreamPixel dashboard, you can correlate any session back to the user who watched it — useful for support, billing, per-user usage reporting, or feeding StreamPixel session data into your own analytics pipeline. ### How to intercept stream metadata ```javascript theme={"dark"} window.addEventListener('message', (event) => { if (event.origin !== 'https://share.streampixel.io') return; if (event.data.type === 'stream-metadata') { const { sessionId, streamerId } = event.data; console.log('Session started:', sessionId, streamerId); // Example: associate this session with the currently signed-in user // saveSessionForUser(currentUser.id, { sessionId, streamerId }); } }); ``` > The `stream-metadata` message is sent once, immediately after the iframe finishes loading and a session is assigned. Make sure your `message` listener is attached **before** the iframe loads so you don't miss it. ## Next steps Send commands to control resolution, audio, and the session. Full working examples that consume stream-state messages. # Frontend side - sending and receiving JSON Source: https://docs.streampixel.io/resources/json-message-communication/frontend-side-sending-and-receiving-json Send JSON messages from your frontend into Unreal via URL or the WebSDK/Iframe API, and listen for messages sent back from Unreal. ## Overview The frontend communicates with Unreal via the **Pixel Streaming messaging system**. * **Sending → Unreal**: You can send json message to your unreal engine application. * **Receiving ← Unreal**: You can listen for responses and messages coming from the Unreal side. There are two ways to send JSON from the frontend: 1. **Via URL** – pass a JSON payload in the launch URL (one-time, no frontend code required). * Sometimes you only need to send a message once, at the very start of the application, and not for the rest of the session. * To avoid building a custom frontend just for this, Streampixel lets you include a JSON message directly in the stream's **URL**. * This is useful for one-time configuration, like setting the default map, graphics quality, or toggling UI elements when the session launches. 2. **Via WebSDK / Iframe** – send JSON interactively at any point after the stream has launched. * If you need ongoing control — sending messages while the app is running — use our WebSDK or Iframe API. * This method supports unlimited, interactive communication. You can send commands in response to button clicks, toggle HUD elements, update camera views, or push live analytics at any point during the session. *** ## Sending messages to Unreal ### Option A: Via URL If you only need to send a message **once at the start of the application**, you don't need to create a custom frontend. You can simply include the JSON data as **parameters in your stream URL**. ``` ### Recommended `allow` attributes | Permission | Why | | - | - | | `autoplay` | Let the stream start without a user click | | `fullscreen` | Enable the fullscreen control | | `microphone` | Required if you use voice chat | | `camera` | Required if you use video chat | | `xr-spatial-tracking` | Required for WebXR / VR sessions | ## Option 3: Custom frontend with the Web SDK If you want full control — custom UI, programmatic events, voice chat hooks — use the Web SDK instead of an iframe. Build a custom player with full control over events and UI. Iframe-specific guides: postMessage, stream states, control commands. ## Embedding tips Wrap the iframe in a 16:9 container and set the iframe to fill it: ```html theme={"dark"}
```
In the project's [security settings](/resources/quick-start-guide/security-control), whitelist the domains that are allowed to embed your stream. Browsers will refuse to load the iframe on other origins. Use [postMessage](/resources/iframe-integration/frontend-and-application-communication) to send commands and listen for events from the iframe. Add a [custom domain](/resources/dashboard/custom-domains) to serve the stream from your own URL.
## Next steps postMessage, lifecycle events, control commands. Serve your stream from `play.yourcompany.com`. Gate access with passwords or domain whitelisting. Drop-in code for vanilla HTML and React projects. # SSO access Source: https://docs.streampixel.io/resources/quick-start-guide/sso-access Restrict a stream to people who can sign in with your organisation's own identity provider. SSO access puts your own login in front of a stream. Viewers are sent to your identity provider, sign in there, and come back to the stream only if they succeed. Streampixel never sees their password; it only receives the signed identity your provider issues. Use it when a stream is for your staff, your customers, or a partner organisation, and a shared password is not good enough. SSO works with any provider that supports **OpenID Connect**: Microsoft Entra ID (Azure AD), Okta, Auth0, Google Workspace, Keycloak and others. It is an access control for *viewers of a stream*. It is separate from how you sign in to the dashboard. ## How it fits with the other access controls A project has one access mode. **Password** and **SSO** are alternatives: turning one on turns the other off. **Domain restrictions** work alongside either, and SSO requires them, see below. | | Public | Password | SSO | | - | - | - | - | | Who can watch | Anyone with the link | Anyone with the password | Anyone who can sign in with your provider, optionally narrowed by domain, group or person | | What the viewer does | Nothing | Types a password | Signs in with the account they already have | | Where it is set | Project Settings | Project Settings | Settings → SSO, then Project Settings | ## Step 1: connect your identity provider This is done once per account, under **Settings → SSO**. A connection can then be used by any number of projects. 1. In your identity provider, create an **OpenID Connect web application** and register this redirect URL exactly: ```text theme={"dark"} https://platform.streampixel.io/api/v1/stream/sso/callback ``` 2. In Streampixel, open **Settings → SSO** and click **Add connection**. Fill in: | Field | What to enter | | - | - | | **Name** | Anything you like. Viewers see it on the sign-in button. | | **Issuer** | Your provider's issuer URL, for example `https://login.microsoftonline.com//v2.0` or `https://.okta.com`. It must be public `https` and match what the provider's discovery document reports. | | **Client ID** | From the application you created. | | **Client secret** | From the same application. It is stored encrypted and never shown again; to change it, enter a new one. | | **Scopes** | Leave the default `openid profile email` unless your provider needs more. | | **Groups claim** | The token claim that carries group membership. `groups` for most providers. Only matters if you restrict by group. | 3. Save. The connection appears in the list with its issuer. ## Step 2: turn it on for a project 1. Open the project and go to **Project Settings**. 2. Under **Domain restrictions**, make sure at least one allowed domain is listed. SSO delivers a one-time sign-in grant to the page that embeds the stream, and that page must be one you trust. The dashboard refuses to enable SSO on a project with an empty list, with the message *Add at least one allowed domain to the allowlist before enabling SSO*. 3. In the **SSO Access** card, choose your connection from the **Identity provider** dropdown. *Off — no SSO* switches it off again. 4. **Save**. With just these steps, **any user who can sign in with that provider may view the stream**, and Password protection is off for the project. ## What viewers see 1. The stream page shows a sign-in button instead of the stream. 2. They are sent to your provider. If they are already signed in there, this can be instant. 3. They land back on the stream and it loads. If they are refused, they see *Your account does not have access to this stream.* A sign-in that took too long shows *That sign-in link expired. Please try again.* Both are safe to retry. The same gate applies to the private preview link in [Test Locally](/resources/dashboard/test-locally), so you can check the experience before sharing anything. ## Narrowing who gets in These rules are part of the project's SSO policy and are applied on every sign-in. They are not yet editable in the dashboard; ask support to set them for a project, or set them through the API. Three lists can be attached to a project. All three are optional. | List | Rule | | - | - | | **Allowed email domains** | The signed-in user's email address must end in one of these domains, for example `example.com`. | | **Allowed groups** | The user's token must carry at least one of these group values in the groups claim configured on the connection. | | **Allowed users** | Specific users, by email address or by the provider's subject id. | How they combine: * A user on the **allowed users** list is always admitted, whatever the other lists say. * If neither domains nor groups are set, signing in is the only requirement. If *only* an allowed-users list is set, everyone else is refused. * If domains are set, the address must match one of them. A provider that explicitly marks an address as unverified is refused. Providers that simply do not report verification, which includes Microsoft Entra, are accepted; enable **Require verified email** on the connection if you want the strict behaviour. * If groups are set, the user must be in at least one. ## Troubleshooting | Symptom | Cause | | - | - | | Provider shows a redirect URI error | The callback URL was not registered on the application, or was registered with a typo. It must match character for character. | | Sign-in fails immediately after returning from the provider | The client secret is wrong. A common slip is pasting the client ID into the secret field. | | *Your account does not have access to this stream* for everyone | A domain or group rule that nobody matches. Check the groups claim name on the connection against what the provider actually emits. | | Entra users refused by a group rule despite being in the group | Entra stops including groups in the token for users in more than about 150 groups. Use a domain rule or an allowed-users list for those users. | | Cannot enable SSO on the project | The domain allow-list is empty. Add the domains that embed the stream first. | | Viewers on an embedded page see the stream page reload with no sign-in | The embedding page's domain is not on the allow-list, so the grant is not delivered there. Add it. | # Upload your build Source: https://docs.streampixel.io/resources/quick-start-guide/uploading-your-build Upload your packaged Unreal `.zip` to Streampixel. This page covers the upload itself. For ongoing build management — auto-release, rollback, tagging, deleting — see [Builds](/resources/dashboard/builds). Prefer not to package locally at all? Connect your GitHub repository or Perforce depot and Streampixel compiles your project in the cloud — see [Cloud builds](/resources/cloud-builds/overview). ## Requirements | What | Value | | - | - | | Format | `.zip` | | Platform | Windows package (also for VR experiences — see [Prepare a VR experience](/resources/quick-start-guide/prepare-your-unreal-engine-project-for-vr) for the in-engine setup) | | Max size | 24 GB *(soft limit — see below)* | | Contents | Packaged project — not the source `.uproject` files | **The 24 GB cap is a soft limit**, set to discourage accidental abuse — not a hard ceiling. If your build is genuinely larger, raise a support ticket from the [dashboard](https://dashboard.streampixel.io) → **Support** with your project ID and the size you need. We'll raise the limit at no extra cost. There's no hard upper bound on build size. ## Upload from the dashboard From the project page. Drag your `.zip` in or paste a public download URL. Track progress in the Builds list. The build moves through several states before going live — see [Build statuses](/resources/dashboard/builds#build-statuses) for what each one means. Build Details Only one build uploads at a time per project. Wait for the current upload to finish. ## Auto-release at upload time By default, your build goes live automatically once it passes processing. Disable **Auto-release** at upload time if you want a staging gate before the build replaces the current live one. Full details in [Builds → Auto-release](/resources/dashboard/builds#auto-release). ## Next Auto-release, rollback, tag, copy, and delete builds. Get your share link and embed it. What each state means in detail and how to handle rejections. # CI/CD: ship a build on every commit Source: https://docs.streampixel.io/resources/recipes/ci-cd-pipeline Automate build delivery to Streampixel — from your own CI/CD pipeline via the Upload File API, or with cloud builds. There are two ways to ship builds to Streampixel automatically: 1. **[Cloud builds](/resources/cloud-builds/overview)** — connect your GitHub repository or Perforce depot and Streampixel compiles, packages, and (optionally) deploys on every push. No build machine, no scripts. If your pipeline exists only to package and upload, use this and skip the rest of this page. 2. **Your own CI/CD** — you keep compiling on your existing pipeline (Jenkins, GitLab CI, TeamCity, Buildkite — anything that can run Unreal's packaging step), host the packaged `.zip` at a direct-download URL, and hand that URL to the [Upload File API](/resources/api-reference/upload-file-api). This recipe covers the second path. The outcome: every commit packages your Unreal project, registers the build with Streampixel, waits for the `build.approved` webhook, then distributes it to streaming servers. No manual dashboard steps. ## Architecture ```mermaid theme={"dark"} flowchart LR A[Commit] --> B[Your CI pipeline] B --> C[Package UE build] C --> D[Host .zip at a direct-download URL] D --> E[POST /projects/upload-file] E --> F[Streampixel build pipeline] F --> G[Webhook: build.approved] G --> H[POST /projects/distribute-file] ``` The CI job is fire-and-forget. Approval and distribution are handled out-of-band by a tiny webhook listener — that decoupling is what lets the build pipeline take 5 minutes or 50 minutes without holding a CI worker open. ## Prerequisites | Requirement | Notes | | - | - | | Streampixel project | Created in the [dashboard](https://dashboard.streampixel.io). Note the `projectId`. | | API key + user ID | From [API authentication](/resources/api-reference/api-authentication) and [Finding your IDs](/resources/api-reference/finding-your-user-and-project-ids). | | A CI/CD tool | Any pipeline that can run Unreal's `RunUAT` packaging step — typically on a Windows worker with the engine installed. | | File storage | Anywhere that serves direct-download HTTPS URLs: S3, GCS, R2, DigitalOcean Spaces, your own server. See [file URL requirements](/resources/api-reference/upload-file-api). | | Webhook host | A public HTTPS endpoint to receive `build.approved`. ngrok works for development. | Keep your API key, user ID, and storage credentials in your CI tool's secret store — never commit them to the repo, and never echo them in pipeline logs. ## Step 1 — Package the build in your pipeline The packaging step is the same command you'd run locally, scripted on your CI worker: ```batch theme={"dark"} "%UE_ROOT%\Engine\Build\BatchFiles\RunUAT.bat" ^ BuildCookRun ^ -project="%WORKSPACE%\MyProject.uproject" ^ -platform=Win64 ^ -clientconfig=Shipping ^ -cook -build -stage -package -archive ^ -archivedirectory="%WORKSPACE%\Build" ^ -pak -nodebuginfo -utf8output ``` Then zip the staged output: ```powershell theme={"dark"} Compress-Archive ` -Path "$env:WORKSPACE\Build\WindowsNoEditor\*" ` -DestinationPath "$env:WORKSPACE\$env:COMMIT_SHA.zip" ` -CompressionLevel Optimal ``` Naming the archive after the commit SHA makes any build traceable back to its exact source revision. ## Step 2 — Host the `.zip` at a direct-download URL Upload the archive to your storage and produce an HTTPS URL that returns the file directly — no login page, no HTML wrapper. With S3-compatible storage, a time-limited presigned URL is ideal: ```bash theme={"dark"} aws s3 cp "$COMMIT_SHA.zip" "s3://$BUCKET/builds/$COMMIT_SHA.zip" --no-progress FILE_URL=$(aws s3 presign "s3://$BUCKET/builds/$COMMIT_SHA.zip" --expires-in 3600) ``` One hour of validity is long enough for Streampixel to download a large build, short enough that the URL is useless if it leaks. ## Step 3 — Register the build with Streampixel The last CI step hands the URL to the Upload File API: ```bash theme={"dark"} RESPONSE=$(curl -sS -X POST \ https://platform.streampixel.io/api/v1/projects/upload-file \ -H "Content-Type: application/json" \ -d @- < { // Respond fast — Streampixel times out after 10s and does not retry. res.status(200).end(); const { event, data } = req.body || {}; if (!event || !data) return; // Defense-in-depth: confirm this event is for our project. if (data.projectId && data.projectId !== STREAMPIXEL_PROJECT_ID) { console.warn('Ignoring event for unknown project:', data.projectId); return; } console.log(`[${event}]`, data.uploadId || ''); if (event !== 'build.approved') return; if (distributed.has(data.uploadId)) return; distributed.add(data.uploadId); try { const resp = await axios.post( 'https://platform.streampixel.io/api/v1/projects/distribute-file', { apiKey: STREAMPIXEL_API_KEY, userId: STREAMPIXEL_USER_ID, projectId: STREAMPIXEL_PROJECT_ID, uploadId: data.uploadId, }, ); console.log('Distribute OK:', resp.data); } catch (err) { // Common cause: rate limit (1 call / 2 min / user). Back off and retry. distributed.delete(data.uploadId); console.error('Distribute failed:', err.response?.data || err.message); } }); app.get('/healthz', (_req, res) => res.status(200).send('ok')); app.listen(8080, () => console.log('listening on :8080')); ``` Run it: ```bash theme={"dark"} STREAMPIXEL_API_KEY=... \ STREAMPIXEL_USER_ID=... \ STREAMPIXEL_PROJECT_ID=... \ WEBHOOK_PATH=/webhooks/streampixel/9f3a4c1ee21b4f2c \ node webhook-listener.js ``` Then register `https://your-host.example.com/webhooks/streampixel/9f3a4c1ee21b4f2c` as your project's webhook URL in the dashboard. ## Handling rate limits The distribute endpoint is rate-limited to **one call per two minutes per user**. The listener above handles this naively — if you ever ship two builds within two minutes (rare in production, common during testing), the second one will fail and stay un-distributed. A robust queue: ```javascript theme={"dark"} const distributeQueue = []; let lastDistributeAt = 0; const COOLDOWN_MS = 2 * 60 * 1000 + 5_000; // 2 min + 5s safety setInterval(async () => { if (distributeQueue.length === 0) return; if (Date.now() - lastDistributeAt < COOLDOWN_MS) return; const uploadId = distributeQueue.shift(); lastDistributeAt = Date.now(); try { await axios.post( 'https://platform.streampixel.io/api/v1/projects/distribute-file', { apiKey: STREAMPIXEL_API_KEY, userId: STREAMPIXEL_USER_ID, projectId: STREAMPIXEL_PROJECT_ID, uploadId, }, ); } catch (err) { // Put it back at the front and try again next tick. distributeQueue.unshift(uploadId); lastDistributeAt = 0; } }, 5_000); ``` Push `uploadId` onto `distributeQueue` from the webhook handler instead of calling distribute inline. ## Tips **Tag stored builds with the commit SHA.** If a build misbehaves in production, the object name or tag identifies the exact source revision without scanning logs. **Use a single Slack channel as your "build feed."** Wire each webhook event into a Slack incoming webhook. The signal — "build approved 12 minutes after commit" — is the fastest way to spot regressions in package size or upload throughput. **Keep `autoRelease: false` even if you don't gate distribution on anything.** It costs nothing, lets you intercept bad builds, and means manual dashboard distribution still works as an emergency rollback path. ## Gotchas Google Drive share links, Dropbox preview pages, and any URL that returns HTML before the file will fail upload. The endpoint streams the response body as a ZIP and gives up if the first bytes are not a valid archive header. Presigned URLs and public bucket URLs work; HTML wrappers do not. See [file URL requirements](/resources/api-reference/upload-file-api). During development, use ngrok or Cloudflare Tunnel: ```bash theme={"dark"} ngrok http 8080 ``` Register the `https://...ngrok.app/webhooks/streampixel/` URL in the dashboard. Keep the tunnel up while you test — Streampixel does not retry failed deliveries. Streampixel does not currently sign webhook payloads. Treat your webhook URL as a shared secret: include a long random token in the path, and reject requests at any other path. See [Security hardening](/resources/recipes/security-hardening) for more. `build.approved` should fire once per upload, but make your handler idempotent anyway. The `distributed` Set above is fine for a single-process listener; use a database or Redis if you run multiple replicas. The default build cap is **24 GB** (a soft limit — [open a ticket](https://dashboard.streampixel.io) to raise it for free if your build is genuinely larger). A 1-hour presigned URL is usually plenty, but if you ship very large builds while ingestion is queued, bump it to 6 hours. ## Next steps The managed alternative — no pipeline to maintain. Full request/response reference for the upload endpoint. All seven webhook events and their payloads. Push an approved build to streaming servers. # Mobile streaming Source: https://docs.streampixel.io/resources/recipes/mobile-streaming Optimize your stream for iOS and Android browsers, including touch input and bandwidth constraints. The outcome: a clean stream that loads fast on cellular, responds to touch, doesn't bake the device, and survives the iOS Safari quirks that break naive integrations. Streampixel works on mobile via the device browser — Safari on iOS and Chrome on Android. There is no native mobile SDK; everything is the same Web SDK with a different config profile. ## Step 1 — Detect mobile, don't guess User-agent sniffing is fragile, but for switching between desktop and mobile config it's good enough: ```javascript theme={"dark"} const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); const isIOS = /iPhone|iPad|iPod/i.test(navigator.userAgent); ``` Use this to feed different values into the SDK — not to gate features. A user on a Pixel Tablet wants the desktop experience; a user on an iPhone Mini wants tighter bitrate caps. Detect, then choose. ## Step 2 — Mobile-tuned SDK init ```html theme={"dark"} Streampixel Mobile
Tap to start
``` What each option buys you: * **`touchInput: true`** — translate browser touch events into Pixel Streaming touch input so Unreal sees taps as taps, not as mouse clicks at (0, 0). * **`startResolutionMobile: '480p'`** — load fast. Adaptive bitrate negotiates upward as the connection allows. * **`preferredCodec: 'H264'`** — every mobile decoder has H264 silicon. VP9 software-decodes on most phones and burns CPU. * **`maxBitrate`** — cap the stream so an over-eager adaptive controller doesn't try to push 12 Mbps over LTE. * **`forceTurn: true`** — most carrier NATs block direct WebRTC; TURN relays just work. ## Step 3 — Touch UI in Unreal The browser-side `touchInput: true` flag delivers touch events to Unreal. Whether your Unreal project does anything useful with them depends on what touch UI is built into the project itself. For projects packaged with on-screen joysticks, on-screen buttons, or other touch HUD, see [Hide / unhide touch controls](/resources/additional-unreal-engine-features/hide-unhide-touch-controls) for runtime control of those overlays. Show them when `touchInput` is active and hide them on desktop. ## Step 4 — Bandwidth budget Plan for the network the user actually has, not the network you wish they had: | Connection | Realistic sustained bitrate | Recommended `maxBitrate` | | - | - | - | | LTE (good signal) | 10–20 Mbps | 4 Mbps | | LTE (weak / congested) | 1–3 Mbps | 2 Mbps | | 5G (sub-6) | 50–200 Mbps | 6 Mbps | | 5G (mmWave) | 500+ Mbps | 8 Mbps | | Hotel / cafe Wi-Fi | 5–20 Mbps shared | 4 Mbps | `maxBitrate` is in bits per second. The 4 Mbps cap above is `4_000_000`. Adaptive bitrate will scale down further on bad networks; it will not scale up beyond the cap. If your user base skews toward mobile data plans with hard caps, default to 2–3 Mbps and let users opt into higher quality from a settings menu. The bandwidth saved over a 30-minute session adds up. ## Step 5 — Battery and thermals A long mobile streaming session is a sustained workload: GPU decode, screen at full brightness, radio active. Phones throttle when they get hot, and a throttled phone produces a worse stream. Things you can do from your application: * **Set a sensible AFK timeout.** If the user puts the phone down for 30 seconds, Streampixel can disconnect them and free the worker. They can reconnect when they pick it back up. * **Drop FPS targets if your UE project supports it.** A console command via `emitConsoleCommand('t.MaxFPS 30')` halves render work and saves battery on long-form, low-motion content (configurators, tours). * **Honour `prefers-reduced-motion`** for any spinner / loader animations on your page. There's nothing the streaming side can do about a phone's thermal envelope. Cap session length where appropriate or design for short bursts. ## Step 6 — iOS Safari quirks iOS has the most edge cases. The big ones: ### Audio requires a user gesture iOS will not start playing audio until a tap, click, or other input event. The "Tap to start" overlay in the example above exists to satisfy that requirement. Without it, the stream connects, video plays muted, and audio never starts even after `audio.play()` is called. ```javascript theme={"dark"} document.getElementById('unmute').addEventListener('click', () => { UIControl.toggleAudio?.(); }, { once: true }); ``` ### Autoplay policies Even muted autoplay can fail in some Safari configurations, especially with PiP enabled. The Web SDK handles the muted autoplay attribute correctly, but if you wrap it in your own `