Skip to content

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:

Authorization: Bearer rk_live_your_key

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

POST /sdk/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

GET /sdk/content/{contentId}

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

POST /sdk/content/{contentId}/manifest

Scope: content:write

{
  "manifest_url": "https://your-storage.example/manifest.json",
  "storage_reference": { "provider": "php" }
}

Response 200:

{ "success": true, "manifest_url": "https://your-storage.example/manifest.json" }

Store encryption key

POST /sdk/content/{contentId}/key

Scope: key:write

{
  "key_material": "base64-encoded-32-byte-key",
  "key_algorithm": "AES-256-GCM",
  "segment_manifest": { "segments": [] }
}

Response 201:

{ "success": true, "key_version": 1, "status": "protected" }

A content item has one active key. A second request returns 409 KEY_ALREADY_SET.

Queue fingerprint

POST /sdk/content/{contentId}/fingerprint

Scope: content:write

{
  "reference_material": "base64-encoded-sample",
  "reference_type": "audio_sample"
}

Response 201:

{ "success": true, "fingerprint_id": 12, "status": "queued" }

Playback

Create a playback session

POST /sdk/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

POST /sdk/playback-event

Scope: playback:create

{
  "session_id": "sess_abcdef1234567890abcdef12",
  "event": "heartbeat",
  "seconds_watched": 30
}

Valid events: start, progress, heartbeat, pause, seek, ended, error. The ended event closes the session.

Response 200:

{ "success": true }

Record a download attempt

POST /sdk/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:

{ "success": true }

Downloads

Request a download

POST /sdk/download-request

Scope: playback:create

{ "session_id": "sess_abcdef1234567890abcdef12" }

Response 202:

{ "success": true, "job_id": "job_abcdef123456" }

The server decrypts the segments, applies a download-specific watermark, and produces an MP4.

Get download status

GET /sdk/download-status/{jobId}

Scope: playback:create

Response 200:

{ "success": true, "status": "ready", "progress": 1.0 }

Statuses: queued, running, ready, failed, expired.

Fetch the download

GET /sdk/download-file/{jobId}

Scope: playback:create

Returns the MP4 as application/octet-stream. The server deletes the file after it is sent.

Engine

Extract a watermark

POST /engine/extract
Content-Type: multipart/form-data

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.

{
  "success": false,
  "code": "CONTENT_NOT_FOUND",
  "message": "We could not find that content."
}

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.