Developers
The Cam API and the camera element
The camera app is also one framework-free element: <ov-camera> runs the preview, the filters, the timer and the burst in the browser, and hands you the picture. The same page has the JSON API under /api/v1, with application/problem+json errors.
OpenVibeCam: record on your site
One script opens Cam on the calling page. On a phone it fills the screen; on a computer it opens a centred stage. The person sees a plain permission prompt, records, reviews, trims and chooses Use this. Cam hands your page a File and metadata; your page decides where to upload it. Cancelling resolves to null.
For an in-place studio, the same SRI-pinned script exposes OpenVibeCam.core: devices(), constraints({ facing, lens, mic, resolution }), open(options), meter(stream, onLevel), network() and stop(stream). Only open() asks for camera or microphone access; call it after the person's explicit action. Saved lens and mic ids are hints on another browser and fall back to its available devices.
<script src="https://openvibe.cam/embed/cam-kit.js" integrity="sha384-6D72Zg6MS9RCQAiXgnP8R8iz/criDdCCgY48Jb87HRjzkVTvCDCCCmDs0E0RLE3s" crossorigin="anonymous" defer></script>
<button id="record">Record a clip</button>
<script>
document.getElementById('record').addEventListener('click', async function () {
const result = await OpenVibeCam.open({ mode: 'video', maxSeconds: 60 });
if (result) { /* result.file is yours to upload or preview */ }
});
</script>
OpenVibeCam.open({ mode: 'video' | 'photo' | 'screen', maxSeconds, timer, onDone }) resolves to { file, mode, mimeType, duration, width, height, maxSeconds }. onDone(result) runs when Use this is pressed and may return a promise. mode defaults to video, maxSeconds defaults to 60, and timer is 0, 3 or 10 seconds. The screen option appears when the browser supports screen capture. Space starts or stops recording; Esc closes. Photos and recordings stay in the browser until your page handles the file. To upload, pass result.file to your site's uploader; use ov-upload's resumable path when it is available.
The demo keeps the file on this page.
The <ov-camera> element
One script, one tag. Nothing starts until a person clicks or your code calls start(); with upload="off" (the default) the picture never leaves the browser. It uses a shadow root and two CSS custom properties, so a site's own styles cannot leak in.
<script src="https://openvibe.cam/embed/ov-camera.js" async></script>
<ov-camera id="my-cam" facing="user" filters="none,mono,warm,cool,fade,vivid,blur" timer="0" mode="photo" upload="off"></ov-camera>
<script>
document.getElementById('my-cam').addEventListener('ov-camera:capture', function (e) {
// e.detail = { blob, width, height, filter }
var img = new Image();
img.src = URL.createObjectURL(e.detail.blob);
document.body.appendChild(img);
});
</script>
Paste that once. ov-camera:capture fires for every photo (three in mode="burst"); ov-camera:error names what went wrong. The live demo below adds the photo to this page and nowhere else.
Attributes
| Attribute | Values | What it does |
|---|---|---|
facing | user | environment | The front camera (user, the default) or the back one (environment). |
filters | none, mono, warm, cool, fade, vivid, blur | A comma list of the filters the strip offers. Unknown names are dropped; an empty list is none. |
timer | 0 | 3 | 10 | A countdown in seconds before a capture, with the number over the preview. Anything else is 0. |
mode | photo | burst | photo (the default) takes one; burst takes three, 450 ms apart, and fires three events. |
upload | off | openvibe | off (the default) never sends the picture. openvibe posts it to POST /api/v1/photos as the signed-in person, and only when the page is served from openvibe.cam. |
Events
| Event | detail | When |
|---|---|---|
ov-camera:capture | { blob, width, height, filter } | A photo is ready: a camera capture, a burst shot or a picture picked from disk. The event bubbles and crosses the shadow boundary. |
ov-camera:error | { code, message } | The camera or the upload failed. Codes: camera_unavailable, permission_denied, not_started, capture_failed, file_unreadable, upload_unavailable, upload_denied, upload_failed. |
Methods
| Method | Returns | What it does |
|---|---|---|
start() | a promise | Asks for the camera; a click is the safe place to call it. It is never called on connect. |
stop() | nothing | Stops every track and returns the viewfinder to the off card. |
capture() | a promise of the detail (three in burst) | Takes a photo through the same path as the shutter, honouring timer and mode. |
flip() | a promise of the new facing | Switches front/back and restarts the camera if it is on. |
Style it with --ov-camera-accent (default #00c7d2) and --ov-camera-radius (default 26px). No camera permission? The element keeps its Turn on the camera button and the Or pick a picture picker, so a site still gets an image.
In an app with a build step, OpenVibe.SDK wraps the loader: import { defineOvCamera } from 'openvibe-sdk/cam'; await defineOvCamera(); injects the script once and returns the element class. The typed attributes, events and methods are in the entry's .d.ts.
The API
JSON under /api/v1, with application/problem+json errors. A person is the cam_at session cookie or a Network user token as a Bearer. No route needs an app or service token.
POST /api/v1/photos- A person; the picture is the raw body (
image/jpeg,image/pngorimage/webp), with optionalX-Filter,X-CaptionandX-Cameraheaders (URI-encoded). Cam uploads it to OpenVibe.Pics and stores the link. GET /api/v1/photos- Your photos, newest first.
GET /api/v1/photos/:id- One of your photos.
My cameras and viewer grants
GET /api/v1/me/camera-settings- Your saved facing, lens, mic, resolution and mirror choices. Defaults are returned before you save.
PUT /api/v1/me/camera-settings{ facing?, lens?, mic?, resolution?, mirror? }. Partial updates preserve your other choices; a signed-in person only.GET /api/v1/cameras- Your paired cameras with their active grant count.
POST /api/v1/cameras{ name, source, stream_id?, slot?, notes? }. A stream id is read from Live and must be on your channel.PATCH /api/v1/cameras/:id{ name?, source?, status?, slot?, notes? }; the owner only.DELETE /api/v1/cameras/:id- Unpair it; every grant on it goes. The owner only.
GET /api/v1/cameras/:id/wall- The private wall: owner, or a viewer whose grant is active. A stranger gets 404, never a hint that it exists.
GET /api/v1/cameras/:id/grants- The grants you gave, including expired and revoked ones. The owner only.
POST /api/v1/cameras/:id/grants{ viewer_username }or{ viewer_subject }, and{ days }(1โ90, default 7). An existing active grant is extended, never duplicated.DELETE /api/v1/cameras/:id/grants/:grantId- Revoke at once: the next wall request is refused.
Stills
POST /api/v1/stills{ slot, caption?, stream_id?, w?, visibility? }. A person; fetches Media's frame and uploads it to Pics as them.GET /api/v1/stills- Your stills, newest first.
GET /api/v1/stills/:id- One still; an unlisted one is readable by anyone with the link, a private one only by its owner.
Privacy
A camera is private. Staff have no bypass; a global moderator without a grant is refused like anyone else. Cam stores no image bytes, no face template and no identity classification. It stores a camera's name and slot, a grant's two subjects and expiry, and a picture's Pics slug, links, filter name and caption. Account export and deletion cover cameras, grants, photos and stills; the picture itself is erased by OpenVibe.Pics on the same Network event.