DreamLake

Annotations

An annotation is where your labeled data lives — your workflow does the labeling, the annotation stores its output. One episode is one raw video plus any number of annotation layers: anything you can pin to that video's clock. Layers are independent and open-ended — send what you have, add the rest later.

one annotationep-001ep-002ep-003…one clock — every layer is pinned to itvideoper-frame valueslabeled intervalsepisode-wide facts…and morethe next layer lands here — same video, same clockmany episodes per annotation — each one a video plus every layer you have for it

Public catalog reads

GET /namespaces/:slug/annotations accepts requests without an Authorization header. Anonymous callers and authenticated nonmembers receive only live public annotation rows; namespace members retain access to their private rows. A supplied invalid or expired token returns 401.

Public annotation details (GET /namespaces/:slug/annotations/:name) and content reads (POST /namespaces/:slug/annotations/:name/presign-read) also accept anonymous requests. Both check the annotation's current visibility. Private, missing and deleted annotations return 404 to callers without access; namespace members can still read their private annotations. Presigned reads retain the existing path validation and expiry limits. Upload credentials, creation, editing and deletion still require authentication and membership.

Browsing in the app

/<namespace>/profile?tab=annotations and /<namespace>/annotations share the same catalog. Personal owners and organization members see the resources and operations permitted to them; anonymous visitors and visitors to someone else's namespace see public annotations only. Profile has an identity rail, while the application has resource navigation for the URL's namespace.

Public details open without a sign-in redirect and retain the namespace sidebar. The detail header returns anonymous readers to the Profile annotations tab and signed-in readers to /<namespace>/annotations. Embedded viewers return to their containing project. See Profiles and workspaces.

Prerequisites

bash
curl -fsSL https://dl.dreamlake.ai/install.sh | bash   # the dreamlake CLI
pip install dreamlake dreamdb   # SDK + storage engine
brew install ffmpeg             # transcoding (any install method works)
dreamlake login                 # browser sign-in; CI uses DREAMLAKE_API_KEY

Windows, what the installer puts where, release channels, and auto-update: Install.

Upload it

your videoany formatannotationswhatever you haveone uploadyou wait, it worksstreaming videoplays in any browseraligned layerson the video's clockone episodenothing to convert by hand — the upload transcodes and aligns for you

One call. The video is transcoded for the browser, each layer is stored against its clock, and adding a layer later just means passing one more argument. Give the episode an id you'll recognise — that's the handle for coming back.

Keep adding

Open an annotation by name on any day and add to it. Revisions never overwrite — newest wins, history stays.

one episode, filled in over time — newest wins, nothing is overwrittenvideo+ one layer+ a layerdays later+ a cameradays later+ next layerwhenever it shipsevery revision stays in history — the episode never has to be re-uploaded
Later, you canEffect
add another episodethe annotation grows
revise a layernewest wins, old versions kept
add a layer you skippedit shows up on an episode you already uploaded
add a layer type that ships latersame thing — just another revision
add a camera angleviews share one clock; video is add-only
make it publicanyone can open it, no login

Nothing you upload today has to be re-uploaded to benefit from what ships tomorrow.

Watch it

Open the web app → Annotations → your annotation → your episode.

your-annotation / your-episodeEPISODES124SEGMENTS3① per-frame layers — drawn on the picture    ② spatial layers — the 3D view③ interval layers — the segment list    ④ the same intervals, on the clockthe video stays raw — new layer types slot into surfaces like these

How a layer is drawn follows from how it's pinned — which is why the list can grow without the picture changing:

A layer pinned toIs drawnShipping today
a frameon the picture, tracking playbackjoint skeletons, 3D reconstruction
an intervalas labeled bars on the timeline; click to seekaction segments
the whole episodeas episode metadatatask labels

Each is optional. The player shows what you have and skips what you don't — a bare video simply streams.

When there's no overlay yet

Every annotation page has two views, and the second one always works.

your-annotationvideo.annotation/v2presetraw trackspreset viewwhen the type is one we renderraw tracks viewalways there — every track, whatever it issubtask_labelevent · strjoints_posejsonrecon_handsimage.binframe_vecdim=512no preset for your data yet? it is still stored, listed and readable

Preset view is what the last section showed: the annotation's type label matches a preset, and you get the player with its overlays.

Raw tracks view ignores presets and lists every track straight off the manifest — name, kind, values along the timeline. Label, JSON, embedding, video; it doesn't matter.

If your dataYou get
matches a presetthe rendered view, overlays and all
doesn't, or not yetthe raw track view — nothing hidden, nothing lost
gets a preset laterthe rendered view, with no re-upload

So a new kind of data is useful the day you upload it.

Bring your own schema

The same machinery with the lid off: declare any columns you like — sensor readings, JSON, images, embeddings — anchored to timestamps, or to row numbers when your data has no clock. It's also the escape hatch when the layer you need doesn't exist yet.

not video? declare your own columns and use the same storagedeclare trackstyped columnsappendrows landat a point in timereadbacksame shapesanywhere, any machinenumbersimagesJSON docsembeddings
RuleWhat it means for you
append-only, write-oncea value at a given time and column is never rewritten
one commit per writesend batches, not one point at a time
one writer at a timeparallel writers to the same annotation will clash

Custom annotations appear in the catalog under their own type label; today you read them back through the SDK.

Share it

Annotations are private by default.

Who can view
🔒 private (default)you and members of your namespace
🌐 publicanyone, no login

Names can be scoped to a team, too: a bare name is yours, an organisation prefix targets that org, and membership is checked per request.

Let Claude do it

Add the annotations skill and skip the SDK — describe what you want, it makes the calls:

You sayIt does
"upload this clip with its hand skeletons and action segments"creates the annotation, uploads the episode
"add the 3D reconstruction to ep-001"fills in the missing layer
"add a wrist camera to ep-001"attaches the second view
"make this annotation public"flips visibility

Next steps

Annotations Reference →

The manual — every method, argument and data shape, with the code this page leaves out.

Reference § Annotation formats →

Every layer's exact fields, including 3D reconstruction.