Test your stream and integrate with our API
Get your API key from the dashboard after adding a "Self Hosted" destination
Get your Permanent Key from the Dashboard -> Developer API Access
Goal: Embed your live stream on your website and track cost.
pk_...)
GET /api/public/stream/status
hlsUrl in your video player
That's it! See the full example below.
| 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 /api/public/stream/status
Returns the current live/offline status of your stream, HLS playback URL, and bandwidth usage information.
Provide your API key using one of these methods (in order of preference):
X-API-Key: YOUR_API_KEY
Authorization: Bearer YOUR_API_KEY
?apiKey=YOUR_API_KEY or ?key=YOUR_API_KEY
60 requests per minute per API key. Exceeding this limit will return a
429 Too Many Requests error.
{
"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
}
}
{
"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
}
}
isLive (boolean) - Whether
the stream is currently livehlsUrl (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 secondsmaxWindowSeconds - Maximum buffer window (3600 = 1 hour)
availableFrom - Timestamp when buffer startedstreamKeyLabel (string) -
Stream label
โ ๏ธ Deprecated. Use label instead. Will be removed in
v1.1.
startedAt (number|null) -
Unix timestamp when stream startedplaybackToken (string|null) -
Secure token for HLS preview (not needed for API)bandwidth (object) -
Bandwidth usage information for current hourusedGB - GB used this hourlimitGB - Free tier limit (10 GB/hour)remainingGB - GB remaining in free tieroverageGB - GB over limit (charged at 5 sats/GB)overageSats - Sats charged for overage this hourGET /api/public/streams/active/cost
Returns the total cost spent on currently active streams. Accessible via API Key.
{
"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
}
]
}
{
"hasActiveStream": false,
"totalCost": 0,
"streams": []
}
{
"error": "Invalid API key or destination disabled"
}
{
"error": "Rate limit exceeded. Maximum 60 requests per minute.",
"retryAfter": 30
}
{
"error": "API key required. Provide via X-API-Key header, Authorization: Bearer header, or ?apiKey= query parameter."
}
Programmatically create and control your streams. These endpoints require authentication with your user session token.
Requires a Bearer token in the Authorization header.
Authorization: Bearer YOUR_SESSION_TOKEN
GET /api/streamsList all your stream keys.
[
{
"id": 12,
"streamKey": "def19b4b...",
"label": "My Event Stream",
"status": "idle",
"createdAt": 1769644800000
}
]
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://..."
}
]
}
GET /api/streams/{id}/destinationsList destinations for a specific stream.
[
{
"id": 45,
"type": "rtmp",
"platform": "Twitch",
"enabled": true,
"url": "rtmp://..."
}
]
POST /api/streamsCreates a new programmable stream key.
{
"label": "My Event Stream"
}
{
"id": 12,
"streamKey": "def19b4b...",
"label": "My Event Stream",
"status": "idle",
"limits": {}
}
POST /api/streams/{id}/destinationsAdds a publishing destination to the stream.
{
"type": "rtmp",
"config": {
"url": "rtmp://live.twitch.tv/app/KEY"
}
}
{
"success": true,
"id": 45
}
POST /api/streams/{id}/limitsEnforce hard limits on duration or budget. Stream stops automatically when reached.
{
"maxSats": 1000,
"maxDurationMinutes": 60
}
{
"success": true
}
POST /api/streams/{id}/stopForcefully terminates the active streaming session.
{
"success": true,
"message": "Stop signal sent"
}
GET /api/wallet/balanceRetrieve your current wallet balance.
{
"balance": 1050,
"cached": false
}
Create a new stream, optionally with a list of initial destinations (Unified Creation).
{
"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.
{
"id": 12,
"streamKey": "8f7...a1",
"label": "My Awesome Stream",
"destinationsCount": 2
}
Manage stream destinations (RTMP, Twitch, Self-Hosted, Cloud Archive).
Delete a destination by its ID. Quick and simple when you only have the destination ID.
id: Destination ID{"success": true}
Delete a destination with explicit stream ownership validation. Safer for production use.
id: Stream Key IDdestId: Destination ID{"success": true}
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.
List all configured webhooks.
Register a new webhook endpoint.
{
"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
}
url (required): HTTPS endpoint to receive webhook eventsevents (required): Array of event names to subscribe tosecret (optional): Signing secret for HMAC signature verificationstream_key_id (optional): Specific stream key ID, or null/omit for all streamsstream.started: Triggered when a stream begins receiving datastream.ended: Triggered when a stream stops (includes duration/cost stats)recording.ready: Triggered when cloud archive upload completestest.ping: Triggered when testing a webhook from the dashboardRemove a webhook configuration.
curl -H "X-API-Key: YOUR_API_KEY" \
https://simulcast.me/api/public/stream/status
curl "https://simulcast.me/api/public/stream/status?apiKey=YOUR_API_KEY"
curl -H "X-API-Key: YOUR_API_KEY" \
https://simulcast.me/api/public/streams/active/cost
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);
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`);
});
}
}
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}")
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")
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));
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`);
}
}
<!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>
curl -X POST https://simulcast.me/api/streams \
-H "Authorization: Bearer YOUR_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{"label": "My Event Stream"}'
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"
}
}'
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
}'
curl -X POST https://simulcast.me/api/streams/12/stop \
-H "Authorization: Bearer YOUR_SESSION_TOKEN"
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');
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');
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);
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);
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")
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")
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)
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)
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();
You can play your HLS stream directly in VLC Media Player using the HLS URL from the API response.
Ctrl+N /
Cmd+N)
data.hlsUrl)vlc "https://simulcast.me/hls/YOUR_TOKEN/index.m3u8?apiKey=YOUR_API_KEY"
A WordPress plugin to easily embed your self-hosted Simulcast.me livestream using the API and HLS player.
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:
/wp-content/plugins/ directoryEmbed the player on any page or post using the shortcode:
[simulcast_player]
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