BharatCall Developers All developer tools BharatCall Videos Get an API key

Video API & Player

Host your videos on BharatCall and play them on your own websites, with your colours and none of the clutter. Upload from your own code, control the player with JavaScript, and see which websites your plays come from.

Embed anywhereA responsive player for any website, with autoplay, loop, background mode and your brand colour.
Privacy you controlUnlisted, hidden from BharatCall, password, and "only on my domains".
Upload by APISend a file, or let us fetch it from a link (move from Vimeo in minutes).
A modern playerAuto quality up to 1080p, subtitles, dubbed audio, chapters, seek previews, call-to-action and more.

Base URL for every API call: https:/api/v1. All requests and responses are JSON.

Quickstart

  1. Open Studio › Developer and press Create API key. Pick what it may do and copy the key (it starts with bcv_ and is only shown once).
  2. Check it works:
curl https:/api/v1/me \
  -H "Authorization: Bearer bcv_YOUR_KEY"
  1. Add a video to your channel from a public file link:
curl -X POST https:/api/v1/videos \
  -H "Authorization: Bearer bcv_YOUR_KEY" -H "Content-Type: application/json" \
  -d '{"name": "Our new collection", "privacy": "unlisted",
       "upload": {"approach": "pull", "link": "https://example.com/video.mp4"}}'
  1. Put it on your website (use the id from the answer):
<div style="position:relative;padding-top:56.25%">
  <iframe src="https:/embed/VIDEO_ID?color=e8590c" style="position:absolute;inset:0;width:100%;height:100%;border:0"
          allow="autoplay; fullscreen; picture-in-picture; encrypted-media" allowfullscreen title="Video"></iframe>
</div>

Authentication

Send your key in the Authorization header of every request: Authorization: Bearer bcv_…. Keys act as your account, so keep them on your server (never in website JavaScript or an app anyone can download). Revoke a key any time in Studio › Developer; it stops working at once.

Each key has permissions (scopes). Ask only for what your code needs:

ScopeAllows
readSee your videos and stats
uploadUpload new videos to your channel
editChange titles, privacy, subtitles and embed settings
deleteDelete videos

Rate limits & errors

Each key can make 1000 requests an hour. Every answer carries X-RateLimit-Limit and X-RateLimit-Remaining; after the limit you get 429 with a Retry-After header (seconds).

Errors look the same everywhere:

{ "error": "Video not found.", "error_code": 404, "documentation": "https:/developers/video-api" }
StatusMeaning
400Something in your request is missing or wrong; error says what.
401No key, or the key is wrong or revoked.
403The key doesn't have the scope this needs, or the account isn't active.
404No such video on your channel.
409Not possible right now (e.g. calling complete before the file arrived).
429Rate limit reached.

Embed a video

Every video has a player at https:/embed/VIDEO_ID. Get the code from the Share › Embed button on a video, from Studio, or from the API. The responsive version fills the width of its box:

<div style="position:relative;padding-top:56.25%">
  <iframe src="https:/embed/VIDEO_ID" style="position:absolute;inset:0;width:100%;height:100%;border:0"
          allow="autoplay; fullscreen; picture-in-picture; encrypted-media" allowfullscreen title="Video"></iframe>
</div>

Or let the Player API script build it for you:

<div data-bharat-id="VIDEO_ID" data-bharat-color="e8590c"></div>
<script src="https:/player/api.js"></script>

Player options

Add them to the embed link, e.g. /embed/VIDEO_ID?autoplay=1&muted=1&color=0f766e&t=1m30s. Use 1/0 for on/off.

OptionDefaultWhat it does
autoplay0Start playing when the page loads. Browsers only allow this muted, so add muted=1.
muted0Start without sound.
loop0Start again at the end.
background0Background video for a website header: autoplay, muted, loop, no controls, no title.
controls10 hides all controls (control it with the Player API).
colorsite colourYour brand colour for the controls, as hex without #, e.g. e8590c.
tStart at a time: 90, 1m30s or 1h2m3s.
title / byline / portrait1Show the video name, "from channel", and the channel picture at the top.
logo1The small BharatCall link in the corner.
speed1Start speed: 0.25 to 2.
qualityautoauto, 1080p, 720p, 480p or 360p.
texttrackTurn on subtitles in a language, e.g. hi (see languages).
chapters1Show chapter marks on the seek bar and the Chapters menu.
cta1Show the video's call-to-action button.
end_screen1At the end: Watch again, more from the channel, Watch on BharatCall.
keyboard1Keyboard shortcuts (Space, K, J, L, F, M, C, arrows, 0–9).
pip1The picture-in-picture button.
autopause1Pause this video when another BharatCall player on the page starts.
dnt0"Do not track": no play counting and nothing saved in the viewer's browser.
api1Allow the Player JavaScript API.
player_idYour name for this player; sent back with every API message.

Privacy & where it plays

Set these in Studio (Edit details › Privacy & embedding) or with PATCH:

SettingValuesEffect
privacypublicEverywhere on BharatCall and in embeds.
unlistedAnyone with the link or an embed can watch. Never shown in feeds, search, your channel page, trending or search engines.
hidden"Hide from BharatCall": plays only in embeds, i.e. on your own website. The watch page is closed.
passwordtext (4+ characters), or empty to removeViewers type it before watching, on BharatCall and in embeds. Password videos aren't listed anywhere.
embed_policyanywhere · nowhere · domainsWhich websites may embed it. With domains, list them in embed_domains (subdomains are included, e.g. shop.in also allows www.shop.in).
How the domain rule works: the embed page sends a Content-Security-Policy: frame-ancestors header listing your domains, so browsers refuse to show it inside any other website, and the page also checks where it was opened from.

oEmbed

Paste a BharatCall link into WordPress, Ghost, Notion and other oEmbed tools and they turn it into a player. To ask yourself:

GET https:/oembed?url=https:/watch/VIDEO_ID&maxwidth=800
{ "type": "video", "provider_name": "BharatCall", "title": "…", "author_name": "…",
  "width": 800, "height": 450, "duration": 120, "thumbnail_url": "…", "html": "<iframe …>" }

Options in the link (autoplay, loop, muted, color, t) are carried into the player.

Player JavaScript API

Control an embedded player from your page, and react to what viewers do. Add the script once:

<iframe id="promo" src="https:/embed/VIDEO_ID" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>
<script src="https:/player/api.js"></script>
<script>
  const player = new BharatPlayer('promo');
  player.on('play', (d) => console.log('Playing from', d.seconds));
  player.on('ended', () => showSignupForm());
  document.querySelector('#jump').onclick = () => player.setCurrentTime(95).then(() => player.play());
</script>

Or create the player in a box: new BharatPlayer('box', { id: 'VIDEO_ID', color: 'e8590c', autoplay: true, muted: true }) (any option works; responsive: false with width/height for a fixed size). Every method returns a Promise. player.ready() resolves once the player is listening.

Methods

MethodResolves with
play() · pause()—
getPaused() · getEnded()true / false
getCurrentTime() · setCurrentTime(seconds)seconds
getDuration()seconds
getVolume() · setVolume(0–1)volume
getMuted() · setMuted(bool)muted
getPlaybackRate() · setPlaybackRate(0.25–2)rate
getLoop() · setLoop(bool)loop
getQualities() · setQuality('auto' | '720' | 'orig')[{id, label, active}] / chosen id
getTextTracks() · enableTextTrack('hi') · disableTextTrack()[{language, label, mode}]
getAudioTracks() · setAudioTrack('ta' | '')[{language, label, enabled}]
getChapters() · getCurrentChapter()[{startTime, title, index}]
requestFullscreen() · exitFullscreen()—
requestPictureInPicture() · exitPictureInPicture()—
getVideoId() · getVideoTitle() · getVideoUrl()text
getVideoWidth() · getVideoHeight()pixels
getColor() · setColor('0f766e')colour
on(event, fn) · off(event, fn?) · destroy()—

Events

EventData
ready · loaded{id} · {id, duration}
play · playing · pause · ended{seconds, duration}
timeupdate (4 a second){seconds, duration, percent}
progress{percent} buffered
seeking · seeked{seconds}
volumechange{volume, muted}
playbackratechange{playbackRate}
qualitychange · texttrackchange · chapterchange{quality} · {language, label} · {index, title, startTime}
bufferstart · bufferend · error— · — · {message}
ctaclick{url} the viewer pressed your call-to-action
enterpictureinpicture · leavepictureinpicture · fullscreenchange{fullscreen}

Without the script: post {method: 'play'} to the iframe's contentWindow; answers come back as {method, value, id} and events as {event, data, player_id} (send {method: 'addEventListener', value: 'timeupdate'} first).

What the player does

Auto quality1080p / 720p / 480p / 360p versions are made after upload; Auto picks for the screen and connection.
SubtitlesAny number of languages, four text sizes, drawn above the controls.
Dubbed audioViewers switch the audio language while the video keeps playing.
ChaptersMarks on the seek bar and a Chapters menu; written in Studio or as "0:00 Intro" lines in the description.
Seek previewsHover the seek bar to see the frame at that moment.
Call to actionA button ("Shop now", "Book a demo") from a moment you choose.
ResumeReturning viewers continue where they stopped.
Made for phonesTap to show controls, double-tap the sides to skip 10 seconds.
Embed analyticsStudio shows plays per website.
AccessibleEvery control works with the keyboard and screen readers.

Your account

GET/api/v1/meread
{ "id": "…", "name": "Upasana Jewels", "username": "upasanajewels", "link": "https:/@upasanajewels",
  "subscribers": 1200, "scopes": ["read", "upload", "edit"], "upload_quota": { "max_file_mb": 2048 } }

List your videos

GET/api/v1/videosread
QueryMeaning
page, per_pagePage number (from 1) and size (1–100, default 25).
sort, directiondate (default), plays or alphabetical; desc (default) or asc.
queryWords in the name or description.
privacypublic, unlisted or hidden.
{ "total": 42, "page": 1, "per_page": 25,
  "paging": { "next": "/api/v1/videos?page=2&per_page=25", "previous": null, "first": "…", "last": "…" },
  "data": [ /* video objects */ ] }

Upload a video

POST/api/v1/videosupload

The video goes on the channel of the key's account. Three ways in, chosen with upload.approach:

approachUse it whenSend
postYour code has the file.upload.size (bytes), upload.type (e.g. video/mp4), upload.filename
pullThe file is online (a download link, e.g. from Vimeo, Dropbox or your server). We fetch it, up to 2 GB.upload.link
linkKeep it where it is (YouTube, Vimeo, Instagram, Facebook, Google Drive…) and play it from there.upload.link

Also accepted: name (required), description (#hashtags work), privacy, password, embed_policy, embed_domains, chapters, cta, allow_download (see Edit). New videos answer 201 with the video object; status is uploading → processing → available (or failed with upload_error).

Direct upload (approach "post")

Three steps: create the video, send the file to upload.upload_link as a form with every upload.form_fields entry plus the file in the field named file, then call complete.

import os, requests
API, KEY = "https:/api/v1", "bcv_YOUR_KEY"
H = {"Authorization": f"Bearer {KEY}"}
path = "launch.mp4"
v = requests.post(f"{API}/videos", headers=H, json={
    "name": "Launch film", "privacy": "hidden", "embed_policy": "domains", "embed_domains": ["mysite.com"],
    "upload": {"approach": "post", "size": os.path.getsize(path), "type": "video/mp4", "filename": path}}).json()
up = v["upload"]
with open(path, "rb") as f:
    requests.post(up["upload_link"], data=up["form_fields"], files={"file": f}).raise_for_status()
done = requests.post(f"{API}/videos/{v['id']}/complete", headers=H).json()
print(done["status"], done["player_embed_url"])
POST/api/v1/videos/{id}/completeupload

Call after the file is sent. Optional body: {"duration": 125.4} (seconds; we also work it out ourselves). The video becomes available; quality versions, seek previews and subscriber notifications (public videos only) follow by themselves.

From a link (approach "pull" or "link")

{ "name": "Showroom tour", "upload": { "approach": "pull", "link": "https://player.vimeo.com/progressive_redirect/…/file.mp4" } }
{ "name": "Our YouTube trailer", "upload": { "approach": "link", "link": "https://youtu.be/VIDEO" } }

Poll GET /videos/{id} until status is available. Pull links must be public http(s) addresses that answer with a video file.

Get a video

GET/api/v1/videos/{id}read

The full video object, including download links for each quality, subtitles, chapters and the embed code.

Edit a video

PATCH/api/v1/videos/{id}edit

Send only what changes:

FieldValue
name, descriptiontext (150 / 3000 characters)
privacypublic · unlisted · hidden
passwordtext, or "" to remove
embed_policy, embed_domainsanywhere · nowhere · domains; a list like ["mysite.com", "shop.in"]
chapters[{"t": 0, "title": "Intro"}, {"t": 95, "title": "Pricing"}], or text "0:00 Intro\n1:35 Pricing"
cta{"text": "Shop now", "url": "https://…", "at": 30, "until": 0} (seconds; until 0 = to the end), or null
allow_downloadtrue / false (shows a download button on the watch page)
curl -X PATCH https:/api/v1/videos/VIDEO_ID -H "Authorization: Bearer bcv_YOUR_KEY" -H "Content-Type: application/json" \
  -d '{"privacy":"hidden","embed_policy":"domains","embed_domains":["mysite.com"],"cta":{"text":"Shop now","url":"https://mysite.com/shop","at":20}}'

Delete a video

DELETE/api/v1/videos/{id}delete

Answers 204. The video, its quality versions and subtitles are removed; embeds show "Video not available".

Stats

GET/api/v1/videos/{id}/statsread
{ "plays": 1820, "likes": 64, "comments": 9, "saves": 12, "watch_time_minutes": 2150.5, "average_watch_seconds": 71.2,
  "embeds": [ { "domain": "mysite.com", "plays": 1210 }, { "domain": "direct", "plays": 33 } ] }

embeds covers the last 90 days.

Subtitles

GET/api/v1/videos/{id}/texttracksread
POST/api/v1/videos/{id}/texttracksedit

Body: {"language": "hi", "text": "<the .srt or .vtt file's text>", "name": "Hindi"} (name is optional). Adding a language that exists replaces it.

DELETE/api/v1/videos/{id}/texttracks/{language}edit

Embed code

GET/api/v1/videos/{id}/embedread

Any player option in the query is built into the code. Answers {"url", "html", "html_fixed"} (responsive and 640×360).

The video object

FieldMeaning
id, uriThe video's id and API address.
name, description, tagsTitle, description, #hashtags.
link, player_embed_urlThe watch page and the embed player.
embed{html, policy, domains}
statusuploading · processing · available · failed (then see upload_error)
privacy{view, password, download}
duration, created_time, modified_timeSeconds; ISO dates (UTC).
pictures.base_linkThumbnail.
stats{plays, likes, comments}
source, externalupload or link; for links, {provider, url}.
filesEach quality: {quality: "source" | "720p" …, height, link}
transcode.statusQuality versions: queued · working · done · failed
text_tracks, audio_tracksSubtitles {language, name, link} and dubbed audio {language, name}.
chapters, call_to_actionAs set with PATCH.

Moving from Vimeo

  1. In Vimeo, open each video's download options (or use Vimeo's API files / download links) to get a direct file link.
  2. Create the video here with "upload": {"approach": "pull", "link": "…"}, keeping your privacy: Vimeo "Hide from Vimeo" = hidden, "Unlisted" = unlisted, "Only sites I choose" = embed_policy: "domains".
  3. Replace https://player.vimeo.com/video/ID in your website with https:/embed/NEW_ID. Most Vimeo options use the same names here: autoplay, muted, loop, background, controls, color, title, byline, portrait, dnt, speed, texttrack, quality, autopause, keyboard, pip, #t= → t=.
  4. Replace Vimeo's player.js with https:/player/api.js and new Vimeo.Player(el) with new BharatPlayer(el). Method and event names match Vimeo's where they exist (play, getCurrentTime, on('timeupdate')…).

Subtitle & audio languages

CodeLanguageCodeLanguage
enEnglishhiHindi
bnBengalitaTamil
teTelugumrMarathi
guGujaratiknKannada
mlMalayalampaPunjabi
orOdiaurUrdu
asAssamesesaSanskrit
neNepaliesSpanish
frFrencharArabic
deGermanjaJapanese
zhChinese

Questions or a feature you need? Contact support. This is version 1 of the API; we only add to it, so your code keeps working.