Skip to main content

API Reference

Complete reference for all StreamGate API endpoints. Endpoints are grouped by access level: Public, Admin, Internal, and HLS Streaming.


Public API

These endpoints require no authentication and are used by the viewer's browser.

POST /api/tokens/validate

Validates an access code and returns a JWT playback token.

Rate limit: 5 requests/minute per IP

Request:

curl -X POST http://localhost:3000/api/tokens/validate \
-H "Content-Type: application/json" \
-d '{"code": "Ab3kF9mNx2Qp"}'

Success Response (200):

{
"event": {
"title": "Annual Conference 2025",
"description": "Live keynote stream",
"startsAt": "2025-03-15T09:00:00.000Z",
"endsAt": "2025-03-15T17:00:00.000Z",
"posterUrl": "https://example.com/poster.jpg",
"isLive": true
},
"playbackToken": "eyJhbGciOiJIUzI1NiJ9...",
"playbackBaseUrl": "http://localhost:4000",
"streamPath": "/streams/abc-123-uuid/",
"expiresAt": "2025-03-17T17:00:00.000Z",
"tokenExpiresIn": 3600
}
note

playbackBaseUrl is dynamically derived from the request's hostname. If the request comes from 192.168.0.11:3000, the response returns http://192.168.0.11:4000 instead of http://localhost:4000.

Error Responses:

StatusConditionBody
400Missing or non-alphanumeric code{ "error": "Access code is required" }
401Code not found in database{ "error": "Invalid access code" }
403Token is revoked{ "error": "Access code has been revoked" }
403Event is deactivated{ "error": "This event is not currently available" }
409Token in use on another device{ "error": "This access code is currently in use on another device", "inUse": true }
410Token has expired{ "error": "Access code has expired" }
429Rate limited{ "error": "Too many requests. Please try again later." }

POST /api/playback/refresh

Refreshes an expiring JWT. The current JWT is sent as a Bearer token.

Rate limit: 12 requests/hour per token code

Request:

curl -X POST http://localhost:3000/api/playback/refresh \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..."

Success Response (200):

{
"playbackToken": "eyJhbGciOiJIUzI1NiJ9...(new token)",
"tokenExpiresIn": 3600
}

Error Responses:

StatusConditionBody
401Missing or invalid JWT{ "error": "Valid playback token required" }
403Token has been revoked since last refresh{ "error": "Access has been revoked" }
410Token or event has expired{ "error": "Access has expired" }
429Rate limited{ "error": "Too many refresh requests" }

POST /api/playback/heartbeat

Keeps the active session alive. Called every 30 seconds by the player.

Request:

curl -X POST http://localhost:3000/api/playback/heartbeat \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..."

Success Response (200):

{
"ok": true
}

Error Responses:

StatusConditionBody
401Missing or invalid JWT{ "error": "Valid playback token required" }
404Session no longer exists{ "error": "Session not found" }

POST /api/playback/release

Releases the active session. Called on player close via navigator.sendBeacon().

Request:

curl -X POST http://localhost:3000/api/playback/release \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..."

Success Response (200):

{
"released": true
}

Error Responses:

StatusConditionBody
401Missing or invalid JWT{ "error": "Valid playback token required" }
info

If the session was already released or timed out, the endpoint still returns { "released": true } to avoid unnecessary errors in the client.


GET /api/events/:id/status

Returns the current status of an event. Used by the pre-event waiting screen.

Request:

curl http://localhost:3000/api/events/abc-123-uuid/status

Success Response (200):

{
"eventId": "abc-123-uuid",
"status": "live",
"startsAt": "2025-03-15T09:00:00.000Z",
"endsAt": "2025-03-15T17:00:00.000Z"
}

The status field is one of: not-started, live, ended, recording.

Error Responses:

StatusConditionBody
404Event not found{ "error": "Event not found" }

Admin API

All admin endpoints require an authenticated session via the iron-session cookie. Send credentials first via /api/admin/login.

Authentication

POST /api/admin/login

Rate limit: 10 requests/minute per IP

curl -X POST http://localhost:3000/api/admin/login \
-H "Content-Type: application/json" \
-d '{"password": "your-admin-password"}' \
-c cookies.txt

Success (200): { "success": true }

Error:

StatusConditionBody
401Wrong password{ "error": "Invalid password" }
429Rate limited{ "error": "Too many login attempts" }

POST /api/admin/logout

curl -X POST http://localhost:3000/api/admin/logout -b cookies.txt

Response (200): { "success": true }

GET /api/admin/session

Check if the current session is authenticated:

curl http://localhost:3000/api/admin/session -b cookies.txt

Response (200): { "authenticated": true }


Events

GET /api/admin/events

List all events:

curl http://localhost:3000/api/admin/events -b cookies.txt

Response (200):

{
"events": [
{
"id": "abc-123",
"title": "Annual Conference 2025",
"description": "Live keynote stream",
"streamUrl": null,
"posterUrl": null,
"startsAt": "2025-03-15T09:00:00.000Z",
"endsAt": "2025-03-15T17:00:00.000Z",
"accessWindowHours": 48,
"isActive": true,
"isArchived": false,
"createdAt": "2025-03-01T12:00:00.000Z",
"updatedAt": "2025-03-01T12:00:00.000Z",
"_count": { "tokens": 100 }
}
]
}

POST /api/admin/events

Create a new event:

curl -X POST http://localhost:3000/api/admin/events \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{
"title": "Annual Conference 2025",
"description": "Live keynote stream",
"startsAt": "2025-03-15T09:00:00.000Z",
"endsAt": "2025-03-15T17:00:00.000Z",
"accessWindowHours": 48,
"streamUrl": "https://origin.example.com/live/stream.m3u8",
"posterUrl": "https://example.com/poster.jpg"
}'

Response (201): Full event object.

GET /api/admin/events/:id

Get single event with details:

curl http://localhost:3000/api/admin/events/abc-123 -b cookies.txt

PUT /api/admin/events/:id

Update an event:

curl -X PUT http://localhost:3000/api/admin/events/abc-123 \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{ "title": "Updated Title", "accessWindowHours": 72 }'

DELETE /api/admin/events/:id

Delete an event and all associated tokens (cascading delete):

curl -X DELETE http://localhost:3000/api/admin/events/abc-123 -b cookies.txt

Response (200): { "deleted": true }

PATCH /api/admin/events/:id/activate

curl -X PATCH http://localhost:3000/api/admin/events/abc-123/activate -b cookies.txt

PATCH /api/admin/events/:id/deactivate

Deactivating an event effectively revokes all its tokens (picked up by HLS revocation sync).

curl -X PATCH http://localhost:3000/api/admin/events/abc-123/deactivate -b cookies.txt

PATCH /api/admin/events/:id/archive

curl -X PATCH http://localhost:3000/api/admin/events/abc-123/archive -b cookies.txt

PATCH /api/admin/events/:id/unarchive

curl -X PATCH http://localhost:3000/api/admin/events/abc-123/unarchive -b cookies.txt

Tokens

GET /api/admin/events/:id/tokens

List tokens for a specific event:

curl http://localhost:3000/api/admin/events/abc-123/tokens -b cookies.txt

Response (200):

{
"tokens": [
{
"id": "tok-uuid",
"code": "Ab3kF9mNx2Qp",
"eventId": "abc-123",
"label": "VIP Guest 1",
"isRevoked": false,
"revokedAt": null,
"redeemedAt": "2025-03-15T10:30:00.000Z",
"redeemedIp": "192.168.1.50",
"expiresAt": "2025-03-17T17:00:00.000Z",
"createdAt": "2025-03-01T12:00:00.000Z"
}
]
}

POST /api/admin/events/:id/tokens

Generate tokens for an event (batch: 1–500):

curl -X POST http://localhost:3000/api/admin/events/abc-123/tokens \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{ "count": 50, "label": "Batch A" }'

Response (201):

{
"tokens": [
{ "id": "tok-1", "code": "Ab3kF9mNx2Qp", "label": "Batch A", ... },
{ "id": "tok-2", "code": "Xp2mNqR7sT4w", "label": "Batch A", ... }
],
"count": 50
}

GET /api/admin/events/:id/tokens/export

Export tokens as CSV:

curl http://localhost:3000/api/admin/events/abc-123/tokens/export \
-b cookies.txt -o tokens.csv

Returns CSV with headers: code,label,status,createdAt,expiresAt

GET /api/admin/tokens

List all tokens across all events (with optional filters):

curl "http://localhost:3000/api/admin/tokens?status=revoked&eventId=abc-123" \
-b cookies.txt

PATCH /api/admin/tokens/:id/revoke

Revoke a single token:

curl -X PATCH http://localhost:3000/api/admin/tokens/tok-uuid/revoke -b cookies.txt

Response (200): Updated token object with isRevoked: true and revokedAt timestamp.

PATCH /api/admin/tokens/:id/unrevoke

Unrevoke a previously revoked token:

curl -X PATCH http://localhost:3000/api/admin/tokens/tok-uuid/unrevoke -b cookies.txt

POST /api/admin/tokens/bulk-revoke

Revoke multiple tokens at once:

curl -X POST http://localhost:3000/api/admin/tokens/bulk-revoke \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{ "tokenIds": ["tok-1", "tok-2", "tok-3"] }'

Response (200): { "revoked": 3 }

GET /api/admin/dashboard

Get dashboard statistics:

curl http://localhost:3000/api/admin/dashboard -b cookies.txt

Response (200):

{
"totalEvents": 12,
"activeEvents": 5,
"totalTokens": 500,
"redeemedTokens": 150,
"activeViewers": 42
}

Internal API

Used by the HLS Media Server for revocation synchronization. Authenticated via X-Internal-Api-Key header.

GET /api/revocations

Fetches token revocations and event deactivations since a given timestamp.

Request:

curl "http://localhost:3000/api/revocations?since=2025-03-15T10:00:00.000Z" \
-H "X-Internal-Api-Key: your-internal-api-key"

Query Parameters:

ParameterTypeRequiredDescription
sinceISO 8601 stringYesOnly return revocations after this timestamp

Success Response (200):

{
"revocations": [
{
"code": "Ab3kF9mNx2Qp",
"revokedAt": "2025-03-15T11:30:00.000Z"
},
{
"code": "Xp2mNqR7sT4w",
"revokedAt": "2025-03-15T12:00:00.000Z"
}
],
"eventDeactivations": [
{
"eventId": "evt-456",
"deactivatedAt": "2025-03-15T11:45:00.000Z",
"tokenCodes": ["Cd4eF5gH6iJk", "Lm7nO8pQ9rSt"]
}
],
"serverTime": "2025-03-15T12:05:00.000Z"
}

Error Responses:

StatusConditionBody
401Missing or invalid X-Internal-Api-Key{ "error": "Unauthorized" }
400Missing since parameter{ "error": "since parameter required" }
info

The serverTime field in the response should be used as the since value for the next poll. This ensures no revocations are missed between polls, even if clocks are slightly out of sync.


HLS Streaming API

These endpoints are served by the HLS Media Server (default port 4000).

GET /streams/:eventId/*.m3u8

Serves HLS manifest files. Requires JWT authorization.

Request:

curl http://localhost:4000/streams/abc-123/stream.m3u8 \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..."

Response: HLS manifest content with Content-Type: application/vnd.apple.mpegurl.

Error Responses:

StatusConditionBody
401No JWT provided{ "error": "Authorization required" }
403JWT invalid/expired/revoked/wrong path{ "error": "Access denied" }
404Manifest file not found{ "error": "Not found" }

GET /streams/:eventId/*.ts

Serves HLS transport stream segments. Same auth as manifests.

curl http://localhost:4000/streams/abc-123/segment-001.ts \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..." \
-o segment.ts

GET /health

Health check endpoint (no auth required):

curl http://localhost:4000/health

Response (200):

{
"status": "ok",
"mode": "hybrid",
"revocationCacheSize": 42,
"lastSyncAgoSeconds": 15
}

DELETE /admin/cache/:eventId

Clears cached segments for an event (no JWT auth — restrict via network policy):

curl -X DELETE http://localhost:4000/admin/cache/abc-123

Response (200): { "cleared": true }

warning

This endpoint is not protected by JWT authentication. In production, restrict access via firewall rules or reverse proxy configuration.