๐Ÿ“ก Self Hosted API

Test your stream and integrate with our API

๐Ÿงช Live Test

Get your API key from the dashboard after adding a "Self Hosted" destination

๐Ÿ› ๏ธ Management API Test

Get your Permanent Key from the Dashboard -> Developer API Access

๐Ÿ“บ HLS Stream Player

Check stream status first to load the HLS player.

๐Ÿ“š API Documentation

๐Ÿš€ Start Here (Most Common Use Case)

Goal: Embed your live stream on your website and track cost.

  1. 1. Add a Self Hosted destination in the dashboard
  2. 2. Copy your Public API Key (pk_...)
  3. 3. Poll GET /api/public/stream/status
  4. 4. Use hlsUrl in your video player

That's it! See the full example below.

๐Ÿ”‘ Authentication Reference

Key Type Prefix Used For
Public API Key pk_... Read-only stream status, playback, cost tracking
Permanent API Key sk_... Webhooks, developer tools, management API

๐Ÿ’ก Most developers only need a Public API Key to get started.

Get Stream Status

GET /api/public/stream/status

Returns the current live/offline status of your stream, HLS playback URL, and bandwidth usage information.

Authentication

Provide your API key using one of these methods (in order of preference):

1. Header (Recommended)
X-API-Key: YOUR_API_KEY
2. Authorization Header
Authorization: Bearer YOUR_API_KEY
3. Query String (Fallback)
?apiKey=YOUR_API_KEY or ?key=YOUR_API_KEY

Rate Limiting

60 requests per minute per API key. Exceeding this limit will return a 429 Too Many Requests error.

Response Format

When Stream is Live:

{
  "isLive": true,
  "hlsUrl": "https://simulcast.me/hls/abc123/index.m3u8?apiKey=...",
  "streamKeyLabel": "My Gaming Stream",
  "startedAt": 1234567890,
  "playbackToken": "abc123...",
  "dvr": {
    "enabled": true,
    "windowSeconds": 1800,
    "maxWindowSeconds": 3600,
    "availableFrom": 1234567890
  },
  "bandwidth": {
    "usedGB": "2.45",
    "limitGB": 10,
    "remainingGB": "7.55",
    "overageGB": "0.00",
    "overageSats": 0,
    "hourStart": 1234560000
  }
}

When Stream is Offline:

{
  "isLive": false,
  "hlsUrl": null,
  "streamKeyLabel": "My Gaming Stream",
  "startedAt": null,
  "playbackToken": null,
  "dvr": {
    "enabled": false,
    "windowSeconds": 0,
    "maxWindowSeconds": 3600,
    "availableFrom": null
  },
  "bandwidth": {
    "usedGB": "0.00",
    "limitGB": 10,
    "remainingGB": "10.00",
    "overageGB": "0.00",
    "overageSats": 0,
    "hourStart": 1234560000
  }
}

Response Fields:

isLive (boolean) - Whether the stream is currently live
hlsUrl (string|null) - HLS playback URL (includes API key in query string)
label (string) - Stream label (alias for streamKeyLabel)
dvr (object) - Rolling buffer (DVR-style rewind) information. DVR allows viewers to rewind a live stream up to a configurable window without creating permanent recordings.
enabled - Whether rolling buffer is available (only when live)
windowSeconds - Current buffer window in seconds
maxWindowSeconds - Maximum buffer window (3600 = 1 hour)
availableFrom - Timestamp when buffer started
โš ๏ธ DVR buffers are temporary and discarded when the stream ends unless a Cloud Archive destination is enabled.
streamKeyLabel (string) - Stream label โš ๏ธ Deprecated. Use label instead. Will be removed in v1.1.
startedAt (number|null) - Unix timestamp when stream started
playbackToken (string|null) - Secure token for HLS preview (not needed for API)
bandwidth (object) - Bandwidth usage information for current hour
usedGB - GB used this hour
limitGB - Free tier limit (10 GB/hour)
remainingGB - GB remaining in free tier
overageGB - GB over limit (charged at 5 sats/GB)
overageSats - Sats charged for overage this hour

Get Active Stream Cost (Public)

GET /api/public/streams/active/cost

Returns the total cost spent on currently active streams. Accessible via API Key.

Response (Active Stream):

{
  "hasActiveStream": true,
  "totalCost": 15,
  "streams": [
    {
      "sessionId": "ABC123",
      "streamKeyId": 3,
      "label": "My Stream",
      "streamKeyLabel": "My Stream",
      "startedAt": 1769644800000,
      "durationMinutes": 5,
      "durationSeconds": 30,
      "totalCharged": 15,
      "ratePerMinute": 3
    }
  ]
}

Response (No Active Stream):

{
  "hasActiveStream": false,
  "totalCost": 0,
  "streams": []
}
โœ… Public API: This endpoint is accessible via API Key for external integrations.

Error Responses

401 Unauthorized - Invalid API Key
{
  "error": "Invalid API key or destination disabled"
}
429 Too Many Requests - Rate Limit Exceeded
{
  "error": "Rate limit exceeded. Maximum 60 requests per minute.",
  "retryAfter": 30
}
400 Bad Request - Missing API Key
{
  "error": "API key required. Provide via X-API-Key header, Authorization: Bearer header, or ?apiKey= query parameter."
}

๐Ÿ› ๏ธ Advanced: Stream Management API

Programmatically create and control your streams. These endpoints require authentication with your user session token.

Authentication

Requires a Bearer token in the Authorization header.

Authorization: Bearer YOUR_SESSION_TOKEN

List Streams

GET /api/streams

List all your stream keys.

[
  {
    "id": 12,
    "streamKey": "def19b4b...",
    "label": "My Event Stream",
    "status": "idle",
    "createdAt": 1769644800000
  }
]

Get Stream

GET /api/streams/{id}

Get details for a specific stream.

{
  "id": 12,
  "streamKey": "def19b4b...",
  "label": "My Event Stream",
  "status": "active",
  "limits": { "maxSats": 1000 },
  "startedAt": 1769644800000,
  "createdAt": 1769640000000,
  "destinations": [
    {
      "id": 45,
      "type": "rtmp",
      "platform": "Twitch",
      "enabled": true,
      "url": "rtmp://..."
    }
  ]
}

List Destinations

GET /api/streams/{id}/destinations

List destinations for a specific stream.

[
  {
    "id": 45,
    "type": "rtmp",
    "platform": "Twitch",
    "enabled": true,
    "url": "rtmp://..."
  }
]

Create Stream

POST /api/streams

Creates a new programmable stream key.

Request Body:

{
  "label": "My Event Stream"
}

Response:

{
  "id": 12,
  "streamKey": "def19b4b...",
  "label": "My Event Stream",
  "status": "idle",
  "limits": {}
}

Add Destination

POST /api/streams/{id}/destinations

Adds a publishing destination to the stream.

Request Body:

{
  "type": "rtmp",
  "config": {
    "url": "rtmp://live.twitch.tv/app/KEY"
  }
}

Response:

{
  "success": true,
  "id": 45
}

Set Limits (Event Mode)

POST /api/streams/{id}/limits

Enforce hard limits on duration or budget. Stream stops automatically when reached.

Request Body:

{
  "maxSats": 1000,
  "maxDurationMinutes": 60
}

Response:

{
  "success": true
}

Stop Stream

POST /api/streams/{id}/stop

Forcefully terminates the active streaming session.

{
  "success": true,
  "message": "Stop signal sent"
}

Get Wallet Balance

GET /api/wallet/balance

Retrieve your current wallet balance.

{
  "balance": 1050,
  "cached": false
}

Streams (Unified Creation)

POST /api/streams

Create a new stream, optionally with a list of initial destinations (Unified Creation).

Request Body

{
  "label": "My Awesome Stream",
  "destinations": [
    {
      "type": "rtmp",
      "config": {
        "url": "rtmp://live.twitch.tv/app/live_...",
        "key": "" 
      }
    },
    { "type": "self-hosted" }
  ]
}

Supported Types: rtmp, twitch, self-hosted, cloud-archive.

Response

{
  "id": 12,
  "streamKey": "8f7...a1",
  "label": "My Awesome Stream",
  "destinationsCount": 2
}

Destinations

Manage stream destinations (RTMP, Twitch, Self-Hosted, Cloud Archive).

DELETE /api/destinations/:id

Delete a destination by its ID. Quick and simple when you only have the destination ID.

Parameters

Response

{"success": true}
DELETE /api/streams/:id/destinations/:destId Recommended

Delete a destination with explicit stream ownership validation. Safer for production use.

Parameters

Response

{"success": true}

Advanced: Webhooks

Configure webhooks to receive real-time events about your streams.

๐Ÿ”‘ Authentication: Webhook management requires a Permanent API Key (sk_...). Public API keys (pk_...) cannot manage webhooks.

GET /api/webhooks

List all configured webhooks.

POST /api/webhooks

Register a new webhook endpoint.

Request Body

{
  "url": "https://your-api.com/callback",
  "events": ["stream.started", "stream.ended", "recording.ready"],
  "secret": "your_signing_secret",
  "stream_key_id": 3  // Optional: null or omit for all streams
}

Parameters

Events Reference

DELETE /api/webhooks/:id

Remove a webhook configuration.

๐Ÿ’ป Code Examples

Public API Examples

cURL (Command Line)

Check Stream Status (header):
curl -H "X-API-Key: YOUR_API_KEY" \
  https://simulcast.me/api/public/stream/status
Check Stream Status (query string):
curl "https://simulcast.me/api/public/stream/status?apiKey=YOUR_API_KEY"
Get Active Stream Cost:
curl -H "X-API-Key: YOUR_API_KEY" \
  https://simulcast.me/api/public/streams/active/cost

JavaScript (Browser/Fetch)

Check Stream Status:
const API_KEY = 'YOUR_API_KEY';
const API_URL = 'https://simulcast.me/api/public/stream/status';

async function checkStream() {
  try {
    const response = await fetch(API_URL, {
      headers: { 'X-API-Key': API_KEY }
    });
    
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    
    const data = await response.json();
    
    if (data.isLive) {
      console.log('Stream is LIVE:', data.hlsUrl);
      // Use data.hlsUrl in your video player
    } else {
      console.log('Stream is OFFLINE');
    }
  } catch (error) {
    console.error('Error:', error);
  }
}

// Check every 5 seconds
checkStream();
setInterval(checkStream, 5000);
Get Active Stream Cost:
async function getActiveCost() {
  const response = await fetch('https://simulcast.me/api/public/streams/active/cost', {
    headers: { 'X-API-Key': API_KEY }
  });
  const data = await response.json();
  
  if (data.hasActiveStream) {
    console.log(`Total cost: ${data.totalCost} sats`);
    data.streams.forEach(stream => {
      console.log(`${stream.label}: ${stream.durationMinutes}m ${stream.durationSeconds}s`);
    });
  }
}

Python (requests)

Check Stream Status:
import requests

API_KEY = "YOUR_API_KEY"
API_URL = "https://simulcast.me/api/public/stream/status"

# Using header (recommended)
response = requests.get(
    API_URL,
    headers={"X-API-Key": API_KEY}
)

if response.status_code == 200:
    data = response.json()
    if data["isLive"]:
        print(f"Stream is LIVE: {data['hlsUrl']}")
        print(f"Bandwidth: {data['bandwidth']['usedGB']} GB used")
    else:
        print("Stream is OFFLINE")
else:
    print(f"Error: {response.status_code} - {response.text}")
Get Active Stream Cost:
response = requests.get(
    "https://simulcast.me/api/public/streams/active/cost",
    headers={"X-API-Key": API_KEY}
)

data = response.json()
if data["hasActiveStream"]:
    print(f"Total cost: {data['totalCost']} sats")
    for stream in data["streams"]:
        print(f"{stream['label']}: {stream['durationMinutes']}m {stream['durationSeconds']}s")

Node.js (fetch/axios)

Check Stream Status:
const API_KEY = 'YOUR_API_KEY';
const API_URL = 'https://simulcast.me/api/public/stream/status';

// Using fetch
async function checkStream() {
  try {
    const response = await fetch(API_URL, {
      headers: { 'X-API-Key': API_KEY }
    });
    const data = await response.json();
    
    if (data.isLive) {
      console.log('Stream is LIVE:', data.hlsUrl);
    } else {
      console.log('Stream is OFFLINE');
    }
  } catch (error) {
    console.error('Error:', error);
  }
}

// Or using axios
const axios = require('axios');
axios.get(API_URL, {
  headers: { 'X-API-Key': API_KEY }
})
.then(response => {
  const data = response.data;
  if (data.isLive) {
    console.log('Stream is LIVE:', data.hlsUrl);
  }
})
.catch(error => console.error('Error:', error));
Get Active Stream Cost:
async function getActiveCost() {
  const response = await fetch('https://simulcast.me/api/public/streams/active/cost', {
    headers: { 'X-API-Key': API_KEY }
  });
  const data = await response.json();
  
  if (data.hasActiveStream) {
    console.log(`Total: ${data.totalCost} sats`);
  }
}

HTML Integration Example

Complete dashboard with status, cost, and balance:
<!DOCTYPE html>
<html>
<head>
    <title>My Stream</title>
    <script src="https://vjs.zencdn.net/8.6.1/video.min.js"></script>
    <link href="https://vjs.zencdn.net/8.6.1/video-js.css" rel="stylesheet">
</head>
<body>
    <div id="status">Loading...</div>
    <div id="cost"></div>
    <video id="video" class="video-js" controls style="display:none;"></video>

    <script>
        const API_KEY = 'YOUR_API_KEY';
        const BASE_URL = 'https://simulcast.me/api/public';

        async function updateDashboard() {
            try {
                // Check stream status
                const streamRes = await fetch(`${BASE_URL}/stream/status`, {
                    headers: { 'X-API-Key': API_KEY }
                });
                const streamData = await streamRes.json();
                
                const statusDiv = document.getElementById('status');
                const video = document.getElementById('video');
                
                if (streamData.isLive) {
                    statusDiv.textContent = `๐Ÿ”ด LIVE: ${streamData.label}`;
                    videojs('video').src({ 
                        type: 'application/x-mpegURL', 
                        src: streamData.hlsUrl 
                    });
                    video.style.display = 'block';
                } else {
                    statusDiv.textContent = 'โšซ OFFLINE';
                    video.style.display = 'none';
                }
                
                // Get active cost
                const costRes = await fetch(`${BASE_URL}/streams/active/cost`, {
                    headers: { 'X-API-Key': API_KEY }
                });
                const costData = await costRes.json();
                
                document.getElementById('cost').textContent = 
                    costData.hasActiveStream 
                        ? `๐Ÿ’ฐ Current cost: ${costData.totalCost} sats`
                        : '๐Ÿ’ฐ No active streams';
                    
            } catch (error) {
                console.error('Error updating dashboard:', error);
            }
        }

        updateDashboard();
        setInterval(updateDashboard, 5000);
    </script>
</body>
</html>

Stream Management API Examples

cURL (Command Line)

Create Stream:
curl -X POST https://simulcast.me/api/streams \
  -H "Authorization: Bearer YOUR_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"label": "My Event Stream"}'
Add Destination:
curl -X POST https://simulcast.me/api/streams/12/destinations \
  -H "Authorization: Bearer YOUR_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "rtmp",
    "config": {
      "url": "rtmp://live.twitch.tv/app/YOUR_STREAM_KEY"
    }
  }'
Set Limits:
curl -X POST https://simulcast.me/api/streams/12/limits \
  -H "Authorization: Bearer YOUR_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "maxSats": 1000,
    "maxDurationMinutes": 60
  }'
Stop Stream:
curl -X POST https://simulcast.me/api/streams/12/stop \
  -H "Authorization: Bearer YOUR_SESSION_TOKEN"

JavaScript (Browser/Fetch)

Create Stream:
const SESSION_TOKEN = 'YOUR_SESSION_TOKEN';
const BASE_URL = 'https://simulcast.me/api';

async function createStream(label) {
  const response = await fetch(`${BASE_URL}/streams`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${SESSION_TOKEN}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ label })
  });
  
  const data = await response.json();
  console.log('Created stream:', data.id, data.streamKey);
  return data;
}

// Usage
createStream('My Event Stream');
Add Destination:
async function addDestination(streamId, type, url) {
  const response = await fetch(`${BASE_URL}/streams/${streamId}/destinations`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${SESSION_TOKEN}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      type: type,
      config: { url: url }
    })
  });
  
  const data = await response.json();
  console.log('Added destination:', data.id);
  return data;
}

// Usage
addDestination(12, 'rtmp', 'rtmp://live.twitch.tv/app/YOUR_KEY');
Set Limits:
async function setLimits(streamId, maxSats, maxDurationMinutes) {
  const response = await fetch(`${BASE_URL}/streams/${streamId}/limits`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${SESSION_TOKEN}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      maxSats,
      maxDurationMinutes
    })
  });
  
  const data = await response.json();
  console.log('Limits set:', data);
  return data;
}

// Usage
setLimits(12, 1000, 60);
Stop Stream:
async function stopStream(streamId) {
  const response = await fetch(`${BASE_URL}/streams/${streamId}/stop`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${SESSION_TOKEN}`
    }
  });
  
  const data = await response.json();
  console.log('Stream stopped:', data.message);
  return data;
}

// Usage
stopStream(12);

Python (requests)

Create Stream:
import requests

SESSION_TOKEN = "YOUR_SESSION_TOKEN"
BASE_URL = "https://simulcast.me/api"

def create_stream(label):
    response = requests.post(
        f"{BASE_URL}/streams",
        headers={
            "Authorization": f"Bearer {SESSION_TOKEN}",
            "Content-Type": "application/json"
        },
        json={"label": label}
    )
    
    data = response.json()
    print(f"Created stream: {data['id']}, Key: {data['streamKey']}")
    return data

# Usage
stream = create_stream("My Event Stream")
Add Destination:
def add_destination(stream_id, dest_type, url):
    response = requests.post(
        f"{BASE_URL}/streams/{stream_id}/destinations",
        headers={
            "Authorization": f"Bearer {SESSION_TOKEN}",
            "Content-Type": "application/json"
        },
        json={
            "type": dest_type,
            "config": {"url": url}
        }
    )
    
    data = response.json()
    print(f"Added destination: {data['id']}")
    return data

# Usage
add_destination(12, "rtmp", "rtmp://live.twitch.tv/app/YOUR_KEY")
Set Limits:
def set_limits(stream_id, max_sats, max_duration_minutes):
    response = requests.post(
        f"{BASE_URL}/streams/{stream_id}/limits",
        headers={
            "Authorization": f"Bearer {SESSION_TOKEN}",
            "Content-Type": "application/json"
        },
        json={
            "maxSats": max_sats,
            "maxDurationMinutes": max_duration_minutes
        }
    )
    
    data = response.json()
    print(f"Limits set: {data}")
    return data

# Usage
set_limits(12, 1000, 60)
Stop Stream:
def stop_stream(stream_id):
    response = requests.post(
        f"{BASE_URL}/streams/{stream_id}/stop",
        headers={
            "Authorization": f"Bearer {SESSION_TOKEN}"
        }
    )
    
    data = response.json()
    print(f"Stream stopped: {data['message']}")
    return data

# Usage
stop_stream(12)

Node.js (axios)

Complete Stream Management Example:
const axios = require('axios');

const SESSION_TOKEN = 'YOUR_SESSION_TOKEN';
const BASE_URL = 'https://simulcast.me/api';

const api = axios.create({
  baseURL: BASE_URL,
  headers: {
    'Authorization': `Bearer ${SESSION_TOKEN}`,
    'Content-Type': 'application/json'
  }
});

// Create stream
async function createStream(label) {
  const { data } = await api.post('/streams', { label });
  console.log('Created stream:', data.id);
  return data;
}

// Add destination
async function addDestination(streamId, type, url) {
  const { data } = await api.post(`/streams/${streamId}/destinations`, {
    type,
    config: { url }
  });
  console.log('Added destination:', data.id);
  return data;
}

// Set limits
async function setLimits(streamId, maxSats, maxDurationMinutes) {
  const { data } = await api.post(`/streams/${streamId}/limits`, {
    maxSats,
    maxDurationMinutes
  });
  console.log('Limits set');
  return data;
}

// Stop stream
async function stopStream(streamId) {
  const { data } = await api.post(`/streams/${streamId}/stop`);
  console.log('Stream stopped:', data.message);
  return data;
}

// Usage example
async function setupEventStream() {
  try {
    // Create a new stream
    const stream = await createStream('Conference Stream');
    
    // Add Twitch destination
    await addDestination(
      stream.id, 
      'rtmp', 
      'rtmp://live.twitch.tv/app/YOUR_KEY'
    );
    
    // Set budget and time limits
    await setLimits(stream.id, 1000, 120);
    
    console.log('Stream ready! Use this key:', stream.streamKey);
  } catch (error) {
    console.error('Error:', error.response?.data || error.message);
  }
}

setupEventStream();

VLC Media Player

You can play your HLS stream directly in VLC Media Player using the HLS URL from the API response.

Method 1: Open Network Stream
  1. Open VLC Media Player
  2. Go to Media โ†’ Open Network Stream (or press Ctrl+N / Cmd+N)
  3. Paste your HLS URL from the API response (data.hlsUrl)
  4. Click Play
Method 2: Command Line
vlc "https://simulcast.me/hls/YOUR_TOKEN/index.m3u8?apiKey=YOUR_API_KEY"
Note: VLC will automatically handle HLS playback and buffering. The stream URL includes your API key for authentication.

WordPress Plugin

A WordPress plugin to easily embed your self-hosted Simulcast.me livestream using the API and HLS player.

โœจ Features:
  • Live Status Indicator: Automatically polls your stream status every 5 seconds
  • Auto-Play: Automatically shows and loads the player when you go live
  • HLS Support: Uses Video.js for reliable HLS playback
  • Stream Tipping: Integrated support for viewer tips using WooCommerce
  • Secure: Proxies API requests through your WordPress backend to keep your API Key hidden
  • Customizable: Simple shortcode to place the player anywhere
๐Ÿ’ฐ Stream Tips:

To accept tips, simply install and activate WooCommerce. The plugin will automatically create a hidden "Stream Tip" product for you. Users can click "Support the Stream" on the player, select an amount, and checkout via your existing WooCommerce payment gateways.

Bitcoin Payment Options: To accept Bitcoin tips via Lightning Network, you can install one of these payment plugins:

๐Ÿ“ฆ Installation:
  1. Upload the plugin folder to the /wp-content/plugins/ directory
  2. Activate the plugin through the 'Plugins' menu in WordPress
  3. Keep the file structure intact
โš™๏ธ Configuration:
  1. Go to Settings > Simulcast.me Stream
  2. Enter your Simulcast Public API Key
  3. Use the "Show/Hide" button to verify your key
  4. Click Save Changes
๐Ÿ“ Usage:

Embed the player on any page or post using the shortcode:

[simulcast_player]

๐Ÿ’ฐ Pricing & Bandwidth

โœ… Included Free:
  • 3 sats/min for Self Hosted destination (same as other platforms)
  • FREE if it's your first active destination
  • 10 GB/hour bandwidth included
๐Ÿ’ธ Overage Charges:
  • 5 sats per GB for bandwidth over 10 GB/hour
  • Overage is calculated and billed per-minute (not hourly)
  • Bandwidth resets at the top of each hour
๐Ÿ’ก Example:

If you use 12 GB in one hour, you'll be charged:
โ€ข 3 sats/min for streaming (if not first destination)
โ€ข 2 GB ร— 5 sats/GB = 10 sats for bandwidth overage
Total: 3 sats/min + 10 sats/hour overage

๐Ÿ”— Quick Test URLs

Status API (small JSON response):
HLS Playback URL (consumes bandwidth):