API reference¶
Endpoint paths are relative to the Rightra API base URL. All requests and responses use JSON unless noted.
Authentication¶
Send the API key as a bearer token:
Each endpoint requires a scope. Create keys and assign scopes in the console.
| Scope | Permits |
|---|---|
content:read |
Reading content items. |
content:write |
Registering content, setting manifests, queuing fingerprints. |
key:write |
Storing encryption keys. |
playback:create |
Creating sessions, recording events, requesting downloads. |
engine:extract |
Running the watermark extractor. |
Content IDs¶
The server generates a content ID in the form RTR-XXXX-XXXX (uppercase hexadecimal) when a work is registered. IDs cannot be chosen.
Content¶
Register content¶
Scope: content:write
| Field | Required | Description |
|---|---|---|
title |
Yes | Title of the work. |
type |
Yes | Content type, for example film. |
description |
No | Free-text description. |
creator_name |
No | Creator name. |
rights_holder_name |
No | Rights holder name. |
storage_reference |
No | Storage descriptor, for example { "provider": "php" }. |
encryption |
No | Encryption settings: enabled, algorithm, segment_seconds. |
{
"title": "My Film",
"type": "film",
"description": "Feature length film.",
"creator_name": "Example Studio",
"rights_holder_name": "Example Studio",
"storage_reference": { "provider": "php" },
"encryption": { "enabled": true, "algorithm": "AES-256-GCM", "segment_seconds": 4 }
}
Response 201:
{
"success": true,
"content": {
"content_id": "RTR-853D-52D0",
"content_uid": "uuid",
"title": "My Film",
"type": "film",
"status": "registered",
"encryption_enabled": true,
"environment": "live",
"created_at": "2026-10-18 10:00:00"
}
}
Get content¶
Scope: content:read
Response 200:
{
"success": true,
"content": {
"content_id": "RTR-853D-52D0",
"title": "My Film",
"storage_reference": { "manifest_url": "https://your-storage.example/manifest.json" },
"encryption_enabled": true,
"status": "protected"
}
}
Set manifest URL¶
Scope: content:write
{
"manifest_url": "https://your-storage.example/manifest.json",
"storage_reference": { "provider": "php" }
}
Response 200:
Store encryption key¶
Scope: key:write
{
"key_material": "base64-encoded-32-byte-key",
"key_algorithm": "AES-256-GCM",
"segment_manifest": { "segments": [] }
}
Response 201:
A content item has one active key. A second request returns 409 KEY_ALREADY_SET.
Queue fingerprint¶
Scope: content:write
Response 201:
Playback¶
Create a playback session¶
Scope: playback:create
| Field | Required | Description |
|---|---|---|
content_id |
Yes | Content ID. |
viewer.id |
Yes | Stable viewer identifier. The request fails with 400 VIEWER_REQUIRED if omitted. |
viewer.label |
No | Display label. |
requested_territory |
No | Two-letter territory code. |
client_public_key |
No | Base64 P-256 public key. When provided, the content key is returned wrapped in key_wrapped (ECDH P-256, HKDF-SHA256). When omitted, it is returned in session_key. |
device |
No | Client details such as user_agent and screen. |
{
"content_id": "RTR-853D-52D0",
"viewer": { "id": "user-123", "label": "Jane" },
"requested_territory": "RW",
"client_public_key": "base64-encoded-p256-public-key",
"device": { "user_agent": "Mozilla/5.0 ...", "screen": "1920x1080" }
}
Response 200:
{
"success": true,
"session_id": "sess_abcdef1234567890abcdef12",
"license_expires_at": "2026-10-18T10:02:00.000Z",
"watermark_payload": "base64-signed-payload",
"watermark_code": 123456789,
"download_allowed": true,
"allowed": true,
"key_wrapped": {
"server_public_key": "base64",
"iv": "base64",
"ciphertext": "base64"
}
}
Sessions expire after 120 seconds. Any playback event other than ended extends the expiry.
Record a playback event¶
Scope: playback:create
Valid events: start, progress, heartbeat, pause, seek, ended, error. The ended event closes the session.
Response 200:
Record a download attempt¶
Scope: playback:create
Logs an action that appears to be an attempt to save the media. Attempts appear per content item on the Playback tab of the console.
{
"session_id": "sess_abcdef1234567890abcdef12",
"attempt_type": "right_click",
"details": { "target": "video" }
}
Response 200:
Downloads¶
Request a download¶
Scope: playback:create
Response 202:
The server decrypts the segments, applies a download-specific watermark, and produces an MP4.
Get download status¶
Scope: playback:create
Response 200:
Statuses: queued, running, ready, failed, expired.
Fetch the download¶
Scope: playback:create
Returns the MP4 as application/octet-stream. The server deletes the file after it is sent.
Engine¶
Extract a watermark¶
Scope: engine:extract
| Field | Description |
|---|---|
file |
The file to inspect. |
The extractor scans audio first, then video. If a code is found, the response includes the matching session, viewer, content, and timestamp. Each run is stored. See Watermarks.
Errors¶
Errors return success: false, a machine-readable code, and a message suitable for display.
Status codes¶
| Status | Meaning |
|---|---|
400 |
A field is missing or malformed. |
401 |
The API key is missing or invalid. |
403 |
The API key lacks the required scope. |
404 |
The content, session, or job does not exist for this key. |
409 |
The request conflicts with current state. |
410 |
The session has expired. |
413 |
The request body is too large. |
429 |
Too many downloads are in progress. Retry later. |
500 |
Server error. The message is safe to display. |
Error codes¶
| Code | Meaning |
|---|---|
VIEWER_REQUIRED |
A playback session was requested without viewer.id. |
CONTENT_NOT_FOUND |
The content ID does not belong to this key. |
CONTENT_NOT_ENCRYPTED |
Playback or download was requested before a key was stored. |
KEY_ALREADY_SET |
A key already exists for this content. |
SESSION_EXPIRED |
The session has expired or does not exist. |
DOWNLOAD_NOT_ALLOWED |
Downloads are disabled for this content. |
VALIDATION_FAILED |
The request body is malformed. |