Skip to main content

Locks & Approvals

The controls that make a video library auditable: freeze content while it's being signed off, route it to named approvers, and leave review comments.

Locks

A lock freezes a video's content. While locked:

Locking cascades: the linked training module and knowledge-base articles freeze with it, so the whole package stays consistent.

Lock

POST /v1/videos/:id/lock

curl -X POST https://api.vidocu.ai/v1/videos/vid_abc123/lock \
-H "Authorization: Bearer vdo_live_your_key_here"
{
"locked": true,
"lock": { "lockedAt": "2026-08-12T09:14:00.000Z", "lockedBy": "usr_...", "lockedByName": "Dana Levi" }
}

Locking an already-locked video is a no-op, not an error - you asked for it to be locked, and it is.

Requires the canLock permission: owner, admin or creator.

Unlock

DELETE /v1/videos/:id/lock

Refused with 409 unless the video's latest approval round has been withdrawn.

That rule has a consequence worth knowing before you send anything for approval: only a pending or rejected round can be withdrawn. Once a round is approved, it can't be withdrawn, so the video can no longer be unlocked - and a locked video can't be deleted either.

An approved video is therefore permanent. That is deliberate: an approved package is a signed-off record. Plan for it, because there is no route back.

Latest roundCan unlock?
noneyes
withdrawnyes
pending / rejectedwithdraw it first, then yes
approvedno - permanently

Approvals

POST /v1/videos/:id/approvals

FieldTypeDescription
approversstring[]Workspace member user ids - every one must approve
messagestringNote to the approvers
curl -X POST https://api.vidocu.ai/v1/videos/vid_abc123/approvals \
-H "Authorization: Bearer vdo_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"approvers": ["usr_a1", "usr_b2"], "message": "Final check before we publish this SOP."}'

The round passes when every named approver approves; a single rejection fails it. Email and display name are resolved from the workspace's own member records, so the request keeps a usable snapshot even if someone leaves later.

Deciding

POST /v1/approvals/:id/decide with {"decision": "approved" | "rejected", "comment": "..."}

Only a named approver can decide, and only once - a second attempt returns 409.

Withdrawing

POST /v1/approvals/:id/withdraw clears the video's approval state entirely, so it goes back to showing none. This is what has to happen before a locked video can be unlocked.

Only pending and rejected rounds can be withdrawn; withdrawing an approved one returns 409.

Listing

GET /v1/approvals (filter by status or videoId) · GET /v1/videos/:id/approvals

Comments

GET /v1/videos/:id/comments · POST /v1/videos/:id/comments DELETE /v1/videos/:id/comments/:commentId

Review comments, optionally pinned to a moment in the video:

curl -X POST https://api.vidocu.ai/v1/videos/vid_abc123/comments \
-H "Authorization: Bearer vdo_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"text": "Step 4 skips the safety check.", "timestamp": 42.5}'

Who actions are attributed to

Locks, approvals and comments all record who did them - that's what makes them auditable.

  • OAuth tokens are attributed to the token's user.
  • API keys authenticate as a workspace, not a person, so they're attributed to the workspace owner.

If you need per-person attribution in an audit trail, use OAuth rather than a shared API key.

Errors

StatusCodeCause
400validation_errorAn approver isn't a member of the workspace, or the body failed validation
403insufficient_roleYour role can't lock content
404not_foundVideo, approval request or comment not found
409conflictUnlock blocked by a round that isn't withdrawn (an approved one never can be); already decided; or not an approver
423lockedYou tried to change locked content

AI provenance

Text that Vidocu generates - help articles, scripts, subtitle rewrites and translations, quiz questions - is marked as machine-generated in a machine-readable form. This satisfies our obligation as the provider of that generation system under Article 50(2) of the EU AI Act (Regulation (EU) 2024/1689), which applies to systems already on the market from 2 December 2026.

Two things this is not:

  • It is not visible. No badge, no label, no change to how an article, PDF or knowledge-base page looks. Article 50(4)'s visible-labelling duty falls on deployers publishing to inform the public on matters of public interest, and does not reach internal work instructions that pass human review.
  • It is not a workspace setting. The duty is ours as provider, not a customer preference, so it is always on and cannot be switched off.

Synthetic speech is a separate matter: the provider of that generation system is the text-to-speech vendor, and its marking is theirs to apply.

The record

FieldMeaning
aiGeneratedAlways true when present
digitalSourceTypeIPTC Digital Source Type URI - trainedAlgorithmicMedia, meaning the text was produced by a trained AI model
kindarticle, articleTranslation, script, subtitleRewrite, subtitleTranslation, quiz, quizTranslation or transcription
provider / modelThe model that actually ran
generatedAtWhen it was generated
sourceHashHash of the text as generated, ignoring image URLs and whitespace
humanEditedtrue once the text has been edited since. Derived by comparing the current text against sourceHash, so it cannot go stale
approvalApprover names and decision time, read from the approval record

digitalSourceType carries the IPTC controlled-vocabulary term rather than a flag only we understand, so an off-the-shelf provenance reader recognises it.

kind: "transcription" is speech-to-text of a real person speaking. A trained model produced the text, so it is marked, but the kind stays distinct from synthesised prose.

Where the marking appears

SurfaceCarrier
Article PDFXMP packet (Iptc4xmpExt:DigitalSourceType + vidocu:*)
Article DOCXdocProps/custom.xml custom properties, plus core description and keywords
Article HTML<meta name="ai-*"> tags in <head>
Article MarkdownYAML front matter
Knowledge-base page<meta name="ai-*"> tags in <head>
WebVTTA NOTE block after the WEBVTT header
Package ZIPaiProvenance in manifest.json, per artifact
REST APIaiProvenance in the JSON body and an X-Content-Provenance response header
Help-centre publishAn HTML comment ahead of the article body

Courses and Studio

Courses. The planner and storyboarder write the course title, summary, module objectives, scene copy and the narration script. The marking sits on the plan (aiProvenance on the course) and per module (on the storyboard), and it appears on GET /v1/courses, GET /v1/courses/:id, GET /v1/courses/:id/storyboard, the public course share payload, and the corresponding MCP tools. When a module is generated into a video, the narration carries the marking onto that video's subtitles, so every video surface - SRT and VTT download, the package manifest, X-Content-Provenance - picks it up too.

Studio. The Studio assistant can write text clips, captions and slide copy. The marking is recorded at project level, not per clip: the editor saves the whole document at once, and by the time it reaches the server an assistant-written clip is indistinguishable from one the user typed. What the record claims is therefore the checkable thing - this project contains text written by that model. It appears on GET /v1/studio/projects/:id and its MCP tool, and it travels with a project cloned from a template. The Studio share page and the MP4 export are burned-in pixels, so there is no text there to mark.

One known gap: importing a video into Studio turns its AI-written narration into caption clips, and that project is not marked from the import alone. The server has no way to verify which model wrote the imported text, and stamping it with the Studio assistant's model would record something untrue - so it records nothing. The source video keeps its own marking.

Where it deliberately does not appear

Article 50(2) requires marking that is effective "as far as this is technically feasible". Two formats have no slot that satisfies it, and rather than damage the file we record the marking beside it:

  • SRT has no comment syntax. A pseudo-comment would risk a player, NLE or subtitle importer rejecting the file. SRT bytes are therefore left exactly as they were, and the marking travels in the X-Content-Provenance response header, in the package manifest, and in a subtitles.srt.provenance.json sidecar inside the package ZIP. The same applies to ASS/SSA, SUB and SBV from the subtitle-translator tool.
  • Notion pages are lists of block objects with no comment or metadata block. Every carrier the Notion API offers - callout, paragraph, code, icon, page property - renders to the reader, and the marking must not change what the reader sees.

Watermarking

Text watermarking in the SynthID sense has to be applied while the model is generating, with access to its output probabilities. Vidocu generates text through third-party model APIs that do not expose that, so it is not available to us at any price. The June 2026 Transparency Code of Practice recognises provenance metadata as the other accepted mechanism, and that is what is described above. If the models we use ever expose watermarking, we will add it alongside this.