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.

Take a photo; it stays in this page.

    Attributes

    AttributeValuesWhat it does
    facinguser | environmentThe front camera (user, the default) or the back one (environment).
    filtersnone, mono, warm, cool, fade, vivid, blurA comma list of the filters the strip offers. Unknown names are dropped; an empty list is none.
    timer0 | 3 | 10A countdown in seconds before a capture, with the number over the preview. Anything else is 0.
    modephoto | burstphoto (the default) takes one; burst takes three, 450 ms apart, and fires three events.
    uploadoff | openvibeoff (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

    EventdetailWhen
    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

    MethodReturnsWhat it does
    start()a promiseAsks for the camera; a click is the safe place to call it. It is never called on connect.
    stop()nothingStops 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 facingSwitches 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/png or image/webp), with optional X-Filter, X-Caption and X-Camera headers (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.

    The Live cam button

    Live calls Cam with the viewer's own Network token โ€” the token in the viewer's Live session โ€” because the picture is saved to that person's OpenVibe.Pics. Cam needs no service token from Live.

    1. Live receives { slot, stream_id, caption } from its watch page and calls POST https://openvibe.cam/api/v1/stills with Authorization: Bearer <viewer's Network token>.
    2. Cam reads GET https://openvibe.media/live/:slot/frame.jpg?w=640. A 404 means the slot is offline; SVG offline cards and HTML errors are never uploaded.
    3. Cam calls POST https://openvibe.pics/api/v1/images with the viewer's token, the JPEG as the body, X-Title and X-Filename.
    4. Cam answers 201 { still, share: { url, caption, text } }. Live posts share.text to the stream's chat with its existing chat authority and shows the still.
    POST /api/v1/stills
    { "slot": "whip", "stream_id": 1234, "caption": "the exact second the robot fell over" }

    A still is unlisted by default: anyone with the link opens /s/<id>; it is not listed anywhere. Pass "visibility": "private" to keep it to yourself. Media's frame endpoint returns at most one frame every five seconds per slot and is rate-limited; a busy frame is 429 cam.frame_busy.

    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.