Live room API
Chat, reactions and gift highlights beside a stream. Your server mints a pass for each viewer or moderator, posts gift highlights from your own giving system, and moderates.
This page is generated from the OpenAPI specification; the same text is inMarkdown for tools and AI assistants.
Get a live room pass
GET/v1/streams/{playback_id}/room
A pass into the stream’s live room (chat, reactions, highlights) for a
viewer or a moderator: a WebSocket URL and a token, valid six hours for
a viewer and four for a moderator. A viewer can join only while the
stream is live. Name a viewer you have signed in with X-Viewer-Name.
Path parameters
| Name | Type | Description |
|---|---|---|
| playback_idrequired | string | The stream’s |
Query parameters
| Name | Type | Description |
|---|---|---|
| role | string |
One of |
| viewer | string |
One of |
| access_code | string | The viewer’s access code for a private stream (or send |
| viewer_ip | string | The viewer’s IP address (or send |
Headers
| Name | Type | Description |
|---|---|---|
| X-Viewer-Name | string | The name of a viewer you have signed in, percent-encoded UTF-8, 1-40 characters. Shown in the room. Send it only for a viewer whose identity you have verified. |
| X-Viewer-Member | string |
One of |
| X-Access-Code | string | The viewer’s access code for a private stream. |
| X-Viewer-IP | string | The viewer’s IP address, so wrong access codes are limited per viewer. |
Response 200
The pass.
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_role |
|
| 400 | invalid_viewer |
|
| 400 | invalid_viewer_name |
|
| 400 | invalid_viewer_member |
|
| 403 | access_code_required | The stream is private and no access code was given. |
| 403 | access_code_invalid | That access code is not this stream’s. |
| 404 | not_found | No such stream in your organization. |
| 409 | not_live | A viewer can join only while the stream is live. |
| 429 | too_many_attempts | Too many wrong access codes from this viewer. |
| 503 | live_unconfigured | The live room is not available on this deployment. |
curl "https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/room?role=moderator" \ -H "Authorization: Bearer $CARDINAL_API_KEY"import os
import requests
response = requests.get( "https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/room", headers={ "Authorization": f"Bearer {os.environ['CARDINAL_API_KEY']}", }, params={ "role": "moderator", }, timeout=30,)print(response.status_code, response.text)const response = await fetch('https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/room?role=moderator', { method: 'GET', headers: { Authorization: `Bearer ${process.env.CARDINAL_API_KEY}`, },});const data = await response.json();console.log(response.status, data);const response: Response = await fetch('https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/room?role=moderator', { method: 'GET', headers: { Authorization: `Bearer ${process.env.CARDINAL_API_KEY}`, },});const data: unknown = await response.json();console.log(response.status, data);<?phprequire 'vendor/autoload.php'; // composer require guzzlehttp/guzzle
$client = new GuzzleHttp\Client();$response = $client->request('GET', 'https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/room', [ 'headers' => [ 'Authorization' => 'Bearer ' . getenv('CARDINAL_API_KEY'), ], 'query' => [ 'role' => 'moderator', ], 'http_errors' => false,]);echo $response->getStatusCode(), ' ', $response->getBody(), PHP_EOL;require 'json'require 'net/http'
uri = URI("https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/room?role=moderator")request = Net::HTTP::Get.new(uri)request['Authorization'] = "Bearer #{ENV.fetch('CARDINAL_API_KEY')}"
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }puts response.code, response.bodypackage main
import ( "fmt" "io" "net/http" "os")
func main() { req, err := http.NewRequest("GET", "https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/room?role=moderator", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer "+os.Getenv("CARDINAL_API_KEY"))
resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() data, _ := io.ReadAll(resp.Body) fmt.Println(resp.StatusCode, string(data))}import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;
public class Example { public static void main(String[] args) throws Exception { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/room?role=moderator")) .header("Authorization", "Bearer " + System.getenv("CARDINAL_API_KEY")) .method("GET", HttpRequest.BodyPublishers.noBody()) .build(); HttpResponse<String> response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.statusCode() + " " + response.body()); }}using System.Net.Http.Headers;using System.Text;
using var client = new HttpClient();using var request = new HttpRequestMessage(new HttpMethod("GET"), "https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/room?role=moderator");request.Headers.Authorization = new AuthenticationHeaderValue( "Bearer", Environment.GetEnvironmentVariable("CARDINAL_API_KEY"));
using var response = await client.SendAsync(request);Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");{ "url": "wss://live.cardinal.stream/rooms/1846bdac2d8c0ce0e846d4a3817012c9", "token": "v1.eyJy...", "expires_at": "2026-09-20T19:41:07Z", "role": "moderator", "viewer_id": "q2Vt3b0yJ1tF6pXhR8m4Ag"}Show a gift in the room
POST/v1/streams/{playback_id}/highlights
Shows a gift from your own giving system to everyone in the room, while
the stream is live. Cardinal takes no payments: post a highlight after
your giving system has taken the gift. id is yours and makes the call
safe to retry: a repeat is answered 200 with duplicate: true and
shown once. message is accepted and discarded — it reaches no viewer.
Path parameters
| Name | Type | Description |
|---|---|---|
| playback_idrequired | string | The stream’s |
Request body
| Name | Type | Description |
|---|---|---|
| idrequired | string | Your id for the gift. A repeat is shown once. |
| kindrequired | string | One of |
| amount_displayrequired | string | Shown exactly as given, such as |
| name | string | The giver, only if they chose to be shown. |
| message | string | Accepted and discarded. |
Response 200
A repeat of an id already shown; nothing new was shown.
Fields
| Name | Type | Description |
|---|---|---|
| deliveredrequired | integer | How many connected viewers it reached. |
| duplicaterequired | boolean | |
| displayed | boolean |
Response 202
Delivered to the room (displayed is false when gifts are on but their display is switched off).
Fields
| Name | Type | Description |
|---|---|---|
| deliveredrequired | integer | How many connected viewers it reached. |
| duplicaterequired | boolean | |
| displayed | boolean |
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_body | The body has a field that is not one of the documented ones. |
| 400 | unknown_kind |
|
| 400 | invalid_id |
|
| 400 | invalid_amount_display |
|
| 404 | not_found | No such stream in your organization. |
| 409 | not_live | Highlights can be posted only while the stream is live. |
| 409 | highlights_off | Highlights are switched off for this stream. |
| 502 | room_unreachable | The live room did not answer; retry shortly. |
| 503 | live_unconfigured | The live room is not available on this deployment. |
curl -X POST "https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/highlights" \ -H "Authorization: Bearer $CARDINAL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "id": "gift-5f1c0a92", "kind": "gift", "amount_display": "$50", "name": "The Alvarez family"}'import os
import requests
response = requests.post( "https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/highlights", headers={ "Authorization": f"Bearer {os.environ['CARDINAL_API_KEY']}", }, json={ "id": "gift-5f1c0a92", "kind": "gift", "amount_display": "$50", "name": "The Alvarez family", }, timeout=30,)print(response.status_code, response.text)const response = await fetch('https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/highlights', { method: 'POST', headers: { Authorization: `Bearer ${process.env.CARDINAL_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ "id": "gift-5f1c0a92", "kind": "gift", "amount_display": "$50", "name": "The Alvarez family" }),});const data = await response.json();console.log(response.status, data);const response: Response = await fetch('https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/highlights', { method: 'POST', headers: { Authorization: `Bearer ${process.env.CARDINAL_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ "id": "gift-5f1c0a92", "kind": "gift", "amount_display": "$50", "name": "The Alvarez family" }),});const data: unknown = await response.json();console.log(response.status, data);<?phprequire 'vendor/autoload.php'; // composer require guzzlehttp/guzzle
$client = new GuzzleHttp\Client();$response = $client->request('POST', 'https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/highlights', [ 'headers' => [ 'Authorization' => 'Bearer ' . getenv('CARDINAL_API_KEY'), ], 'json' => [ 'id' => 'gift-5f1c0a92', 'kind' => 'gift', 'amount_display' => '$50', 'name' => 'The Alvarez family', ], 'http_errors' => false,]);echo $response->getStatusCode(), ' ', $response->getBody(), PHP_EOL;require 'json'require 'net/http'
uri = URI("https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/highlights")request = Net::HTTP::Post.new(uri)request['Authorization'] = "Bearer #{ENV.fetch('CARDINAL_API_KEY')}"request['Content-Type'] = 'application/json'request.body = JSON.generate({ "id" => "gift-5f1c0a92", "kind" => "gift", "amount_display" => "$50", "name" => "The Alvarez family"})
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }puts response.code, response.bodypackage main
import ( "fmt" "io" "net/http" "os" "strings")
func main() { req, err := http.NewRequest("POST", "https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/highlights", strings.NewReader(`{ "id": "gift-5f1c0a92", "kind": "gift", "amount_display": "$50", "name": "The Alvarez family"}`)) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer "+os.Getenv("CARDINAL_API_KEY")) req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() data, _ := io.ReadAll(resp.Body) fmt.Println(resp.StatusCode, string(data))}import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;
public class Example { public static void main(String[] args) throws Exception { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/highlights")) .header("Authorization", "Bearer " + System.getenv("CARDINAL_API_KEY")) .header("Content-Type", "application/json") .method("POST", HttpRequest.BodyPublishers.ofString("{\"id\":\"gift-5f1c0a92\",\"kind\":\"gift\",\"amount_display\":\"$50\",\"name\":\"The Alvarez family\"}")) .build(); HttpResponse<String> response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.statusCode() + " " + response.body()); }}using System.Net.Http.Headers;using System.Text;
using var client = new HttpClient();using var request = new HttpRequestMessage(new HttpMethod("POST"), "https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/highlights");request.Headers.Authorization = new AuthenticationHeaderValue( "Bearer", Environment.GetEnvironmentVariable("CARDINAL_API_KEY"));request.Content = new StringContent("{\"id\":\"gift-5f1c0a92\",\"kind\":\"gift\",\"amount_display\":\"$50\",\"name\":\"The Alvarez family\"}", Encoding.UTF8, "application/json");
using var response = await client.SendAsync(request);Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");{ "delivered": 312, "duplicate": false}Moderate the room
POST/v1/streams/{playback_id}/moderation
Removes a chat message, or mutes or unmutes a viewer (optionally removing everything they have said). The ids come from the room’s own messages.
Path parameters
| Name | Type | Description |
|---|---|---|
| playback_idrequired | string | The stream’s |
Request body
| Name | Type | Description |
|---|---|---|
| actionrequired | string | One of |
| message_id | string | For |
| viewer_id | string | For |
| remove_messages | boolean | With |
Response 200
What the room did.
Fields
| Name | Type | Description |
|---|---|---|
| removed | one of | Whether the message was removed, or how many messages a mute removed. |
| muted | boolean | |
| unmuted | boolean | |
| duplicate | boolean | |
| repeated | boolean |
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_body | The body has a field that is not one of the documented ones. |
| 400 | unknown_action |
|
| 400 | invalid_message_id |
|
| 400 | invalid_viewer_id |
|
| 404 | not_found | No such stream in your organization. |
| 502 | room_unreachable | The live room did not answer; retry shortly. |
| 503 | live_unconfigured | The live room is not available on this deployment. |
curl -X POST "https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/moderation" \ -H "Authorization: Bearer $CARDINAL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "action": "mute_viewer", "viewer_id": "q2Vt3b0yJ1tF6pXhR8m4Ag", "remove_messages": true}'import os
import requests
response = requests.post( "https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/moderation", headers={ "Authorization": f"Bearer {os.environ['CARDINAL_API_KEY']}", }, json={ "action": "mute_viewer", "viewer_id": "q2Vt3b0yJ1tF6pXhR8m4Ag", "remove_messages": True, }, timeout=30,)print(response.status_code, response.text)const response = await fetch('https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/moderation', { method: 'POST', headers: { Authorization: `Bearer ${process.env.CARDINAL_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ "action": "mute_viewer", "viewer_id": "q2Vt3b0yJ1tF6pXhR8m4Ag", "remove_messages": true }),});const data = await response.json();console.log(response.status, data);const response: Response = await fetch('https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/moderation', { method: 'POST', headers: { Authorization: `Bearer ${process.env.CARDINAL_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ "action": "mute_viewer", "viewer_id": "q2Vt3b0yJ1tF6pXhR8m4Ag", "remove_messages": true }),});const data: unknown = await response.json();console.log(response.status, data);<?phprequire 'vendor/autoload.php'; // composer require guzzlehttp/guzzle
$client = new GuzzleHttp\Client();$response = $client->request('POST', 'https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/moderation', [ 'headers' => [ 'Authorization' => 'Bearer ' . getenv('CARDINAL_API_KEY'), ], 'json' => [ 'action' => 'mute_viewer', 'viewer_id' => 'q2Vt3b0yJ1tF6pXhR8m4Ag', 'remove_messages' => true, ], 'http_errors' => false,]);echo $response->getStatusCode(), ' ', $response->getBody(), PHP_EOL;require 'json'require 'net/http'
uri = URI("https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/moderation")request = Net::HTTP::Post.new(uri)request['Authorization'] = "Bearer #{ENV.fetch('CARDINAL_API_KEY')}"request['Content-Type'] = 'application/json'request.body = JSON.generate({ "action" => "mute_viewer", "viewer_id" => "q2Vt3b0yJ1tF6pXhR8m4Ag", "remove_messages" => true})
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }puts response.code, response.bodypackage main
import ( "fmt" "io" "net/http" "os" "strings")
func main() { req, err := http.NewRequest("POST", "https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/moderation", strings.NewReader(`{ "action": "mute_viewer", "viewer_id": "q2Vt3b0yJ1tF6pXhR8m4Ag", "remove_messages": true}`)) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer "+os.Getenv("CARDINAL_API_KEY")) req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() data, _ := io.ReadAll(resp.Body) fmt.Println(resp.StatusCode, string(data))}import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;
public class Example { public static void main(String[] args) throws Exception { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/moderation")) .header("Authorization", "Bearer " + System.getenv("CARDINAL_API_KEY")) .header("Content-Type", "application/json") .method("POST", HttpRequest.BodyPublishers.ofString("{\"action\":\"mute_viewer\",\"viewer_id\":\"q2Vt3b0yJ1tF6pXhR8m4Ag\",\"remove_messages\":true}")) .build(); HttpResponse<String> response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.statusCode() + " " + response.body()); }}using System.Net.Http.Headers;using System.Text;
using var client = new HttpClient();using var request = new HttpRequestMessage(new HttpMethod("POST"), "https://api.cardinalstreaming.com/v1/streams/1846bdac2d8c0ce0e846d4a3817012c9/moderation");request.Headers.Authorization = new AuthenticationHeaderValue( "Bearer", Environment.GetEnvironmentVariable("CARDINAL_API_KEY"));request.Content = new StringContent("{\"action\":\"mute_viewer\",\"viewer_id\":\"q2Vt3b0yJ1tF6pXhR8m4Ag\",\"remove_messages\":true}", Encoding.UTF8, "application/json");
using var response = await client.SendAsync(request);Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");{ "muted": true, "removed": 3}