DEVELOPER DOCUMENTATION

Veluraq Documentation

Guides and API reference for video hosting, live classrooms, storage and webhooks, with copy-paste examples and ready-made AI prompts.

Get Started Free AI prompts Community Forum
Getting started

๐Ÿ“˜ Introduction

Veluraq is a platform for video hosting, live classrooms and S3-compatible storage. You upload a video, Veluraq encodes it into adaptive HLS, and you embed one iframe on your site. The same account can also run interactive live classes in the browser and broadcast from OBS.

ProductWhat it doesWhere to read
Video streamingUpload, encode to adaptive HLS (AES-128 encrypted), thumbnails, captions, chapters, webhooksUploading video, Videos API
PlayerEmbeddable player with accent colour, logo, speed, picture-in-picture and Player.js controlEmbedding the player
ShieldDomain lock, signed tokens, country rules, moving viewer watermark, session controlProtecting your videos
Live classroomBrowser classes with waiting room, hand raising, screen sharing, chat, host controls and recordingLive classrooms
Live broadcastRTMP ingest from OBS converted to HLS, optional automatic recordingBroadcasting with OBS
StorageS3-compatible storage zones with read-write and read-only keysStorage zones (S3)

How the pieces fit together

OBS / encoder โ”€โ”€RTMP :1935โ”€โ”€โ–บ Veluraq ingest โ”€โ”€โ–บ HLS  โ”€โ”€โ–บ /live/LIBRARY_ID/index.m3u8
Your app / uploader โ”€โ”€HTTPSโ”€โ”€โ–บ REST API โ”€โ”€โ–บ encoder (HLS ladder) โ”€โ”€โ–บ iframe player /embed/LIBRARY_ID/VIDEO_ID
Teacher + students โ”€โ”€WebRTC (browser)โ”€โ”€โ–บ classroom media server (SFU) โ”€โ”€โ–บ every participant
AWS CLI / rclone โ”€โ”€S3 API (/s3/ZONE)โ”€โ”€โ–บ storage zones
No web technology can stop someone from filming a screen with another device. Shield raises the effort needed to copy a video and helps you trace leaks; it is not a guarantee.
Getting started

โšก Quick start

From zero to a playing video in five steps. Replace the upper-case placeholders.

  1. Create an account at https://live.veluraq.net/register. New accounts get a $5 welcome credit once the e-mail address is verified.
  2. Create a library in the dashboard (Video Streaming โ†’ Add Video Library) or with the account API. Copy the Library ID and the API key from the library's API page.
  3. Create a video object:
    curl -X POST "https://live.veluraq.net/api/v1/library/LIBRARY_ID/videos" \
      -H "AccessKey: YOUR_LIBRARY_API_KEY" -H "Content-Type: application/json" \
      -d '{"title":"My first video"}'
    # โ†’ {"guid":"VIDEO_GUID","status":"created","statusCode":0, ...}
  4. Upload the file (simple PUT, or resumable TUS for large files):
    curl -X PUT "https://live.veluraq.net/api/v1/library/LIBRARY_ID/videos/VIDEO_GUID" \
      -H "AccessKey: YOUR_LIBRARY_API_KEY" -H "Content-Type: video/mp4" \
      --data-binary @video.mp4
    Poll GET /api/v1/library/LIBRARY_ID/videos/VIDEO_GUID until "status":"ready" (statusCode: 3).
  5. Embed it:
    <iframe src="https://live.veluraq.net/embed/LIBRARY_ID/VIDEO_GUID" style="width:100%;aspect-ratio:16/9;border:0"
      allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>
Opening the embed URL directly in a browser tab may be refused when Block direct embed or a domain lock is enabled for the library. Test it inside an iframe on an allowed domain.
Getting started

๐Ÿ”‘ Authentication & keys

Veluraq uses long random keys sent in the AccessKey header. There are three kinds:

KeyWhere to find itUsed for
Library API keyLibrary โ†’ APIEverything under /api/v1/library/{id}/โ€ฆ: videos, collections, captions, live sessions. Full read and write.
Library read-only keyLibrary โ†’ APIGET requests only.
Account API keyAccount settings/api/v1/account/libraries: list, create and delete libraries.
Storage zone keyStorage โ†’ zoneStorage S3 endpoint. A read-only key is also provided per zone.
curl "https://live.veluraq.net/api/v1/library/LIBRARY_ID/videos" -H "AccessKey: YOUR_LIBRARY_API_KEY"

Keys can be reset at any time from the dashboard; the old key stops working immediately. Never put a library API key in browser code. Use the embed URL (optionally signed, see Protecting your videos) for anything a viewer's browser touches.

Account API

MethodPathDescription
GET/api/v1/account/librariesList libraries with their keys and video counts
POST/api/v1/account/librariesCreate a library. Body: {"name":"โ€ฆ"}
DELETE/api/v1/account/libraries/{id}Delete a library and its videos
curl "https://live.veluraq.net/api/v1/account/libraries" -H "AccessKey: YOUR_ACCOUNT_API_KEY"

Conventions

  • Requests and responses are JSON (Content-Type: application/json); request bodies are limited to 3 MB. Use PUT or TUS for video files.
  • Errors return a non-2xx status and {"success":false,"message":"โ€ฆ","statusCode":401}.
  • Typical statuses: 400 invalid input, 401 missing or wrong key, 403 not allowed (read-only key, disabled feature), 404 not found, 413 body too large, 429 too many requests.
  • Video identifiers are GUIDs. Library identifiers are numeric.
Video

โฌ†๏ธ Uploading video

Create a video object first (POST /videos), then send the bytes with one of the methods below. The dashboard offers the same options under Add Media.

1. Direct PUT (simple, up to your upload limit)

curl -X PUT "https://live.veluraq.net/api/v1/library/LIBRARY_ID/videos/VIDEO_GUID" \
  -H "AccessKey: YOUR_LIBRARY_API_KEY" -H "Content-Type: video/mp4" --data-binary @video.mp4

2. Resumable upload with TUS 1.0.0 (large files, unreliable networks)

Use any TUS client. The upload is created with POST /videos/tus, which also creates the video object, then continued with PATCH on the returned location. After a dropped connection, ask for the current offset with HEAD and continue from there.

# 1. create the upload (title goes in Upload-Metadata, base64)
curl -i -X POST "https://live.veluraq.net/api/v1/library/LIBRARY_ID/videos/tus" \
  -H "AccessKey: YOUR_LIBRARY_API_KEY" -H "Tus-Resumable: 1.0.0" \
  -H "Upload-Length: 104857600" \
  -H "Upload-Metadata: title $(printf 'Keynote' | base64)"
# โ†’ 201 Created, Location: /api/v1/library/LIBRARY_ID/videos/VIDEO_GUID/tus

# 2. send the bytes (repeat from the returned Upload-Offset after an interruption)
curl -i -X PATCH "https://live.veluraq.net/api/v1/library/LIBRARY_ID/videos/VIDEO_GUID/tus" \
  -H "AccessKey: YOUR_LIBRARY_API_KEY" -H "Tus-Resumable: 1.0.0" \
  -H "Upload-Offset: 0" -H "Content-Type: application/offset+octet-stream" \
  --data-binary @keynote.mp4
A video created with POST /videos (JSON) is uploaded with PUT. A TUS PATCH is only accepted for an upload that was created with POST /videos/tus; otherwise the server answers 409 Upload is not in progress.
// Browser or Node with tus-js-client
new tus.Upload(file, {
  endpoint: "https://live.veluraq.net/api/v1/library/LIBRARY_ID/videos/tus",
  headers: { AccessKey: "YOUR_LIBRARY_API_KEY" },   // use a server-side proxy; never ship the key to browsers
  metadata: { title: file.name },
  chunkSize: 8 * 1024 * 1024,
  retryDelays: [0, 1000, 3000, 5000],
  onProgress: (sent, total) => console.log(((sent / total) * 100).toFixed(1) + "%"),
  onSuccess: () => console.log("uploaded"),
}).start();

3. Chunked PUT

PUT /videos/{guid}/chunk accepts a file in parts for clients that cannot use TUS. The dashboard uploader uses it.

4. Import from a URL

curl -X POST "https://live.veluraq.net/api/v1/library/LIBRARY_ID/videos/fetch" \
  -H "AccessKey: YOUR_LIBRARY_API_KEY" -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/video.mp4","title":"Imported","collectionId":""}'

Public http(s) URLs only. The request is accepted and queued; a URL that points to a private network address ends with status failed and the reason URL points to a private address. A Google Drive link can be sent as driveUrl when Drive import is configured for the platform.

Video

๐ŸŽž Videos API reference

All paths are relative to https://live.veluraq.net/api/v1/library/LIBRARY_ID and need the AccessKey header.

MethodPathDescription
GET/videosList videos. Query: page, itemsPerPage (max 100), search, collection, orderBy (title, title-desc, old, views, length, size; default newest first)
POST/videosCreate a video object. Body: title, collectionId
GET/videos/{guid}Get one video
POST/videos/{guid}Update title, description, collectionId, chapters, moments, metaTags
DELETE/videos/{guid}Delete a video and its files
PUT/videos/{guid}Upload the file (raw body)
POST/videos/tusCreate a TUS upload
PATCH/videos/{guid}/tusSend bytes for a TUS upload
POST/videos/fetchImport from a URL
PUT/videos/{guid}/thumbnailSet a custom thumbnail (image body)
POST/videos/{guid}/thumbnail/selectChoose one of the generated thumbnails
GET/videos/{guid}/playPlayback data for the video
GET/videos/{guid}/resolutionsEncoded renditions and sizes
GET/videos/{guid}/storageFiles and bytes stored for the video
GET/videos/{guid}/statisticsViews and watch time
POST/videos/{guid}/reencodeEncode again (needs the original to be kept: library option Keep original files)
POST/videos/{guid}/captions/{lang}Add or replace a caption track
DELETE/videos/{guid}/captions/{lang}Remove a caption track
GET/collectionsList collections
POST/collectionsCreate a collection. Body: {"name":"โ€ฆ"}
POST/collections/{id}Rename a collection
DELETE/collections/{id}Delete a collection
GET/statistics/encodingCounts of videos by status and resolution

Video object

{
  "videoLibraryId": 831233,
  "guid": "4a135072-ff00-491e-b0cb-d9588d70e770",
  "title": "Demo clip",
  "dateUploaded": "2026-11-07T01:32:47.205Z",
  "views": 0,
  "status": "ready",          // created | uploaded | downloading | transcoding | ready | failed
  "statusCode": 3,            // 0 created/uploaded, 1 downloading, 2 transcoding, 3 ready, 5 failed
  "encodeProgress": 100,
  "length": 6,                // seconds
  "width": 1280, "height": 720,
  "availableResolutions": "240p,360p,480p,720p",
  "thumbnailFileName": "thumb.jpg",
  "storageSize": 1288401,     // bytes of encoded files
  "collectionId": "",
  "captions": [], "chapters": [], "moments": [],
  "hasMp4Fallback": false, "audioTracks": []
}

List responses look like {"totalItems":12,"currentPage":1,"itemsPerPage":100,"items":[โ€ฆ]}.

Collections

curl -X POST "https://live.veluraq.net/api/v1/library/LIBRARY_ID/collections" -H "AccessKey: YOUR_LIBRARY_API_KEY" \
  -H "Content-Type: application/json" -d '{"name":"Algebra 101"}'
# โ†’ {"guid":"โ€ฆ","name":"Algebra 101"}
Video

โš™๏ธ Encoding & statuses

Every uploaded video is encoded into an adaptive HLS ladder. You choose the qualities per library (Library โ†’ Encoding). Veluraq never upscales: a 720p source produces 240p to 720p.

RenditionDefault video bitrate
240p400 kbps
360p800 kbps
480p1,400 kbps
720p2,800 kbps
1080p5,000 kbps
1440p9,000 kbps
2160p16,000 kbps

Bitrates can be changed per library. Options include the encoder speed preset, output codec (H.264 by default), audio normalization, keeping the original file, an MP4 fallback and early playback while encoding is still running. Thumbnails and an animated preview are created automatically.

statusCodestatusMeaning
0created / uploadedObject exists or the file has arrived and is waiting
1downloadingImporting from a URL
2transcodingEncoding in progress (see encodeProgress)
3readyPlayable
5failedEncoding failed; the reason is shown in the dashboard
32deletedWebhook only: the video was deleted

Encoding runs one job at a time per server, so a long queue delays newer uploads. Encoding is included in the price (see Billing).

Video

๐Ÿ’ฌ Captions, chapters & transcription

Captions

Upload a WebVTT or SRT file. captionsFile is the file text (or its base64); label is the name shown in the player menu.

curl -X POST "https://live.veluraq.net/api/v1/library/LIBRARY_ID/videos/VIDEO_GUID/captions/en" \
  -H "AccessKey: YOUR_LIBRARY_API_KEY" -H "Content-Type: application/json" \
  -d '{"label":"English","captionsFile":"WEBVTT\n\n00:00.000 --> 00:02.000\nHello"}'

Chapters and moments

Send chapters (objects with title and start in seconds) and moments (label and timestamp in seconds) with POST /videos/{guid}. Chapters appear on the player timeline; viewers can jump to a moment.

Automatic transcription

POST /videos/{guid}/transcribe with {"language":"en"} queues a speech-to-text job. It needs the speech engine (whisper.cpp and a model) to be installed on the server; check GET /transcribe/engine which reports ready and what is missing. Jobs are listed at GET /transcribe/jobs and can be retried or deleted. POST /videos/{guid}/captions/{lang}/translate translates an existing track. Transcription can be switched off per account by an administrator.

Playback

๐ŸŽฌ Embedding the player

Iframe embed

<div style="position:relative;padding-top:56.25%">
  <iframe src="https://live.veluraq.net/embed/LIBRARY_ID/VIDEO_GUID"
    style="border:0;position:absolute;inset:0;width:100%;height:100%"
    allow="autoplay; fullscreen; picture-in-picture" allowfullscreen loading="lazy"></iframe>
</div>

URL parameters

ParameterExampleEffect
autoplaytrue | falseStart playing (browsers may force muted playback)
mutedtrueStart muted
looptrueRepeat
preloadfalseDo not preload video data
t90 or 1:30Start time (seconds or m:ss)
colorff8a00Accent colour as hex without #
captionsenCaption language turned on by default
tokenโ€ฆSigned token when token authentication is on (with expires)
expires1893456000Token expiry, Unix seconds
uid / user / viewerIdstudent-42Viewer name shown in the moving watermark
wm_sub / phone / student_id+201โ€ฆSecond watermark line
wm_time, wm_ip1Add time or IP address to the watermark
wm_posfloat | tl | tr | bl | brWatermark position
wm_color, wm_bg, wm_size, wm_opacity, wm_tileโ€ฆWatermark appearance
watermarktrueForce the watermark on

Player appearance (accent colour, logo, controls, heatmap, remembered position, title) is configured per library under Library โ†’ Player.

Controlling the player with Player.js

The embed implements the open Player.js protocol.

MethodsEvents
play pause mute unmute getPaused getMuted getVolume setVolume getCurrentTime setCurrentTime getDuration getLoop setLoop getPlaybackRate setPlaybackRate addEventListener removeEventListenerready play pause ended timeupdate progress seeked error
<script src="https://cdn.embed.ly/player-0.1.0.min.js"></script>
<script>
  const player = new playerjs.Player(document.querySelector('iframe'));
  player.on('ready', () => player.play());
  player.on('timeupdate', ({ seconds, duration }) => console.log(seconds, duration));
</script>

Live HLS in your own player

For a live broadcast use https://live.veluraq.net/live/LIBRARY_ID/index.m3u8 with any HLS player (hls.js, Safari). Library videos are encrypted HLS and are meant to be played through the embed.

Playback

๐Ÿ›ก๏ธ Protecting your videos (Shield)

Shield options are under Library โ†’ Security. They combine; enable only what you need.

FeatureHow it works
Encrypted HLSSegments are AES-128 encrypted. Keys are given only to valid playback sessions.
Domain lockAllow only listed referrer domains (wildcards such as *.school.com work) and block others. Block direct embed refuses opening the player URL as a page.
Token authenticationThe embed URL must carry a valid token and expires (below).
Country rulesAllow or block viewers by country (two-letter codes; uses the CDN or proxy country header).
Moving watermarkShows a viewer name, ID or IP over the video to discourage and trace leaks.
Session controlActive playback sessions are listed; one can be revoked immediately.

Signed embed URLs

With Token authentication on, generate the URL on your server with the library's security key (Library โ†’ Security; it is different from the API key). The token is sha256(securityKey + videoGuid + expires) in hex, where expires is Unix time in seconds.

const crypto = require('crypto');
function signedEmbed(libraryId, videoGuid, securityKey, ttlSeconds = 3600, viewer = '') {
  const expires = Math.floor(Date.now() / 1000) + ttlSeconds;
  const token = crypto.createHash('sha256').update(securityKey + videoGuid + expires).digest('hex');
  const q = new URLSearchParams({ token, expires, ...(viewer && { uid: viewer }) });
  return `https://live.veluraq.net/embed/${libraryId}/${videoGuid}?${q}`;
}
import hashlib, time
def signed_embed(library_id, guid, security_key, ttl=3600):
    expires = int(time.time()) + ttl
    token = hashlib.sha256(f"{security_key}{guid}{expires}".encode()).hexdigest()
    return f"https://live.veluraq.net/embed/{library_id}/{guid}?token={token}&expires={expires}"
Shield cannot prevent screen recording or filming a screen. It makes casual copying hard and lets you trace a leak back to a viewer through the watermark.
Live

๐Ÿ”ด Broadcasting with OBS (RTMP)

Broadcast from OBS Studio, vMix, Wirecast or an FFmpeg command. The stream is converted to HLS so viewers can watch in any browser, with a delay of a few seconds. Each library has one live channel.

SettingValue
Serverrtmp://live.veluraq.net:1935/live
Stream keyLibrary โ†’ Live Studio. Press Reset key once to generate your private key; keep it secret.
Keyframe interval2 seconds
Rate control / bitrateCBR, 3,000 to 6,000 kbps for 1080p
CodecsH.264 video, AAC audio
ffmpeg -re -i input.mp4 -c:v libx264 -preset veryfast -g 50 -b:v 4500k -c:a aac -b:a 160k \
  -f flv "rtmp://YOUR_HOST:1935/live/YOUR_STREAM_KEY"

Playback and status

URLDescription
GET /live/LIBRARY_ID/index.m3u8HLS playlist (404 "Stream is offline" while nobody is broadcasting)
GET /live/LIBRARY_ID/statusPublic status: isLive, uptime, viewers, peak viewers
GET /api/libraries/LIBRARY_ID/live/statusSame plus the stream key (dashboard session required)

With Auto-record on (default) the broadcast is saved to the library as a normal video when it ends. The channel may show as live for up to a minute after the encoder stops while the recording is finalised. Your hosting must allow inbound TCP 1935 for RTMP to work; self-hosted deployments should open it in the firewall.

Live

๐ŸŽ“ Live classrooms

A live classroom is an interactive session in the browser: nothing to install, students join from a link. Media goes through a media server (SFU), so the host uploads one stream however many people attend (up to 300 participants and 12 simultaneous student speakers by default).

Features

  • Host and participant roles. Hosts use a private link containing the host key; participants use the share link.
  • Waiting room (host approval). Optionally every person waits on a standby screen until the host admits or declines them; the host can admit everyone at once.
  • Hand raising and speaking permission. Students ask to speak and the host approves, or the host allows open microphones and cameras.
  • Host controls. Mute or stop the camera of any participant, mute everyone, remove people, allow or stop screen sharing, end the class.
  • Screen sharing, camera and microphone, with audio always prioritised over video on weak connections and a data saver (audio only) switch for students.
  • Chat with history, and a moving watermark option.
  • Recording. The class is recorded in the browser and saved to the library as a video named "(Recorded Class)". Recording starts when the host joins.
  • Language. The classroom follows the site language (English or Arabic, including right-to-left).

Create a session from the dashboard

Open Library โ†’ Live Studio, create a session, then copy the host link and the student link. The session options include Host approves people before entering.

Create a session with the API

curl -X POST "https://live.veluraq.net/api/v1/library/LIBRARY_ID/sessions" \
  -H "AccessKey: YOUR_LIBRARY_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "title": "Algebra - week 3",
    "description": "Quadratic equations",
    "isScheduled": false,
    "duration": 60,
    "settings": {
      "requireHostApproval": true,
      "allowHandRaise": true, "allowStudentMic": true, "allowStudentCam": true,
      "allowChat": true, "autoRecord": true, "requireApprovalToSpeak": true,
      "dynamicWatermark": true, "watermarkText": ""
    }
  }'
{
  "success": true,
  "session": {
    "id": "sess_330a6bac7f78", "libraryId": "LIBRARY_ID", "title": "Algebra - week 3",
    "status": "live",                       // scheduled | live | ended
    "shareUrl": "https://live.veluraq.net/live/session/sess_330a6bac7f78",
    "hostUrl":  "https://live.veluraq.net/live/session/sess_330a6bac7f78?role=host&key=hk_โ€ฆ",
    "embedUrl": "https://live.veluraq.net/live/session/sess_330a6bac7f78/embed",
    "hostEmbedUrl": "โ€ฆ/embed?role=host&key=hk_โ€ฆ",
    "embedCode": "<iframe โ€ฆ>", "hostKey": "hk_โ€ฆ",
    "recordedVideoId": "โ€ฆ", "startedAt": 1794054512831
  }
}
Treat hostUrl and hostKey like a password: anyone who has them controls the class.
MethodPath (relative to /api/v1/library/LIBRARY_ID)Description
POST/sessionsCreate a session (live now, or scheduled with isScheduled and scheduledFor)
GET/sessionsList sessions. Optional ?status=live|scheduled|ended
GET/sessions/{id}Get one session
PATCH/sessions/{id}Update title, description, schedule or settings
POST/sessions/{id}/startStart a scheduled session
POST/sessions/{id}/endEnd a session
DELETE/sessions/{id}Delete a session
GET/api/sessions/{id}Shortcut without the library id (also /api/v1/sessions)
GET/live/session/{id}/infoPublic session info for the join page
GET/live/session/{id}/chatChat history
GET/live/session/{id}/recordingThe recording, when available

Embedding a classroom

<iframe src="https://live.veluraq.net/live/session/SESSION_ID/embed" style="width:100%;height:700px;border:0"
  allow="camera; microphone; display-capture; fullscreen" allowfullscreen></iframe>

The browser asks for camera and microphone permission when someone starts them; the page must be served over HTTPS (or localhost).

Operating notes

  • Server settings: CLASSROOM_MAX_PARTICIPANTS, CLASSROOM_MAX_SPEAKERS, SFU_ANNOUNCED_IP (public address) and UDP+TCP ports 44400-44403. Media does not pass through an HTTP reverse proxy or Cloudflare's proxy.
  • The classroom needs a modern Chromium, Firefox or Safari browser.
Storage

๐Ÿ—„ Storage zones (S3)

A storage zone is a private bucket for files such as backups, raw footage and website assets. Create zones under Storage in the dashboard. Each zone has a read-write key and a read-only key, plus optional size and quota limits.

Address a zone by its zone ID (for example sz_5b03339e6a60, shown on the zone page). Use the ID in scripts: zone names are not guaranteed to be unique across accounts.

OperationRequest
UploadPUT /s3/{zone}/path/file.ext
DownloadGET /s3/{zone}/path/file.ext
MetadataHEAD /s3/{zone}/path/file.ext
DeleteDELETE /s3/{zone}/path/file.ext
ListGET /s3/{zone}/ (S3 ListObjects XML; supports prefix)

Authenticate with the AccessKey header, or with the AWS signature header: the access key ID is the zone key, and the secret value is not checked.

# direct HTTP
curl -X PUT "https://live.veluraq.net/s3/ZONE_ID/docs/readme.txt" -H "AccessKey: ZONE_ACCESS_KEY" --data-binary @readme.txt
curl "https://live.veluraq.net/s3/ZONE_ID/" -H "AccessKey: ZONE_ACCESS_KEY"

# AWS CLI  (~/.aws/credentials: aws_access_key_id = ZONE_ACCESS_KEY, aws_secret_access_key = any-value)
aws --endpoint-url https://live.veluraq.net/s3 s3 ls s3://ZONE_ID/
aws --endpoint-url https://live.veluraq.net/s3 s3 cp ./data.zip s3://ZONE_ID/
aws --endpoint-url https://live.veluraq.net/s3 s3 sync ./assets s3://ZONE_ID/assets/
// Node.js with @aws-sdk/client-s3
const s3 = new S3Client({
  region: 'auto', forcePathStyle: true,
  endpoint: 'https://live.veluraq.net/s3',
  credentials: { accessKeyId: 'ZONE_ACCESS_KEY', secretAccessKey: 'any-value' },
});
await s3.send(new PutObjectCommand({ Bucket: 'ZONE_ID', Key: 'lesson-01.mp4', Body: stream }));

rclone and Cyberduck work the same way: provider "Other S3", endpoint https://live.veluraq.net/s3, path-style addressing. Multipart upload is not supported; upload files with a single PUT.

Your own storage node and R2

Platform administrators can mirror encoded video into a Cloudflare R2 bucket and attach custom S3-compatible nodes for video storage. Those settings apply to the whole platform and are managed by administrators.

Events

๐Ÿ”— Webhooks

Set a webhook URL in Library โ†’ API โ†’ Webhook URL. Veluraq sends a POST with a JSON body whenever a video changes state. URLs must be public http(s) addresses.

StatusEvent
0Video uploaded
1Importing from a URL
2Encoding started
3Encoding finished (playable)
5Encoding failed
32Video deleted
POST https://your-app.example/webhook
Content-Type: application/json
X-Veluraq-Signature: sha256=3f9aโ€ฆ        (HMAC-SHA256 of the raw body)
X-Veluraq-Event-Id: 5b7eโ€ฆ

{
  "IsLiveStreamWebhook": false,
  "VideoLibraryId": 831233,
  "VideoGuid": "4a135072-ff00-491e-b0cb-d9588d70e770",
  "Status": 3,
  "EventId": "5b7eโ€ฆ",
  "Timestamp": "2026-11-07T01:32:51.200Z"
}

The signature key is the library API key. Verify against the raw request body before parsing it.

const crypto = require('crypto');
const express = require('express');
const app = express();

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const expected = 'sha256=' + crypto.createHmac('sha256', process.env.VELURAQ_LIBRARY_API_KEY)
    .update(req.body).digest('hex');
  const got = String(req.headers['x-veluraq-signature'] || '');
  if (got.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected))) {
    return res.sendStatus(403);
  }
  const event = JSON.parse(req.body);
  if (event.Status === 3) console.log('ready:', event.VideoGuid);
  res.sendStatus(200);
});
import hmac, hashlib
def valid(raw_body: bytes, header: str, api_key: str) -> bool:
    expected = "sha256=" + hmac.new(api_key.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header)

Delivery

  • Answer with any 2xx within 10 seconds. Otherwise Veluraq retries up to 3 times with growing delays.
  • Deliveries (status, response code) are listed on the same page, where you can send a test event and re-send a past event.
  • Use EventId to ignore duplicates.
Account

๐Ÿ’ณ Billing & credit

Veluraq is pay-as-you-go with a prepaid balance. There are no contracts or per-seat fees.

ItemPrice
Video storage$0.01 per GB per month
Delivery$0.005 per GB streamed to viewers
Playback fee$0.0002 per view + $0.001 per GB
EncodingIncluded
Live classroomsIncluded
Minimum top-up$10
  • Welcome credit. New accounts receive $5 after verifying their e-mail address. Credit may be withheld when the same person, device or network already claimed it.
  • Top up by card or PayPal when enabled, by voucher code (Billing โ†’ Use coupon code), or by contacting sales for invoice or local payment methods.
  • Auto-recharge can top up your balance when it runs low.
  • Prices shown in Arabic mode are converted to Egyptian pounds for display; billing is in US dollars.
  • Usage, invoices and the monthly breakdown are on the Billing pages.

Redeem a voucher

curl -X POST "https://live.veluraq.net/api/billing/redeem-code" -H "Content-Type: application/json" \
  -H "Cookie: YOUR_SESSION_COOKIE" -d '{"code":"WELCOME-1234"}'

Voucher redemption uses your signed-in dashboard session, not an API key.

Account

๐Ÿค Affiliate program

Share your referral link (Affiliate page, format https://live.veluraq.net/register?ref=CODE). When a referred user signs up you earn a sign-up reward, and a commission on their spend for a limited period.

TierActive referrals neededCommission
Starter020%
Pro525%
Elite2030%
  • Commissions are held for 14 days before they become available.
  • Minimum payout: $50. Request payouts from the Affiliate page.
  • Exact terms are shown in your dashboard and can be adjusted by the platform operator.
Account

๐Ÿ” Account security

  • Sign-in: e-mail and password, or Google / Apple when the platform has enabled them.
  • Two-factor authentication: enable an authenticator app under Account โ†’ Security.
  • Password reset: use "Forgot password" on the sign-in page; a link is sent by e-mail when e-mail delivery is configured.
  • Team members: invite teammates from the Account page; invitations are tied to the invited e-mail address.
  • Language: English and Arabic (right-to-left). Switch with the language menu or add ?lang=ar to any page.
AI assistants

โœจ AI integration prompts

Copy a prompt into ChatGPT, Claude, Gemini, Cursor, Copilot or Windsurf as the first message. The prompt contains the exact, current API of this platform (https://live.veluraq.net), so the assistant writes code that works instead of guessing. Start with the Master prompt; use a task prompt when you already know what you are building.

๐ŸŒŸ Master promptEverything the AI needs: APIs, uploads, player, webhooks, live, storage and billing
You are a senior engineer helping a developer integrate with the Veluraq platform. Use ONLY the facts below. If something is not listed here, say you are not sure instead of inventing an endpoint or option.

## Platform
- Base URL: https://live.veluraq.net
- Video library API: https://live.veluraq.net/api/v1/library/{libraryId}/... โ€” header "AccessKey: <library API key>" (a read-only key allows GET only)
- Account API: https://live.veluraq.net/api/v1/account/libraries โ€” header "AccessKey: <account API key>"
- Storage (S3-compatible): https://live.veluraq.net/s3/{zoneId}/... โ€” "AccessKey: <zone key>" or an AWS signature whose access key id is the zone key
- RTMP ingest: rtmp://live.veluraq.net:1935/live/{streamKey}; live HLS: https://live.veluraq.net/live/{libraryId}/index.m3u8
- Errors: non-2xx with {"success":false,"message":"...","statusCode":N}. JSON bodies up to 3 MB.

## Videos
- Create: POST /videos {title, collectionId} -> {guid, status:"created"}
- Upload: PUT /videos/{guid} (raw body, Content-Type video/mp4), or TUS 1.0.0: POST /videos/tus (Upload-Length, Upload-Metadata "title <base64>") -> Location, then PATCH that Location with Tus-Resumable, Upload-Offset, Content-Type application/offset+octet-stream. PATCH on a video not created via /videos/tus returns 409.
- Import: POST /videos/fetch {url, title}
- Poll GET /videos/{guid}: status created|uploaded|downloading|transcoding|ready|failed, statusCode 0|0|1|2|3|5, encodeProgress, availableResolutions ("240p,360p,...")
- Other: GET/POST/DELETE /videos/{guid}, GET /videos (page, itemsPerPage, search, collection, orderBy), collections (GET/POST/DELETE /collections), captions POST /videos/{guid}/captions/{lang} {label, captionsFile}, thumbnails PUT /videos/{guid}/thumbnail.
- Encoding is adaptive HLS (AES-128), up to the source resolution, never upscaled.

## Playback
- Embed: <iframe src="https://live.veluraq.net/embed/{libraryId}/{guid}"> with allow="autoplay; fullscreen; picture-in-picture".
- Query params: autoplay, muted, loop, preload, t, color (hex without #), captions, token+expires, uid/user/viewerId, wm_* (watermark).
- Player.js protocol is supported (play, pause, mute, unmute, setVolume, getCurrentTime, setCurrentTime, getDuration, events ready/play/pause/ended/timeupdate/progress/seeked/error).
- If "Token authentication" is on: token = sha256_hex(securityKey + guid + expires) with expires in Unix seconds; securityKey is NOT the API key. Never expose the API key or security key in browser code.

## Webhooks
- Configured per library. POST JSON {IsLiveStreamWebhook, VideoLibraryId, VideoGuid, Status, EventId, Timestamp}. Status: 0 uploaded, 1 importing, 2 encoding, 3 finished, 5 failed, 32 deleted.
- Header X-Veluraq-Signature: sha256=<hex HMAC-SHA256 of the RAW body using the library API key>. Verify with a constant-time compare. Reply 2xx; up to 3 attempts.

## Live classroom
- POST /api/v1/library/{id}/sessions {title, description, isScheduled, scheduledFor, duration, settings:{requireHostApproval, allowHandRaise, allowStudentMic, allowStudentCam, allowChat, autoRecord, requireApprovalToSpeak, dynamicWatermark, watermarkText}} -> session {id, status, shareUrl, hostUrl, embedUrl, hostKey}. hostUrl contains the host key: treat as secret.
- GET /sessions, GET/PATCH/DELETE /sessions/{id}, POST /sessions/{id}/start, POST /sessions/{id}/end.

## Storage
- PUT/GET/HEAD/DELETE /s3/{zoneId}/{key}; GET /s3/{zoneId}/ lists objects. Single PUT only (no multipart).

## Billing
- Storage $0.01/GB-month, delivery $0.005/GB, playback fee $0.0002/view + $0.001/GB. Encoding and live classrooms included. $5 welcome credit after e-mail verification.

Rules: show complete, runnable code with error handling; keep keys in environment variables; never invent endpoints; say what to check in the dashboard when unsure.
First, greet the developer and ask what they want to build (video upload, embedding, live class, OBS broadcast, storage, or webhooks).
โš›๏ธ Next.js / React playerSigned embed URLs, Player.js component and server-side upload proxy
You are a senior engineer helping a developer integrate with the Veluraq platform. Use ONLY the facts below. If something is not listed here, say you are not sure instead of inventing an endpoint or option.

## Platform
- Base URL: https://live.veluraq.net
- Video library API: https://live.veluraq.net/api/v1/library/{libraryId}/... โ€” header "AccessKey: <library API key>" (a read-only key allows GET only)
- Account API: https://live.veluraq.net/api/v1/account/libraries โ€” header "AccessKey: <account API key>"
- Storage (S3-compatible): https://live.veluraq.net/s3/{zoneId}/... โ€” "AccessKey: <zone key>" or an AWS signature whose access key id is the zone key
- RTMP ingest: rtmp://live.veluraq.net:1935/live/{streamKey}; live HLS: https://live.veluraq.net/live/{libraryId}/index.m3u8
- Errors: non-2xx with {"success":false,"message":"...","statusCode":N}. JSON bodies up to 3 MB.

## Videos
- Create: POST /videos {title, collectionId} -> {guid, status:"created"}
- Upload: PUT /videos/{guid} (raw body, Content-Type video/mp4), or TUS 1.0.0: POST /videos/tus (Upload-Length, Upload-Metadata "title <base64>") -> Location, then PATCH that Location with Tus-Resumable, Upload-Offset, Content-Type application/offset+octet-stream. PATCH on a video not created via /videos/tus returns 409.
- Import: POST /videos/fetch {url, title}
- Poll GET /videos/{guid}: status created|uploaded|downloading|transcoding|ready|failed, statusCode 0|0|1|2|3|5, encodeProgress, availableResolutions ("240p,360p,...")
- Other: GET/POST/DELETE /videos/{guid}, GET /videos (page, itemsPerPage, search, collection, orderBy), collections (GET/POST/DELETE /collections), captions POST /videos/{guid}/captions/{lang} {label, captionsFile}, thumbnails PUT /videos/{guid}/thumbnail.
- Encoding is adaptive HLS (AES-128), up to the source resolution, never upscaled.

## Playback
- Embed: <iframe src="https://live.veluraq.net/embed/{libraryId}/{guid}"> with allow="autoplay; fullscreen; picture-in-picture".
- Query params: autoplay, muted, loop, preload, t, color (hex without #), captions, token+expires, uid/user/viewerId, wm_* (watermark).
- Player.js protocol is supported (play, pause, mute, unmute, setVolume, getCurrentTime, setCurrentTime, getDuration, events ready/play/pause/ended/timeupdate/progress/seeked/error).
- If "Token authentication" is on: token = sha256_hex(securityKey + guid + expires) with expires in Unix seconds; securityKey is NOT the API key. Never expose the API key or security key in browser code.

## Webhooks
- Configured per library. POST JSON {IsLiveStreamWebhook, VideoLibraryId, VideoGuid, Status, EventId, Timestamp}. Status: 0 uploaded, 1 importing, 2 encoding, 3 finished, 5 failed, 32 deleted.
- Header X-Veluraq-Signature: sha256=<hex HMAC-SHA256 of the RAW body using the library API key>. Verify with a constant-time compare. Reply 2xx; up to 3 attempts.

## Live classroom
- POST /api/v1/library/{id}/sessions {title, description, isScheduled, scheduledFor, duration, settings:{requireHostApproval, allowHandRaise, allowStudentMic, allowStudentCam, allowChat, autoRecord, requireApprovalToSpeak, dynamicWatermark, watermarkText}} -> session {id, status, shareUrl, hostUrl, embedUrl, hostKey}. hostUrl contains the host key: treat as secret.
- GET /sessions, GET/PATCH/DELETE /sessions/{id}, POST /sessions/{id}/start, POST /sessions/{id}/end.

## Storage
- PUT/GET/HEAD/DELETE /s3/{zoneId}/{key}; GET /s3/{zoneId}/ lists objects. Single PUT only (no multipart).

## Billing
- Storage $0.01/GB-month, delivery $0.005/GB, playback fee $0.0002/view + $0.001/GB. Encoding and live classrooms included. $5 welcome credit after e-mail verification.

Rules: show complete, runnable code with error handling; keep keys in environment variables; never invent endpoints; say what to check in the dashboard when unsure.
First, greet the developer and ask what they want to build (video upload, embedding, live class, OBS broadcast, storage, or webhooks).

TASK: Build a Next.js (App Router, TypeScript) integration.
1. A server route that signs embed URLs (only when token authentication is on) using the security key from an environment variable.
2. A <VelurqPlayer libraryId guid viewerId /> client component that renders the responsive iframe and uses Player.js (play, pause, timeupdate, ended) without exposing any key.
3. A server action that creates a video object and returns an upload URL strategy that does not leak the API key (proxy uploads through the server).
Explain which values come from the dashboard (Library ID, API key, security key).
๐Ÿ Python resumable uploaderTUS upload with resume, retries, progress and status polling
You are a senior engineer helping a developer integrate with the Veluraq platform. Use ONLY the facts below. If something is not listed here, say you are not sure instead of inventing an endpoint or option.

## Platform
- Base URL: https://live.veluraq.net
- Video library API: https://live.veluraq.net/api/v1/library/{libraryId}/... โ€” header "AccessKey: <library API key>" (a read-only key allows GET only)
- Account API: https://live.veluraq.net/api/v1/account/libraries โ€” header "AccessKey: <account API key>"
- Storage (S3-compatible): https://live.veluraq.net/s3/{zoneId}/... โ€” "AccessKey: <zone key>" or an AWS signature whose access key id is the zone key
- RTMP ingest: rtmp://live.veluraq.net:1935/live/{streamKey}; live HLS: https://live.veluraq.net/live/{libraryId}/index.m3u8
- Errors: non-2xx with {"success":false,"message":"...","statusCode":N}. JSON bodies up to 3 MB.

## Videos
- Create: POST /videos {title, collectionId} -> {guid, status:"created"}
- Upload: PUT /videos/{guid} (raw body, Content-Type video/mp4), or TUS 1.0.0: POST /videos/tus (Upload-Length, Upload-Metadata "title <base64>") -> Location, then PATCH that Location with Tus-Resumable, Upload-Offset, Content-Type application/offset+octet-stream. PATCH on a video not created via /videos/tus returns 409.
- Import: POST /videos/fetch {url, title}
- Poll GET /videos/{guid}: status created|uploaded|downloading|transcoding|ready|failed, statusCode 0|0|1|2|3|5, encodeProgress, availableResolutions ("240p,360p,...")
- Other: GET/POST/DELETE /videos/{guid}, GET /videos (page, itemsPerPage, search, collection, orderBy), collections (GET/POST/DELETE /collections), captions POST /videos/{guid}/captions/{lang} {label, captionsFile}, thumbnails PUT /videos/{guid}/thumbnail.
- Encoding is adaptive HLS (AES-128), up to the source resolution, never upscaled.

## Playback
- Embed: <iframe src="https://live.veluraq.net/embed/{libraryId}/{guid}"> with allow="autoplay; fullscreen; picture-in-picture".
- Query params: autoplay, muted, loop, preload, t, color (hex without #), captions, token+expires, uid/user/viewerId, wm_* (watermark).
- Player.js protocol is supported (play, pause, mute, unmute, setVolume, getCurrentTime, setCurrentTime, getDuration, events ready/play/pause/ended/timeupdate/progress/seeked/error).
- If "Token authentication" is on: token = sha256_hex(securityKey + guid + expires) with expires in Unix seconds; securityKey is NOT the API key. Never expose the API key or security key in browser code.

## Webhooks
- Configured per library. POST JSON {IsLiveStreamWebhook, VideoLibraryId, VideoGuid, Status, EventId, Timestamp}. Status: 0 uploaded, 1 importing, 2 encoding, 3 finished, 5 failed, 32 deleted.
- Header X-Veluraq-Signature: sha256=<hex HMAC-SHA256 of the RAW body using the library API key>. Verify with a constant-time compare. Reply 2xx; up to 3 attempts.

## Live classroom
- POST /api/v1/library/{id}/sessions {title, description, isScheduled, scheduledFor, duration, settings:{requireHostApproval, allowHandRaise, allowStudentMic, allowStudentCam, allowChat, autoRecord, requireApprovalToSpeak, dynamicWatermark, watermarkText}} -> session {id, status, shareUrl, hostUrl, embedUrl, hostKey}. hostUrl contains the host key: treat as secret.
- GET /sessions, GET/PATCH/DELETE /sessions/{id}, POST /sessions/{id}/start, POST /sessions/{id}/end.

## Storage
- PUT/GET/HEAD/DELETE /s3/{zoneId}/{key}; GET /s3/{zoneId}/ lists objects. Single PUT only (no multipart).

## Billing
- Storage $0.01/GB-month, delivery $0.005/GB, playback fee $0.0002/view + $0.001/GB. Encoding and live classrooms included. $5 welcome credit after e-mail verification.

Rules: show complete, runnable code with error handling; keep keys in environment variables; never invent endpoints; say what to check in the dashboard when unsure.
First, greet the developer and ask what they want to build (video upload, embedding, live class, OBS broadcast, storage, or webhooks).

TASK: Write a Python 3 uploader.
1. Create the video with POST /videos, then upload with TUS: POST /videos/tus then PATCH in 8 MB chunks, resuming from the Upload-Offset returned by HEAD after any failure (exponential backoff, max 6 retries).
2. Show a progress bar, then poll GET /videos/{guid} every 3 seconds until status is "ready" or "failed" and print availableResolutions.
3. Read the Library ID and API key from environment variables. Use only the requests library.
๐ŸŽฅ OBS / RTMP broadcastEncoder settings, FFmpeg command and live HLS playback
You are a senior engineer helping a developer integrate with the Veluraq platform. Use ONLY the facts below. If something is not listed here, say you are not sure instead of inventing an endpoint or option.

## Platform
- Base URL: https://live.veluraq.net
- Video library API: https://live.veluraq.net/api/v1/library/{libraryId}/... โ€” header "AccessKey: <library API key>" (a read-only key allows GET only)
- Account API: https://live.veluraq.net/api/v1/account/libraries โ€” header "AccessKey: <account API key>"
- Storage (S3-compatible): https://live.veluraq.net/s3/{zoneId}/... โ€” "AccessKey: <zone key>" or an AWS signature whose access key id is the zone key
- RTMP ingest: rtmp://live.veluraq.net:1935/live/{streamKey}; live HLS: https://live.veluraq.net/live/{libraryId}/index.m3u8
- Errors: non-2xx with {"success":false,"message":"...","statusCode":N}. JSON bodies up to 3 MB.

## Videos
- Create: POST /videos {title, collectionId} -> {guid, status:"created"}
- Upload: PUT /videos/{guid} (raw body, Content-Type video/mp4), or TUS 1.0.0: POST /videos/tus (Upload-Length, Upload-Metadata "title <base64>") -> Location, then PATCH that Location with Tus-Resumable, Upload-Offset, Content-Type application/offset+octet-stream. PATCH on a video not created via /videos/tus returns 409.
- Import: POST /videos/fetch {url, title}
- Poll GET /videos/{guid}: status created|uploaded|downloading|transcoding|ready|failed, statusCode 0|0|1|2|3|5, encodeProgress, availableResolutions ("240p,360p,...")
- Other: GET/POST/DELETE /videos/{guid}, GET /videos (page, itemsPerPage, search, collection, orderBy), collections (GET/POST/DELETE /collections), captions POST /videos/{guid}/captions/{lang} {label, captionsFile}, thumbnails PUT /videos/{guid}/thumbnail.
- Encoding is adaptive HLS (AES-128), up to the source resolution, never upscaled.

## Playback
- Embed: <iframe src="https://live.veluraq.net/embed/{libraryId}/{guid}"> with allow="autoplay; fullscreen; picture-in-picture".
- Query params: autoplay, muted, loop, preload, t, color (hex without #), captions, token+expires, uid/user/viewerId, wm_* (watermark).
- Player.js protocol is supported (play, pause, mute, unmute, setVolume, getCurrentTime, setCurrentTime, getDuration, events ready/play/pause/ended/timeupdate/progress/seeked/error).
- If "Token authentication" is on: token = sha256_hex(securityKey + guid + expires) with expires in Unix seconds; securityKey is NOT the API key. Never expose the API key or security key in browser code.

## Webhooks
- Configured per library. POST JSON {IsLiveStreamWebhook, VideoLibraryId, VideoGuid, Status, EventId, Timestamp}. Status: 0 uploaded, 1 importing, 2 encoding, 3 finished, 5 failed, 32 deleted.
- Header X-Veluraq-Signature: sha256=<hex HMAC-SHA256 of the RAW body using the library API key>. Verify with a constant-time compare. Reply 2xx; up to 3 attempts.

## Live classroom
- POST /api/v1/library/{id}/sessions {title, description, isScheduled, scheduledFor, duration, settings:{requireHostApproval, allowHandRaise, allowStudentMic, allowStudentCam, allowChat, autoRecord, requireApprovalToSpeak, dynamicWatermark, watermarkText}} -> session {id, status, shareUrl, hostUrl, embedUrl, hostKey}. hostUrl contains the host key: treat as secret.
- GET /sessions, GET/PATCH/DELETE /sessions/{id}, POST /sessions/{id}/start, POST /sessions/{id}/end.

## Storage
- PUT/GET/HEAD/DELETE /s3/{zoneId}/{key}; GET /s3/{zoneId}/ lists objects. Single PUT only (no multipart).

## Billing
- Storage $0.01/GB-month, delivery $0.005/GB, playback fee $0.0002/view + $0.001/GB. Encoding and live classrooms included. $5 welcome credit after e-mail verification.

Rules: show complete, runnable code with error handling; keep keys in environment variables; never invent endpoints; say what to check in the dashboard when unsure.
First, greet the developer and ask what they want to build (video upload, embedding, live class, OBS broadcast, storage, or webhooks).

TASK: Help the user broadcast live.
1. Give exact OBS Studio settings: server rtmp://live.veluraq.net:1935/live, stream key from Library > Live Studio (press Reset key once to generate it), CBR 3000-6000 kbps, keyframe interval 2 s, x264 veryfast, AAC 160 kbps.
2. Give an equivalent FFmpeg command.
3. Show how to play https://live.veluraq.net/live/{libraryId}/index.m3u8 with hls.js and how to poll https://live.veluraq.net/live/{libraryId}/status.
4. Explain that the recording is saved to the library automatically when the stream ends (auto-record) and that RTMP needs TCP 1935 open on the server.
๐ŸŽ“ Live classroom integrationCreate sessions through the API and embed them in an LMS
You are a senior engineer helping a developer integrate with the Veluraq platform. Use ONLY the facts below. If something is not listed here, say you are not sure instead of inventing an endpoint or option.

## Platform
- Base URL: https://live.veluraq.net
- Video library API: https://live.veluraq.net/api/v1/library/{libraryId}/... โ€” header "AccessKey: <library API key>" (a read-only key allows GET only)
- Account API: https://live.veluraq.net/api/v1/account/libraries โ€” header "AccessKey: <account API key>"
- Storage (S3-compatible): https://live.veluraq.net/s3/{zoneId}/... โ€” "AccessKey: <zone key>" or an AWS signature whose access key id is the zone key
- RTMP ingest: rtmp://live.veluraq.net:1935/live/{streamKey}; live HLS: https://live.veluraq.net/live/{libraryId}/index.m3u8
- Errors: non-2xx with {"success":false,"message":"...","statusCode":N}. JSON bodies up to 3 MB.

## Videos
- Create: POST /videos {title, collectionId} -> {guid, status:"created"}
- Upload: PUT /videos/{guid} (raw body, Content-Type video/mp4), or TUS 1.0.0: POST /videos/tus (Upload-Length, Upload-Metadata "title <base64>") -> Location, then PATCH that Location with Tus-Resumable, Upload-Offset, Content-Type application/offset+octet-stream. PATCH on a video not created via /videos/tus returns 409.
- Import: POST /videos/fetch {url, title}
- Poll GET /videos/{guid}: status created|uploaded|downloading|transcoding|ready|failed, statusCode 0|0|1|2|3|5, encodeProgress, availableResolutions ("240p,360p,...")
- Other: GET/POST/DELETE /videos/{guid}, GET /videos (page, itemsPerPage, search, collection, orderBy), collections (GET/POST/DELETE /collections), captions POST /videos/{guid}/captions/{lang} {label, captionsFile}, thumbnails PUT /videos/{guid}/thumbnail.
- Encoding is adaptive HLS (AES-128), up to the source resolution, never upscaled.

## Playback
- Embed: <iframe src="https://live.veluraq.net/embed/{libraryId}/{guid}"> with allow="autoplay; fullscreen; picture-in-picture".
- Query params: autoplay, muted, loop, preload, t, color (hex without #), captions, token+expires, uid/user/viewerId, wm_* (watermark).
- Player.js protocol is supported (play, pause, mute, unmute, setVolume, getCurrentTime, setCurrentTime, getDuration, events ready/play/pause/ended/timeupdate/progress/seeked/error).
- If "Token authentication" is on: token = sha256_hex(securityKey + guid + expires) with expires in Unix seconds; securityKey is NOT the API key. Never expose the API key or security key in browser code.

## Webhooks
- Configured per library. POST JSON {IsLiveStreamWebhook, VideoLibraryId, VideoGuid, Status, EventId, Timestamp}. Status: 0 uploaded, 1 importing, 2 encoding, 3 finished, 5 failed, 32 deleted.
- Header X-Veluraq-Signature: sha256=<hex HMAC-SHA256 of the RAW body using the library API key>. Verify with a constant-time compare. Reply 2xx; up to 3 attempts.

## Live classroom
- POST /api/v1/library/{id}/sessions {title, description, isScheduled, scheduledFor, duration, settings:{requireHostApproval, allowHandRaise, allowStudentMic, allowStudentCam, allowChat, autoRecord, requireApprovalToSpeak, dynamicWatermark, watermarkText}} -> session {id, status, shareUrl, hostUrl, embedUrl, hostKey}. hostUrl contains the host key: treat as secret.
- GET /sessions, GET/PATCH/DELETE /sessions/{id}, POST /sessions/{id}/start, POST /sessions/{id}/end.

## Storage
- PUT/GET/HEAD/DELETE /s3/{zoneId}/{key}; GET /s3/{zoneId}/ lists objects. Single PUT only (no multipart).

## Billing
- Storage $0.01/GB-month, delivery $0.005/GB, playback fee $0.0002/view + $0.001/GB. Encoding and live classrooms included. $5 welcome credit after e-mail verification.

Rules: show complete, runnable code with error handling; keep keys in environment variables; never invent endpoints; say what to check in the dashboard when unsure.
First, greet the developer and ask what they want to build (video upload, embedding, live class, OBS broadcast, storage, or webhooks).

TASK: Integrate live classrooms into an LMS.
1. Create a session with the API when a lesson is scheduled (isScheduled true + scheduledFor), store session.id, shareUrl and hostUrl server-side.
2. Show the teacher the hostUrl only; show students the shareUrl or embedCode inside the LMS page (allow="camera; microphone; display-capture").
3. Enable requireHostApproval for private classes; explain the waiting room, hand raising and host mute controls.
4. After the class, find the recorded video (recordedVideoId) and embed it with the player.
๐Ÿ›ก๏ธ Playback protectionDomain lock, signed tokens and watermark
You are a senior engineer helping a developer integrate with the Veluraq platform. Use ONLY the facts below. If something is not listed here, say you are not sure instead of inventing an endpoint or option.

## Platform
- Base URL: https://live.veluraq.net
- Video library API: https://live.veluraq.net/api/v1/library/{libraryId}/... โ€” header "AccessKey: <library API key>" (a read-only key allows GET only)
- Account API: https://live.veluraq.net/api/v1/account/libraries โ€” header "AccessKey: <account API key>"
- Storage (S3-compatible): https://live.veluraq.net/s3/{zoneId}/... โ€” "AccessKey: <zone key>" or an AWS signature whose access key id is the zone key
- RTMP ingest: rtmp://live.veluraq.net:1935/live/{streamKey}; live HLS: https://live.veluraq.net/live/{libraryId}/index.m3u8
- Errors: non-2xx with {"success":false,"message":"...","statusCode":N}. JSON bodies up to 3 MB.

## Videos
- Create: POST /videos {title, collectionId} -> {guid, status:"created"}
- Upload: PUT /videos/{guid} (raw body, Content-Type video/mp4), or TUS 1.0.0: POST /videos/tus (Upload-Length, Upload-Metadata "title <base64>") -> Location, then PATCH that Location with Tus-Resumable, Upload-Offset, Content-Type application/offset+octet-stream. PATCH on a video not created via /videos/tus returns 409.
- Import: POST /videos/fetch {url, title}
- Poll GET /videos/{guid}: status created|uploaded|downloading|transcoding|ready|failed, statusCode 0|0|1|2|3|5, encodeProgress, availableResolutions ("240p,360p,...")
- Other: GET/POST/DELETE /videos/{guid}, GET /videos (page, itemsPerPage, search, collection, orderBy), collections (GET/POST/DELETE /collections), captions POST /videos/{guid}/captions/{lang} {label, captionsFile}, thumbnails PUT /videos/{guid}/thumbnail.
- Encoding is adaptive HLS (AES-128), up to the source resolution, never upscaled.

## Playback
- Embed: <iframe src="https://live.veluraq.net/embed/{libraryId}/{guid}"> with allow="autoplay; fullscreen; picture-in-picture".
- Query params: autoplay, muted, loop, preload, t, color (hex without #), captions, token+expires, uid/user/viewerId, wm_* (watermark).
- Player.js protocol is supported (play, pause, mute, unmute, setVolume, getCurrentTime, setCurrentTime, getDuration, events ready/play/pause/ended/timeupdate/progress/seeked/error).
- If "Token authentication" is on: token = sha256_hex(securityKey + guid + expires) with expires in Unix seconds; securityKey is NOT the API key. Never expose the API key or security key in browser code.

## Webhooks
- Configured per library. POST JSON {IsLiveStreamWebhook, VideoLibraryId, VideoGuid, Status, EventId, Timestamp}. Status: 0 uploaded, 1 importing, 2 encoding, 3 finished, 5 failed, 32 deleted.
- Header X-Veluraq-Signature: sha256=<hex HMAC-SHA256 of the RAW body using the library API key>. Verify with a constant-time compare. Reply 2xx; up to 3 attempts.

## Live classroom
- POST /api/v1/library/{id}/sessions {title, description, isScheduled, scheduledFor, duration, settings:{requireHostApproval, allowHandRaise, allowStudentMic, allowStudentCam, allowChat, autoRecord, requireApprovalToSpeak, dynamicWatermark, watermarkText}} -> session {id, status, shareUrl, hostUrl, embedUrl, hostKey}. hostUrl contains the host key: treat as secret.
- GET /sessions, GET/PATCH/DELETE /sessions/{id}, POST /sessions/{id}/start, POST /sessions/{id}/end.

## Storage
- PUT/GET/HEAD/DELETE /s3/{zoneId}/{key}; GET /s3/{zoneId}/ lists objects. Single PUT only (no multipart).

## Billing
- Storage $0.01/GB-month, delivery $0.005/GB, playback fee $0.0002/view + $0.001/GB. Encoding and live classrooms included. $5 welcome credit after e-mail verification.

Rules: show complete, runnable code with error handling; keep keys in environment variables; never invent endpoints; say what to check in the dashboard when unsure.
First, greet the developer and ask what they want to build (video upload, embedding, live class, OBS broadcast, storage, or webhooks).

TASK: Protect video playback.
1. Turn on domain lock and token authentication (Library > Security), then write a server function that returns a signed embed URL: expires = now + 3600; token = sha256_hex(securityKey + guid + expires); add uid=<viewer id> for the moving watermark.
2. Show the iframe code, and how to refresh the URL before it expires.
3. State clearly that nothing can prevent filming a screen; the watermark helps trace leaks.
๐Ÿ—„ Storage zones (S3)AWS CLI, boto3 and rclone setup
You are a senior engineer helping a developer integrate with the Veluraq platform. Use ONLY the facts below. If something is not listed here, say you are not sure instead of inventing an endpoint or option.

## Platform
- Base URL: https://live.veluraq.net
- Video library API: https://live.veluraq.net/api/v1/library/{libraryId}/... โ€” header "AccessKey: <library API key>" (a read-only key allows GET only)
- Account API: https://live.veluraq.net/api/v1/account/libraries โ€” header "AccessKey: <account API key>"
- Storage (S3-compatible): https://live.veluraq.net/s3/{zoneId}/... โ€” "AccessKey: <zone key>" or an AWS signature whose access key id is the zone key
- RTMP ingest: rtmp://live.veluraq.net:1935/live/{streamKey}; live HLS: https://live.veluraq.net/live/{libraryId}/index.m3u8
- Errors: non-2xx with {"success":false,"message":"...","statusCode":N}. JSON bodies up to 3 MB.

## Videos
- Create: POST /videos {title, collectionId} -> {guid, status:"created"}
- Upload: PUT /videos/{guid} (raw body, Content-Type video/mp4), or TUS 1.0.0: POST /videos/tus (Upload-Length, Upload-Metadata "title <base64>") -> Location, then PATCH that Location with Tus-Resumable, Upload-Offset, Content-Type application/offset+octet-stream. PATCH on a video not created via /videos/tus returns 409.
- Import: POST /videos/fetch {url, title}
- Poll GET /videos/{guid}: status created|uploaded|downloading|transcoding|ready|failed, statusCode 0|0|1|2|3|5, encodeProgress, availableResolutions ("240p,360p,...")
- Other: GET/POST/DELETE /videos/{guid}, GET /videos (page, itemsPerPage, search, collection, orderBy), collections (GET/POST/DELETE /collections), captions POST /videos/{guid}/captions/{lang} {label, captionsFile}, thumbnails PUT /videos/{guid}/thumbnail.
- Encoding is adaptive HLS (AES-128), up to the source resolution, never upscaled.

## Playback
- Embed: <iframe src="https://live.veluraq.net/embed/{libraryId}/{guid}"> with allow="autoplay; fullscreen; picture-in-picture".
- Query params: autoplay, muted, loop, preload, t, color (hex without #), captions, token+expires, uid/user/viewerId, wm_* (watermark).
- Player.js protocol is supported (play, pause, mute, unmute, setVolume, getCurrentTime, setCurrentTime, getDuration, events ready/play/pause/ended/timeupdate/progress/seeked/error).
- If "Token authentication" is on: token = sha256_hex(securityKey + guid + expires) with expires in Unix seconds; securityKey is NOT the API key. Never expose the API key or security key in browser code.

## Webhooks
- Configured per library. POST JSON {IsLiveStreamWebhook, VideoLibraryId, VideoGuid, Status, EventId, Timestamp}. Status: 0 uploaded, 1 importing, 2 encoding, 3 finished, 5 failed, 32 deleted.
- Header X-Veluraq-Signature: sha256=<hex HMAC-SHA256 of the RAW body using the library API key>. Verify with a constant-time compare. Reply 2xx; up to 3 attempts.

## Live classroom
- POST /api/v1/library/{id}/sessions {title, description, isScheduled, scheduledFor, duration, settings:{requireHostApproval, allowHandRaise, allowStudentMic, allowStudentCam, allowChat, autoRecord, requireApprovalToSpeak, dynamicWatermark, watermarkText}} -> session {id, status, shareUrl, hostUrl, embedUrl, hostKey}. hostUrl contains the host key: treat as secret.
- GET /sessions, GET/PATCH/DELETE /sessions/{id}, POST /sessions/{id}/start, POST /sessions/{id}/end.

## Storage
- PUT/GET/HEAD/DELETE /s3/{zoneId}/{key}; GET /s3/{zoneId}/ lists objects. Single PUT only (no multipart).

## Billing
- Storage $0.01/GB-month, delivery $0.005/GB, playback fee $0.0002/view + $0.001/GB. Encoding and live classrooms included. $5 welcome credit after e-mail verification.

Rules: show complete, runnable code with error handling; keep keys in environment variables; never invent endpoints; say what to check in the dashboard when unsure.
First, greet the developer and ask what they want to build (video upload, embedding, live class, OBS broadcast, storage, or webhooks).

TASK: Set up storage-zone access.
1. AWS CLI profile (access key id = zone key, secret = any value, endpoint https://live.veluraq.net/s3) with ls, cp and sync examples using the zone ID.
2. A boto3 script (path-style addressing) for PUT, GET, LIST and DELETE.
3. An rclone remote (provider Other, endpoint https://live.veluraq.net/s3, path style).
Note: single PUT only (no multipart), use the read-only key for download-only jobs.
๐Ÿ”” Webhook receiverHMAC verification and event handling
You are a senior engineer helping a developer integrate with the Veluraq platform. Use ONLY the facts below. If something is not listed here, say you are not sure instead of inventing an endpoint or option.

## Platform
- Base URL: https://live.veluraq.net
- Video library API: https://live.veluraq.net/api/v1/library/{libraryId}/... โ€” header "AccessKey: <library API key>" (a read-only key allows GET only)
- Account API: https://live.veluraq.net/api/v1/account/libraries โ€” header "AccessKey: <account API key>"
- Storage (S3-compatible): https://live.veluraq.net/s3/{zoneId}/... โ€” "AccessKey: <zone key>" or an AWS signature whose access key id is the zone key
- RTMP ingest: rtmp://live.veluraq.net:1935/live/{streamKey}; live HLS: https://live.veluraq.net/live/{libraryId}/index.m3u8
- Errors: non-2xx with {"success":false,"message":"...","statusCode":N}. JSON bodies up to 3 MB.

## Videos
- Create: POST /videos {title, collectionId} -> {guid, status:"created"}
- Upload: PUT /videos/{guid} (raw body, Content-Type video/mp4), or TUS 1.0.0: POST /videos/tus (Upload-Length, Upload-Metadata "title <base64>") -> Location, then PATCH that Location with Tus-Resumable, Upload-Offset, Content-Type application/offset+octet-stream. PATCH on a video not created via /videos/tus returns 409.
- Import: POST /videos/fetch {url, title}
- Poll GET /videos/{guid}: status created|uploaded|downloading|transcoding|ready|failed, statusCode 0|0|1|2|3|5, encodeProgress, availableResolutions ("240p,360p,...")
- Other: GET/POST/DELETE /videos/{guid}, GET /videos (page, itemsPerPage, search, collection, orderBy), collections (GET/POST/DELETE /collections), captions POST /videos/{guid}/captions/{lang} {label, captionsFile}, thumbnails PUT /videos/{guid}/thumbnail.
- Encoding is adaptive HLS (AES-128), up to the source resolution, never upscaled.

## Playback
- Embed: <iframe src="https://live.veluraq.net/embed/{libraryId}/{guid}"> with allow="autoplay; fullscreen; picture-in-picture".
- Query params: autoplay, muted, loop, preload, t, color (hex without #), captions, token+expires, uid/user/viewerId, wm_* (watermark).
- Player.js protocol is supported (play, pause, mute, unmute, setVolume, getCurrentTime, setCurrentTime, getDuration, events ready/play/pause/ended/timeupdate/progress/seeked/error).
- If "Token authentication" is on: token = sha256_hex(securityKey + guid + expires) with expires in Unix seconds; securityKey is NOT the API key. Never expose the API key or security key in browser code.

## Webhooks
- Configured per library. POST JSON {IsLiveStreamWebhook, VideoLibraryId, VideoGuid, Status, EventId, Timestamp}. Status: 0 uploaded, 1 importing, 2 encoding, 3 finished, 5 failed, 32 deleted.
- Header X-Veluraq-Signature: sha256=<hex HMAC-SHA256 of the RAW body using the library API key>. Verify with a constant-time compare. Reply 2xx; up to 3 attempts.

## Live classroom
- POST /api/v1/library/{id}/sessions {title, description, isScheduled, scheduledFor, duration, settings:{requireHostApproval, allowHandRaise, allowStudentMic, allowStudentCam, allowChat, autoRecord, requireApprovalToSpeak, dynamicWatermark, watermarkText}} -> session {id, status, shareUrl, hostUrl, embedUrl, hostKey}. hostUrl contains the host key: treat as secret.
- GET /sessions, GET/PATCH/DELETE /sessions/{id}, POST /sessions/{id}/start, POST /sessions/{id}/end.

## Storage
- PUT/GET/HEAD/DELETE /s3/{zoneId}/{key}; GET /s3/{zoneId}/ lists objects. Single PUT only (no multipart).

## Billing
- Storage $0.01/GB-month, delivery $0.005/GB, playback fee $0.0002/view + $0.001/GB. Encoding and live classrooms included. $5 welcome credit after e-mail verification.

Rules: show complete, runnable code with error handling; keep keys in environment variables; never invent endpoints; say what to check in the dashboard when unsure.
First, greet the developer and ask what they want to build (video upload, embedding, live class, OBS broadcast, storage, or webhooks).

TASK: Write a robust webhook receiver (Node.js Express and Python Flask).
1. Capture the RAW body, compute sha256 HMAC with the library API key, compare in constant time with the X-Veluraq-Signature header.
2. Ignore duplicate EventId values, switch on Status (3 = ready: update your database), and answer 200 quickly; do heavy work asynchronously.
3. Log failures and explain the retry behaviour (up to 3 attempts) and how to test with the dashboard's test-event button.

The same prompts are available in the dashboard under AI Prompt.