Video API
Use the Video API to upload and manage video assets, captions, player settings, webhooks, and usage estimates.
Base URL and authentication
The API base URL is:
https://rabata.io/api/v1/video
Create a Video API key from Dashboard → Video → API keys. Send it as a Bearer token with every request:
curl https://rabata.io/api/v1/video/assets \
--header "Authorization: Bearer YOUR_VIDEO_API_KEY"
Keep the key in a secret manager and send it only over HTTPS. A missing, malformed, disabled, or unknown key returns 401 Unauthorized.
Requests are limited to 60 per minute for each authenticated account. Missing
or invalid credentials consume a per-IP allowance rather than bypassing the
limit. The account allowance is shared across all of its Video API keys. Every
response includes X-RateLimit-Limit, X-RateLimit-Remaining, and
X-RateLimit-Reset (a Unix timestamp). A throttled request returns 429 Too Many Requests with Retry-After and the standard JSON error body.
Browser applications can call the API cross-origin. Preflight responses allow
the Bearer Authorization, Content-Type, and Idempotency-Key headers. The
API exposes the rate-limit headers to browser clients and does not use
credentialed cookies.
Errors
Successful requests return JSON. Errors use this shape:
{
"error": "A description of the problem"
}
Common statuses are:
| Status | Meaning |
|---|---|
400 |
The request could not be parsed. |
401 |
The Bearer token is missing or invalid. |
403 |
The account is blocked, the Video subscription is inactive, or a metered operation has no active card or spendable promotional credit. |
404 |
The requested resource does not exist in the authenticated account. |
409 |
An idempotency key was reused for a different asset-create request. |
415 |
A JSON mutation used an unsupported Content-Type. |
422 |
A parameter, file, or resource state is invalid. |
429 |
The request limit was exceeded. |
503 |
A dependent storage service is temporarily unavailable. |
Resource identifiers are UUIDs. A valid UUID owned by another account is treated as not found.
Send Content-Type: application/json with POST, PUT, and PATCH requests,
except caption uploads, which use multipart form data.
Assets
List assets
GET /api/v1/video/assets?page=1&limit=20
page starts at 1. limit defaults to 20 and is constrained to 1–100.
{
"data": [
{
"id": "ASSET_UUID",
"title": "Product tour",
"status": "ready",
"original_filename": "tour.mp4",
"file_size": 73400320,
"duration": 83.4,
"width": 1920,
"height": 1080,
"thumbnail_url": "https://...",
"allowed_domains": ["example.com"],
"created_at": "2026-07-30T10:00:00Z",
"embed_url": "https://rabata.io/embed/ASSET_UUID",
"stream_url": "https://..."
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 1,
"pages": 1
}
}
Retrieve one asset with:
GET /api/v1/video/assets/ASSET_UUID
stream_url is returned only when the asset is ready and its owner has valid,
funded Video access. If metered access is paused because the account has no active
card, active legacy trial, or at least $0.01 of promotional credit remaining,
list and show
requests still return asset metadata but omit stream_url; embeds also refuse
playback. Mutating requests return 403 until funding is restored.
Assets with status: "errored" include error_code and error_message in list
and detail responses. These contain the stored preparation failure code and a
safe explanation, for example InvalidMedia and
Video could not be processed. Upload a valid video at least 360 pixels high.
Either field can be null for older failures without recorded details. Other
statuses omit both fields. A failed asset still returns HTTP 200 when retrieved;
the error describes media preparation, not the API request.
Player settings in asset responses
Asset responses include two objects with the same six setting names:
-
player_settings: the asset's stored overrides. Anullvalue means inherit the account default. -
effective_player_settings: the resolved values used by the player. Asset overrides take precedence over account settings, then system defaults. Boolean values are alwaystrueorfalse;accent_coloris a hex color.
These objects are returned in asset list, detail and update responses. For example, an asset inheriting every setting from an account using system defaults returns:
{
"player_settings": {
"accent_color": null,
"autoplay": null,
"muted": null,
"loop_video": null,
"show_controls": null,
"show_title": null
},
"effective_player_settings": {
"accent_color": "#28CDD9",
"autoplay": false,
"muted": false,
"loop_video": false,
"show_controls": true,
"show_title": false
}
}
Read effective_player_settings to use the current values without a second
request. To change an override, PATCH player_settings; send null to reset
that field to inheritance. Explicit false remains an override. The effective
object is read-only, and subsequent requests reflect account-default changes
for inherited fields.
Upload an asset
Uploads go directly to object storage with presigned multipart URLs.
-
Create the asset and upload session:
curl https://rabata.io/api/v1/video/assets \ --request POST \ --header "Authorization: Bearer YOUR_VIDEO_API_KEY" \ --header "Content-Type: application/json" \ --header "Idempotency-Key: YOUR_UNIQUE_UPLOAD_REQUEST_ID" \ --data '{ "title": "Product tour", "original_filename": "tour.mp4", "file_size": 73400320 }'file_sizeis in bytes. Supported extensions are.mp4,.mov,.avi,.mkv,.webm,.flv,.wmv,.m4v,.3gp, and.ogv. The maximum file size is 10 GB.The response contains the new asset
id, anupload.upload_id, theupload.chunk_size, and one presigned URL per part.Use a unique
Idempotency-Keyof 1–255 bytes for every logical create request. Retrying the same payload with the same key returns the original asset and a fresh set of presigned part URLs without creating another asset or multipart session. Reusing the key with different create parameters returns409 Conflict. -
Split the file into chunks of
upload.chunk_sizebytes.PUTeach chunk to the corresponding presigned URL in order, without the Rabata Authorization header. Save theETagresponse header from every upload. -
Complete the upload:
curl https://rabata.io/api/v1/video/assets/ASSET_UUID/confirm \ --request POST \ --header "Authorization: Bearer YOUR_VIDEO_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "upload_id": "UPLOAD_ID", "parts": [ { "part_number": 1, "etag": "PART_1_ETAG" }, { "part_number": 2, "etag": "PART_2_ETAG" } ] }'
The asset moves through waiting, uploaded, processing, and ready. Poll the asset or listen for an asset.ready webhook before using its stream URL. A response with transcoding_started: false includes a warning and can be retried.
If the upload cannot be completed, cancel its multipart session and discard the waiting asset:
POST /api/v1/video/assets/ASSET_UUID/abort
Content-Type: application/json
{
"upload_id": "UPLOAD_ID"
}
Cancellation remains available when the Video subscription has expired. A completed cancellation returns HTTP 200 with success: true, discarded: true, and cleanup_pending: false. If billing suspension prevents storage access, the asset is removed from the library immediately and the API returns HTTP 202 with cleanup_pending: true. Storage cleanup is retried in the background when access returns; this does not remove account restrictions. Repeating the abort request is safe.
Update or delete an asset
Update the domain allowlist or asset-specific player settings:
curl https://rabata.io/api/v1/video/assets/ASSET_UUID \
--request PATCH \
--header "Authorization: Bearer YOUR_VIDEO_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"allowed_domains": ["example.com", "www.example.com"],
"player_settings": {
"accent_color": "#28CDD9",
"autoplay": false,
"muted": false,
"loop_video": false,
"show_controls": true,
"show_title": false
}
}'
An empty allowed_domains array allows embedding from any domain. At most 20 HTTPS hostnames can be supplied; enter hostnames without paths. A hostname without a port permits HTTPS port 443 only. Specify an explicit port, such as intranet.example.com:8443, to allow another HTTPS port (1–65535); wildcard ports are not supported.
*.example.com permits the main site and all of its subdomains. Restrictions are enforced by the browser's Content Security Policy (frame-ancestors), and the recommended embed_html checks the parent origin before loading the iframe so it can show an actionable message on an unlisted site. Legacy iframe-only embeds remain blank when blocked and do not count as a video view.
Delete an asset with:
DELETE /api/v1/video/assets/ASSET_UUID
Deleting an asset removes it from the API and schedules its stored media for cleanup. Deleting a waiting upload follows the cancellation behavior above, including HTTP 202 when storage cleanup is pending.
Get embed code
When the asset is ready:
GET /api/v1/video/assets/ASSET_UUID/embed
The response contains embed_url, the original iframe string, and embed_html.
Use embed_html for the recommended iframe plus viewer-identity script. The
dashboard's Copy embed code action copies this enhanced snippet too.
The original iframe and embed_url remain available for integrations that
cannot include scripts. See Viewer identity for their limits.
Manage a custom thumbnail
Request a presigned image upload:
POST /api/v1/video/assets/ASSET_UUID/thumbnail
Content-Type: application/json
{
"filename": "poster.jpg",
"content_type": "image/jpeg",
"file_size": 245760
}
Upload the image to the returned url, then confirm its returned storage key:
PUT /api/v1/video/assets/ASSET_UUID/thumbnail
Content-Type: application/json
{
"key": "RETURNED_STORAGE_KEY"
}
Remove the custom thumbnail with:
DELETE /api/v1/video/assets/ASSET_UUID/thumbnail
Captions
List or retrieve captions:
GET /api/v1/video/assets/ASSET_UUID/captions
GET /api/v1/video/assets/ASSET_UUID/captions/CAPTION_UUID
Upload a WebVTT or SubRip file as multipart form data:
curl https://rabata.io/api/v1/video/assets/ASSET_UUID/captions \
--request POST \
--header "Authorization: Bearer YOUR_VIDEO_API_KEY" \
--form "file=@captions.vtt" \
--form "label=English" \
--form "language=en"
Caption files can be up to 2 MB. Language values use a lowercase BCP 47 tag such as en, en-us, or pt-br. Each asset can have one active caption per language and up to 30 captions.
Delete a caption with:
DELETE /api/v1/video/assets/ASSET_UUID/captions/CAPTION_UUID
Player defaults
Get the account-wide player defaults:
GET /api/v1/video/player_config
Update any subset of the settings:
PATCH /api/v1/video/player_config
Content-Type: application/json
{
"accent_color": "#28CDD9",
"autoplay": false,
"muted": false,
"loop_video": false,
"show_controls": true,
"show_title": false
}
Asset-specific player settings override these account defaults.
Analytics
Get views and watch-time totals plus a time series:
GET /api/v1/video/assets/ASSET_UUID/analytics?from=2026-07-01&to=2026-07-30&granularity=day
granularity can be day or hour; it defaults to day. from defaults to 30 days ago and to defaults to now.
The summary and each time-series point contain separate unique_viewers and
playback_sessions values. Unique viewers are deduplicated across the selected
time range, not calculated by adding daily unique counts. The library dashboard
also deduplicates a viewer who watches multiple videos belonging to one customer.
Viewer identity
The player uses a new session ID on each page load. Pause/resume does not start another view on that page. A separate persistent viewer ID lets a returning browser count once across multiple sessions. Anonymous browser IDs estimate viewers: shared browsers, clearing storage, expiry, and browser restrictions affect that estimate. They do not identify a person across devices.
The recommended embed_html includes /video-viewer.js. This script stores a
random ID in the embedding site's first-party local storage, scoped to the
Rabata origin and customer. IDs expire after one year. Existing iframe-only
embeds use storage inside the iframe, additionally scoped to the embedding
origin; browsers may partition or block that storage. If storage or the expected
bridge is unavailable, playback and session analytics continue without a viewer
ID. Those plays are not counted as unique viewers.
For signed-in viewers, set data-viewer-id on the generated iframe before loading
it. Use a stable, opaque ID from your application, never an email address, name,
username, or other personal detail. Accepted IDs contain 1–128 letters, digits,
underscores, or hyphens. A customer-supplied ID can identify the same viewer across
devices and sites within that customer's library.
Keep the generated data-viewer-scope unchanged; the placeholder below means
the customer UUID already included in embed_html.
<iframe data-rabata-video data-viewer-id="u_7bd123e9"
data-viewer-scope="CUSTOMER_UUID_FROM_EMBED_HTML"
data-rabata-src="https://rabata.io/embed/ASSET_UUID?viewer_bridge=1"
width="640" height="360" allow="autoplay; fullscreen" allowfullscreen></iframe>
<script defer src="https://rabata.io/video-viewer.js"></script>
The script verifies the embedding origin, loads the iframe when it is allowed, and passes the ID directly to the iframe with origin-checked messages; do not put viewer IDs in URLs. Rabata stores a customer-scoped keyed hash, not the supplied ID. Anonymous and customer-supplied IDs use different namespaces. These are pseudonymous analytics identifiers, not authentication credentials.
To disable anonymous persistence, add disable_cookies=true to the iframe URL
and data-disable-cookies="true" to the enhanced iframe. Despite the option's
name, it disables the player's local-storage persistence too. Apply it before
loading the iframe, and connect this setting to your site's consent controls.
An explicit data-viewer-id still identifies a signed-in viewer; omit it as well
when you do not want persistent viewer identification. To change identity or
these options, reload the iframe so the current session never mixes viewers.
Historical events without a viewer ID remain playback-session data and are not backfilled as unique users. The unique-viewer metric covers identified plays recorded after viewer tracking was enabled; it can therefore be lower than playback sessions. It counts viewer IDs from view starts, not every heartbeat.
Get the audience-retention curve for the same period:
GET /api/v1/video/assets/ASSET_UUID/retention?from=2026-07-01&to=2026-07-30
Usage and pricing
Retrieve the current calendar month's completed usage and estimated charges:
GET /api/v1/video/usage
To retrieve a previous month, pass month in YYYY-MM format:
GET /api/v1/video/usage?month=2026-08
Omitting month selects the current month. Invalid or future months return HTTP
400. Both data and summary refer to the selected month.
summary.status is estimated for the current month, pending for a past month
without a finalized billing snapshot, and finalized when a snapshot exists.
Finalized responses use the usage, rates, and costs saved when the invoice was
created, even if today's prices or subscription have changed. invoice_id
identifies the associated invoice, or is null when none is attached.
For compatibility, estimated_cost and estimated_total retain their names in
finalized responses. They then contain the saved Video charges before promotional
credit, tax, and invoice-level rounding; other services on the same invoice are
not included. source_storage.current_bytes is live storage for the current month
and null for historical months; average_bytes describes the selected period.
The data array retains its existing record format. The summary object contains
the usage-pricing contract:
{
"data": [
{
"period_start": "2026-07-31T00:00:00Z",
"period_end": "2026-07-31T23:59:59Z",
"storage_bytes": 1200000000,
"egress_bytes": 410000000
}
],
"summary": {
"period_start": "2026-07-01",
"period_end": "2026-07-31",
"data_through": "2026-07-30",
"status": "estimated",
"invoice_id": null,
"currency": "usd",
"source_storage": {
"current_bytes": 1200000000,
"average_bytes": "1000000000.000000",
"rate_per_tb_month": "6.000000",
"rate_per_gb_month": "0.006000",
"estimated_cost": "0.0060"
},
"delivery": {
"720p": {
"seconds": "6000.000",
"minutes": "100.0",
"rate_per_minute": "0.000600",
"estimated_cost": "0.0600"
},
"1080p": {
"seconds": "0.000",
"minutes": "0.0",
"rate_per_minute": "0.000800",
"estimated_cost": "0.0000"
},
"2k": {
"seconds": "0.000",
"minutes": "0.0",
"rate_per_minute": "0.001300",
"estimated_cost": "0.0000"
},
"4k": {
"seconds": "0.000",
"minutes": "0.0",
"rate_per_minute": "0.002500",
"estimated_cost": "0.0000"
}
},
"estimated_total": "0.0660",
"transcoding": {
"standard_on_demand_included": true
}
}
}
rate_per_tb_month is the canonical source-storage price. The deprecated
rate_per_gb_month field remains temporarily for existing clients and expresses
the equivalent rate in the API's legacy decimal-GB unit.
delivery always contains the 720p, 1080p, 2k, and 4k rate tiers. data_through is the last contiguous completed UTC day, or null when no day is ready. For a finalized month, it equals period_end. Current-month values are estimates; invoice charges are finalized only after every billable day in the month is complete.
Webhooks
Create a public HTTP or HTTPS callback for any combination of asset.created, asset.ready, asset.errored, and asset.deleted. Use HTTPS in production:
curl https://rabata.io/api/v1/video/webhooks \
--request POST \
--header "Authorization: Bearer YOUR_VIDEO_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"callback_url": "https://example.com/rabata-webhooks",
"events": ["asset.ready", "asset.errored"]
}'
The callback hostname must resolve only to public IP addresses. An empty events array subscribes to all supported events.
Each account may register a normalized callback URL only once. Host casing, default HTTP/HTTPS ports, and a trailing slash do not make separate webhook registrations.
The create response returns the signing secret once; store it in your secret manager because later responses do not expose it.
List, retrieve, update, or delete webhook configurations with:
GET /api/v1/video/webhooks
GET /api/v1/video/webhooks/WEBHOOK_UUID
PATCH /api/v1/video/webhooks/WEBHOOK_UUID
DELETE /api/v1/video/webhooks/WEBHOOK_UUID
Delivery requests use Content-Type: application/json, identify the event in X-Rabata-Event, and include an HMAC-SHA256 digest in X-Rabata-Signature. Respond with a 2xx status after accepting the event. Failed deliveries are retried with backoff.
The asset.errored payload includes error_code and error_message alongside
event, asset_id, status, and timestamp. These use the same stored
preparation details as asset responses and can be null when no details were
recorded. The error is captured when the event is queued, so delivery retries
retain the original explanation. Other webhook events omit these fields.