Guide
Streaming software integration
If you are building OBS, Meld Studio, Streamlabs or your own broadcaster, this is the whole surface: five endpoints that hand you a streamer's key and ingest URLs, read and write what their stream says it is, and rotate the key when it leaks. Everything acts as the person who authorised you.
Two things do the most damage if you get them wrong, so they lead: which host you dial, and which protocol you send. The endpoint reference is below them.
Ingest protocols
Velora accepts two ingest protocols. Both use the same stream key.
WHIP (WebRTC-HTTP Ingestion Protocol)
RecommendedSub-second latency, and the only path that carries multiple quality levels. OBS 30+ and any WHIP-capable encoder.
- URL (the key is inside it)
- https://publish.velora.tv/live/{streamKey}?direction=whip
- Video / Audio
- H.264 / Opus
Paste as Server and leave Bearer Tokenempty — the key is already in the URL, and filling that field is the most common mistake.
RTMP (Real-Time Messaging Protocol)
Works with every encoder. Higher latency than WHIP and a single quality level — no simulcast.
- Server
- rtmp://rtmp.velora.tv/live
- Stream Key
- {streamKey}
- Video / Audio
- H.264 / AAC
Single video track only — we don't accept multi-track video over RTMP.
What the API gives you
The Velora Streaming API allows your application to:
- Retrieve the user's stream key and ingest server URLs
- Get and update stream title, category, and tags
- Check live status and viewer count
- Regenerate stream keys programmatically
- List available categories for stream configuration
Base URL
All streaming API requests use the following base URL. Every path below is relative to it.
https://api.velora.tv/api/integrations/oauth
Required scopes
Two of these are granted automatically. The two that touch the key or write to the channel need approval before your app can request them in production.
| Scope | Purpose | Approval |
|---|---|---|
stream:key | Access stream key and ingest URLs | Required |
stream:read | Read stream info, status, categories | Auto |
stream:write | Update title, category, tags | Required |
user:read | Get user profile information | Auto |
Endpoints
In the order a broadcaster integration calls them: fetch the key, read what the stream says it is, change it, look up a category slug, and — rarely — rotate the key.
/stream/setupscope stream:keyRetrieve the user's stream key and all available ingest server URLs. This endpoint requires the stream:key scope.
curl https://api.velora.tv/api/integrations/oauth/stream/setup \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
{
"streamKey": "live_abc123xyz789",
"whipUrl": "https://publish.velora.tv/live/live_abc123xyz789?direction=whip",
"rtmpUrl": "rtmp://rtmp.velora.tv/live",
"ingestServer": "publish.velora.tv",
"preferredProtocol": "whip"
}Note: The WHIP URL includes the stream key for WebRTC-based streaming.
/stream/infoscope stream:readGet the current stream information including title, category, and live status. Requires stream:read scope.
curl https://api.velora.tv/api/integrations/oauth/stream/info \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
{
"title": "Building an awesome project!",
"categoryName": "Software & Game Development",
"categorySlug": "software-and-game-development",
"isLive": true,
"viewerCount": 142,
"startedAt": "2026-01-19T02:30:00.000Z",
"tags": ["coding", "chill", "flashing-lights"],
"channelTags": ["vtuber", "lgbtqia"],
"contentWarnings": ["flashing-lights"]
}/stream/infoscope stream:writeUpdate the stream title and/or category. Requires stream:write scope. All fields are optional.
| Field | Type | Description |
|---|---|---|
title | string | Stream title (max 140 characters) |
categorySlug | string | Category slug (see /stream/categories) |
tags | string[] | This stream's tags. Up to 7, plus any number of content warning tags, which never count toward the limit. Omit to leave them unchanged; send [] to clear. Stays set into the next stream until changed. |
channelTags | string[] | The channel's identity tags (who the channel is, shown on the profile). Up to 5, plus any number of content warning tags. Omit to leave unchanged; send [] to clear. |
Fields you leave out are left as they are. Tags are stored as slugs: sending "Speed Run" saves speed-run, exactly as if the streamer typed it in the dashboard. A list over the limit is refused with a 400 rather than trimmed, so your app always knows what was saved. The response shows the stored result.
curl -X PUT https://api.velora.tv/api/integrations/oauth/stream/info \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Building a chat bot with Node.js",
"categorySlug": "software-and-game-development",
"tags": ["coding", "chill", "flashing-lights"]
}'{
"success": true,
"message": "Stream info updated",
"title": "Building a chat bot with Node.js",
"categorySlug": "software-and-game-development",
"tags": ["coding", "chill", "flashing-lights"],
"channelTags": ["vtuber", "lgbtqia"],
"contentWarnings": ["flashing-lights"]
}/stream/tagsscope stream:readThe curated tag vocabulary and the rules for setting tags, for building a tag picker. Each tag says which tier it fits (session for this stream, identity for the channel, or both) and whether it is a content warning. Custom tags are allowed too; this list is the common ground, not a limit.
curl https://api.velora.tv/api/integrations/oauth/stream/tags \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
{
"rules": {
"streamTagLimit": 7,
"channelTagLimit": 5,
"contentWarningsCountTowardLimit": false,
"customTagsAllowed": true,
"slugFormat": "lowercase a-z, 0-9 and single hyphens, up to 40 characters; other input is converted"
},
"tags": [
{
"slug": "flashing-lights",
"label": "Flashing Lights",
"tier": "both",
"category": "warning",
"isContentWarning": true
},
{
"slug": "speedrun",
"label": "Speedrun",
"tier": "session",
"category": "content",
"isContentWarning": false
}
]
}/stream/categoriesscope stream:readGet a list of available stream categories. Use the slug value when updating stream info.
curl https://api.velora.tv/api/integrations/oauth/stream/categories \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
{
"categories": [
{
"slug": "just-chatting",
"name": "Just Chatting",
"imageUrl": "https://assets.velora.tv/categories/just-chatting.jpg"
},
{
"slug": "software-and-game-development",
"name": "Software & Game Development",
"imageUrl": "https://assets.velora.tv/categories/dev.jpg"
},
{
"slug": "music",
"name": "Music",
"imageUrl": null
}
]
}/stream/key/regeneratescope stream:keyGenerate a new stream key for the user. The old key will immediately stop working. Requires stream:key scope.
Warning: This action is irreversible. The previous stream key will be invalidated immediately.
curl -X POST https://api.velora.tv/api/integrations/oauth/stream/key/regenerate \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
{
"streamKey": "live_newkey456def"
}Complete example
Here's a complete example in JavaScript that fetches stream setup and updates stream info:
// Velora Streaming API Client Example
const VELORA_API = 'https://api.velora.tv/api/integrations/oauth';
class VeloraStreamClient {
constructor(accessToken) {
this.accessToken = accessToken;
}
async request(endpoint, options = {}) {
const response = await fetch(`${VELORA_API}${endpoint}`, {
...options,
headers: {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'application/json',
...options.headers,
},
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'API request failed');
}
return response.json();
}
// Get stream key and ingest URLs
async getStreamSetup() {
return this.request('/stream/setup');
}
// Get current stream info
async getStreamInfo() {
return this.request('/stream/info');
}
// Update stream title and category
async updateStreamInfo({ title, categorySlug, tags }) {
return this.request('/stream/info', {
method: 'PUT',
body: JSON.stringify({ title, categorySlug, tags }),
});
}
// Get available categories
async getCategories() {
return this.request('/stream/categories');
}
// Regenerate stream key
async regenerateStreamKey() {
return this.request('/stream/key/regenerate', { method: 'POST' });
}
}
// Usage
const client = new VeloraStreamClient('your_access_token');
// Configure OBS with Velora credentials
async function configureOBS() {
const setup = await client.getStreamSetup();
console.log('Configure OBS with these settings:');
console.log('Server:', setup.rtmpUrl);
console.log('Stream Key:', setup.streamKey);
return setup;
}
// Update stream before going live
async function prepareStream(title, category) {
await client.updateStreamInfo({
title,
categorySlug: category,
});
console.log('Stream info updated!');
}
// Example: Set up a coding stream
configureOBS().then(setup => {
prepareStream(
'Building a Velora integration!',
'software-and-game-development'
);
});Error handling
| Status | Meaning |
|---|---|
401 | Invalid or expired access token |
403 | User doesn't have streaming enabled or missing required scope |
400 | Too many tags (over 7 stream tags or 5 channel tags, not counting content warnings). Nothing is saved. |
404 | Category not found (when updating stream info) |
429 | Rate limit exceeded |