Skip to content

Lesson Studio

Lesson Studio: studio projects, compositions, slides, project versions, comments, plus slide templates + themes, image rendering and YouTube thumbnails.

Endpoint https://studio.mcp.devfellowship.com/mcp
Tools 73 in 17 groups
Package packages/dfl-mcp-studio
Auth Your dfl-auth login token as Authorization: Bearer <token>. Every call runs as you, under RLS. See Auth & security.
.mcp.json
{
"mcpServers": {
"dfl-studio": {
"type": "http",
"url": "https://studio.mcp.devfellowship.com/mcp",
"headers": {
"Authorization": "Bearer YOUR_ACCESS_TOKEN"
}
}
}
}

Other clients (Cursor, VS Code, codex, the Anthropic SDK): see Getting started, step 3.

Backing datastudio projects, compositions, slides, versions, comments, video exports; slide templates + themes (read/update); PNG renders via the dfl-render headless capture; YouTube thumbnails via the Thumbify renderer on dfl-services.

Review comments on slides, and all comments of a version as one text block.

create_slide_comment

Create a review comment anchored to a slide in a Lesson Studio project (Course Canvas comments).

Create Slide Comment

Create a review comment anchored to a slide in a Lesson Studio project (Course Canvas comments). Stamped with the project current version; version 1 is auto-created if none exists.

ParameterTypeRequiredDescription
project_idstringyesUUID of the project.
bodystringyesThe comment text (change request / note)
slide_idstringnoUUID of the slide the comment targets.
composition_idstringnoUUID of the composition (for reference / canvas grouping)
anchor_xnumbernoOptional Figma-style pin X coordinate on the canvas.
anchor_ynumbernoOptional Figma-style pin Y coordinate on the canvas.

list_slide_comments

List Course Canvas comments of a project (newest first), optionally filtered by version and/or resolved state.

List Slide Comments

List Course Canvas comments of a project (newest first), optionally filtered by version and/or resolved state. Enriched with composition title + slide order for reference.

ParameterTypeRequiredDescription
project_idstringyesUUID of the project.
project_version_idstringnoFilter to comments stamped with this version.
resolvedbooleannoFilter by resolved state (true/false)

update_slide_comment

Update a Course Canvas comment body and/or its resolved state.

Update Slide Comment

Update a Course Canvas comment body and/or its resolved state.

ParameterTypeRequiredDescription
idstringyesUUID of the comment.
bodystringnoNew comment text.
resolvedbooleannoMark resolved (true) or reopen (false)

delete_slide_comment

Delete a Course Canvas comment by id.

Delete Slide Comment

Delete a Course Canvas comment by id.

ParameterTypeRequiredDescription
idstringyesUUID of the comment to delete.

get_version_comments

Get all comments for a project version as a clipboard-ready text block, one comment per line prefixed with project/composition/slide references.

Get Version Comments (clipboard text)

Get all comments for a project version as a clipboard-ready text block, one comment per line prefixed with project/composition/slide references. Defaults to the project current version. Use for the "copy all comments → paste to Claude" flow.

ParameterTypeRequiredDescription
project_idstringyesUUID of the project.
project_version_idstringnoUUID of the version. If omitted, the current (max version_number) is used.

The lessons (compositions) inside a Studio project.

create_composition

Create a composition (lesson-level grouping / "frame") under a Lesson Studio project.

Create Composition

Create a composition (lesson-level grouping / "frame") under a Lesson Studio project. Slides attach to a composition.

ParameterTypeRequiredDescription
project_idstringyesUUID of the parent project.
titlestringnoComposition title (default: "Untitled Composition")
order_indexnumbernoDisplay order within the project. If omitted, appended after existing compositions.

list_compositions

List the compositions of a Lesson Studio project, ordered by order_index.

List Compositions

List the compositions of a Lesson Studio project, ordered by order_index.

ParameterTypeRequiredDescription
project_idstringyesUUID of the parent project.

update_composition

Update a composition title and/or order_index.

Update Composition

Update a composition title and/or order_index.

ParameterTypeRequiredDescription
idstringyesUUID of the composition.
titlestringnoNew title.
order_indexnumbernoNew display order.

delete_composition

Delete a composition. Its slides must be removed or reassigned first (slides.composition_id is NOT NULL).

Delete Composition

Delete a composition. Its slides must be removed or reassigned first (slides.composition_id is NOT NULL).

ParameterTypeRequiredDescription
idstringyesUUID of the composition to delete.

Render a lesson to MP4, follow the render, check the result, and cut it into Story parts.

expire_stale_studio_exports

Find Lesson Studio video exports stuck in a non-terminal state (pending/processing) older than N hours and mark them failed with an explanatory error_message, so an abandoned export stops looking like it is still…

Expire Stale Lesson Studio Exports

Find Lesson Studio video exports stuck in a non-terminal state (pending/processing) older than N hours and mark them failed with an explanatory error_message, so an abandoned export stops looking like it is still running. Defaults to a DRY RUN — pass dry_run: false to actually write. RLS-scoped to the caller's own projects.

ParameterTypeRequiredDescription
older_than_hoursnumbernoOnly expire exports whose started_at is at least this many hours ago (default 6, minimum 1). Keep this comfortably above your longest legitimate render.
project_idstringnoRestrict the sweep to a single project UUID. Omit to sweep all of the caller's projects.
statusesstring[]noWhich non-terminal statuses to sweep (default ["pending","processing"]).
reasonstringnoMessage written to error_message. Defaults to a user-facing explanation.
limitnumbernoMaximum number of rows to expire in one call (safety cap).
dry_runbooleannoWhen true (the DEFAULT), only report what WOULD be expired without writing.

start_composition_export

Render a whole composition into an MP4, server-side.

Export a Lesson Studio composition to MP4

Render a whole composition into an MP4, server-side. Returns as soon as the job is accepted — rendering takes minutes, so this does NOT wait: poll get_studio_export with the returned export_id until it is completed, then pass that export to register_export_as_media to get a media_id the post calendar accepts. Recording a webcam over the deck is still done in the Lesson Studio UI; this exports what is there. Set variant to mobile for a 9:16 reel/short/story cut; omit it to follow the project's own orientation.

ParameterTypeRequiredDescription
composition_idstringyesThe composition to render (from list_compositions).
variantenumnoAspect to cut to: desktop is landscape 16:9 (YouTube), mobile is portrait 9:16 (Instagram reels, YouTube shorts, stories). Omit to follow the project orientation (set at create_studio_project, changeable with update_studio_project). Use mobile to cut a portrait video out of a landscape project without changing it. One of: desktop, mobile.
resolutionenumnoOutput size. Omit for the render service default (1080p). One of: 720p, 1080p, 4k.
fpsnumbernoOutput frame rate. Omit for the render service default.

get_studio_export

Read one export and, while it is still rendering, ask the render service where it is and write the answer back to the row.

Get one Lesson Studio export

Read one export and, while it is still rendering, ask the render service where it is and write the answer back to the row. Poll this after start_composition_export until completed, then pass the export id to register_export_as_media. A failed export carries the real reason, not a generic one.

ParameterTypeRequiredDescription
export_idstringyesThe export to read (from start_composition_export).

list_studio_exports

List the rendered video exports (MP4) of the caller's Lesson Studio projects, newest first.

List Lesson Studio Exports

List the rendered video exports (MP4) of the caller's Lesson Studio projects, newest first. Defaults to completed exports only. Use the returned export id with register_export_as_media to turn a video into a media_id the post calendar accepts.

ParameterTypeRequiredDescription
project_idstringnoRestrict to one project.
statusenumnoExport status to match (default completed). all lists every state. One of: completed, failed, pending, processing, all.
limitnumbernoMaximum rows (default 20, max 100).

register_export_as_media

Make a completed Lesson Studio export (MP4) usable by the post calendar: creates a public.media row pointing at the exported file and returns the media_id plus the stable media.devfellowship.com/<id> link.

Register a Lesson Studio Export as Post Media

Make a completed Lesson Studio export (MP4) usable by the post calendar: creates a public.media row pointing at the exported file and returns the media_id plus the stable media.devfellowship.com/<id> link. Pass that media_id to draft_post_for_review on the campaigns MCP. Idempotent — the same export returns the same media_id. Only exports stored in the DevFellowship bucket can be registered.

ParameterTypeRequiredDescription
export_idstringyesAn export id from list_studio_exports (status must be completed).
namestringnoDisplay name for the media row. Defaults to the export file name.

split_export_for_stories

Cut a completed export (MP4) into ordered Story parts of at most 60 s each, and register each part as media.

Split a Lesson Studio export into Instagram Story parts

Cut a completed export (MP4) into ordered Story parts of at most 60 s each, and register each part as media. The cuts follow the slide changes when the render service knows them, else scene changes. Returns the parts in order, each with a media_id — pass those media ids, in order, to the campaigns Story sequence. Waits up to ~45 s; if the split is still running, it returns split_job_id — then poll get_story_split with it.

ParameterTypeRequiredDescription
export_idstringyesA completed export (from get_studio_export or list_studio_exports).
max_segment_snumbernoLongest part in seconds (6–60). Omit for the render service default (59).

check_studio_export

A machine review of a COMPLETED export (dfl-render /media/export-check): the duration (and the gap to expected_duration_s), the frame size, black-frame ranges, JPEG frames just after each slide starts, at its middle and…

Check a Lesson Studio export

A machine review of a COMPLETED export (dfl-render /media/export-check): the duration (and the gap to expected_duration_s), the frame size, black-frame ranges, JPEG frames just after each slide starts, at its middle and just before it ends (slides from the render job), and a caption-over-face flag per mid-slide frame (one vision-LLM pass; face_check false skips it). flags lists what a reviewer must look at first. Waits ~50 s; if the check still runs it returns check_job_id — then call get_export_check.

ParameterTypeRequiredDescription
export_idstringyesA completed export (get_studio_export / list_studio_exports).
expected_duration_snumbernoThe length you expect, seconds.
face_checkbooleannoDefault true: the caption/face overlap pass.

get_export_check

Read an export check that check_studio_export started (the same answer when it is done).

Get an export check

Read an export check that check_studio_export started (the same answer when it is done).

ParameterTypeRequiredDescription
check_job_idstringyesThe check_job_id check_studio_export returned.

get_story_split

Read a Story split job that split_export_for_stories started.

Get a Story split job

Read a Story split job that split_export_for_stories started. While it runs, this returns its status — call it again. When it is done, it registers each part as media and returns the parts in order with their media ids (the same answer split_export_for_stories gives). Safe to call again: a part keeps its media_id.

ParameterTypeRequiredDescription
split_job_idstringyesThe split_job_id that split_export_for_stories returned.

Create, list, edit and delete Lesson Studio projects.

create_studio_project

Create a new Lesson Studio project in the lesson_studio schema, plus a "Default" composition.

Create Lesson Studio Project

Create a new Lesson Studio project in the lesson_studio schema, plus a "Default" composition. Returns the project_id and the default composition_id (attach slides to a composition via create_slide).

ParameterTypeRequiredDescription
titlestringyesProject title.
owner_idstringnoUUID of the owner (auth.users.id). Defaults to the authenticated caller.
orientationenumnoCanvas orientation (default: landscape). social-portrait = Feed/carousel post, 1080×1350 (4:5): static slides, no camera/captions/video export; only templates declaring the social-portrait canvas. One of: landscape, portrait, social-portrait.
descriptionstringnoOptional project description.
settingsobjectnoOptional settings JSON, merged OVER the defaults (resolution, fps, backgroundColor, theme_id, and portrait's 1:1 camera) rather than replacing them. Pass theme_id here to start on a theme other than the DFL one; update_studio_project changes it later.

list_studio_projects

List Lesson Studio projects owned by the caller (RLS-scoped), most recently updated first.

List Lesson Studio Projects

List Lesson Studio projects owned by the caller (RLS-scoped), most recently updated first.

ParameterTypeRequiredDescription
limitnumbernoMax projects to return (default: 50, max: 100)
offsetnumbernoNumber of projects to skip (pagination)
searchstringnoSearch by title (ilike)

delete_studio_project

Delete a Lesson Studio project.

Delete Lesson Studio Project

Delete a Lesson Studio project. Its compositions, slides, slide_elements and slide_media cascade. RLS-scoped: only the caller's own projects can be deleted.

ParameterTypeRequiredDescription
project_idstringyesUUID of the project to delete.

update_studio_project

Update a project's title, description, thumbnail, orientation, active theme (settings.theme_id), burned-in captions (settings.captions_*) or export watermark (settings.watermark).

Update Lesson Studio Project

Update a project's title, description, thumbnail, orientation, active theme (settings.theme_id), burned-in captions (settings.captions_*) or export watermark (settings.watermark). For a 9:16 reel, pass captions {enabled: true, position: "top"} and watermark {enabled: true} before start_composition_export. Camera defaults are set_project_camera's job, not this tool's. theme_id is checked against list_themes when the registry is reachable; an unknown id is rejected. A settings write (theme_id, captions, watermark) is guarded on updated_at (it reads settings first to merge; keys you leave out keep their stored value); a change to the other fields overwrites only the columns passed. Changing orientation does NOT reshape camera boxes — it warns and points at set_project_camera.

ParameterTypeRequiredDescription
project_idstringyesUUID of the project.
titlestringnoNew title.
descriptionstringnoNew description, or null to clear it.
thumbnail_urlstringnoNew thumbnail URL, or null to clear it.
orientationenumnoCanvas orientation. social-portrait = Feed/carousel post, 1080×1350 (4:5): static slides, no camera/captions/video export; only templates declaring the social-portrait canvas. Changing an existing project to or from social-portrait is refused unless it has no slides (soft-deleted included); an empty project switching resets safe_area_top and camera settings to the target orientation defaults. One of: landscape, portrait, social-portrait.
theme_idstringnoTheme id to apply project-wide, as reported by list_themes (e.g. "default", "devfellowship").
captionsobjectnoBurned-in caption settings, merged key by key into settings.captions_*.
watermarkobjectnoExport watermark settings, merged key by key into settings.watermark. The logo comes from the theme.

Create a slide or an image slide, edit, delete or restore slides, and find stock photos for them.

create_slide

Create a slide under a composition (mirrors the Lesson Studio editor insert shape so it is valid + editable in the UI).

Create Slide

Create a slide under a composition (mirrors the Lesson Studio editor insert shape so it is valid + editable in the UI). Optional template_data.animation animates the slide. Preset 'steps-reveal' shows list items one at a time; use it when the narration walks through steps, on a template whose items carry data-anim-target="step". Example: {preset:'steps-reveal',version:1,beat_source:'manual',beats:[{kind:'reveal',target:'step',index:0,start_ms:600,duration_ms:300}]}. Sort beats by start_ms. Every beat must end before the slide duration_ms. The response can list warnings. sfx: [{sound, at_ms, volume?}] — see list_sound_effects.

ParameterTypeRequiredDescription
composition_idstringyesUUID of the parent composition.
project_idstringnoUUID of the parent project. Resolved from the composition if omitted.
order_indexnumbernoDisplay order within the composition. If omitted, appended at the end.
template_idstringnoSlide template id, as published in the dfl-slide-templates registry. Call search_templates("<what the slide should do>") or list_templates to pick one, and get_template("<id>") for its exact slots — this list is NOT enumerated here on purpose, so it can never go stale. An id that is not in the registry is rejected. Required slots must carry real content (see template_data). For a single image filling the whole canvas, prefer the dedicated create_image_slide tool.
template_dataobjectnoTemplate data JSON (text/props consumed by the template renderer). Validated against the slots the template declares in the registry (templates/<id>/config.yaml) — call get_template("<id>") to see each slot's type and a valid sample. Required slots must be non-empty and correctly shaped: text fields need real text (not "" or whitespace); array fields need at least one entry matching the sample shape (e.g. bullet-list items: [{ "text": "Agents run tools autonomously" }], table rows: [{ "cells": [{ "value": "Revenue" }] }]). Use template_id "blank" for an intentionally empty slide.
duration_msnumbernoSlide duration in ms, a positive integer (default: 5000)
background_colorstringnoHex background color (default: "#1a1a2e"). Only a color other than the default sets template_data.background_override = true, so the Studio shows it over the template background.
background_image_urlstringnoOptional background image URL. A non-empty URL also sets template_data.background_override = true.
transition_typestringnoTransition type (default: "fade")
transition_duration_msnumbernoTransition duration in ms (default: 500)

create_image_slide

Create a slide that displays a single image filling the entire slide canvas, using the fullscreen-image template.

Create Full-screen Image Slide

Create a slide that displays a single image filling the entire slide canvas, using the fullscreen-image template. Use this to bring your own finished slide images (e.g. an image hosted on S3, or a pre-rendered slide exported from another tool) instead of reusing the built-in content templates. The fit option controls how the image fills the canvas: cover (default) crops to fill, contain letterboxes to show the whole image.

ParameterTypeRequiredDescription
composition_idstringyesUUID of the parent composition.
project_idstringnoUUID of the parent project. Resolved from the composition if omitted.
image_urlstringyesURL of the image to display full-screen (e.g. an S3 URL)
fitenumnoHow the image fills the canvas: "cover" (crop to fill, default) or "contain" (letterbox the whole image) One of: cover, contain.
bgstringnoLetterbox/background color (hex) used behind the image in "contain" mode. Defaults handled by the template CSS.
image_altstringnoAccessibility alt text for the image.
order_indexnumbernoDisplay order within the composition. If omitted, appended at the end.
duration_msnumbernoSlide duration in ms (default: 5000)
background_colorstringnoHex background color (default: "#1a1a2e")
transition_typestringnoTransition type (default: "fade")
transition_duration_msnumbernoTransition duration in ms (default: 500)

list_slides

List the slides of a composition, ordered by order_index.

List Slides

List the slides of a composition, ordered by order_index. Soft-deleted slides are excluded by default; pass include_deleted:true to return all (including soft-deleted).

ParameterTypeRequiredDescription
composition_idstringyesUUID of the parent composition.
include_deletedbooleannoWhen true, also return soft-deleted slides (deleted_at IS NOT NULL). Default false → only live slides.

update_slide

Update a slide. template_data replaces the slots; an omitted slot is removed.

Update Slide

Update a slide. template_data replaces the slots; an omitted slot is removed. Studio keys (not slots): narration_notes, animation, background_override, camera, camera_box, capture, camera_box_custom, watermark, webcam_layout, pointer_track, is_cover, sfx, framing, caption_cues, clips, style_overrides, slide_name, caption_cues_source, caption_language, camera_segments, content_over_camera, caption_position, camera_focus, insert. An omitted Studio key is kept, except animation: a template_id change removes it unless you send it. Remove one via clear_template_data_keys, only when the user asks. narration_notes and pointer_track are user work and cannot be restored. The response lists removed_template_data_keys. A new background sets background_override = true. To show the template background again, send clear_template_data_keys: ['background_override'] and no background field. A sent animation is checked as the create_slide description says, against the slide duration_ms. A new duration_ms is checked against the stored animation. The response can list warnings. sfx: [{sound, at_ms, volume?}] — see list_sound_effects.

ParameterTypeRequiredDescription
idstringyesUUID of the slide.
order_indexnumbernoNew display order in the composition.
composition_idstringnoMove the slide to another composition.
template_idstringnoRegistry template id. An unknown id is rejected.
template_dataobjectnoTemplate slot values, validated against get_template("<id>").
clear_template_data_keysenum[]noStudio keys to remove; do not also send in template_data.
duration_msnumbernoSlide duration in ms (positive int)
background_colorstringnoHex background color.
background_image_urlstringnoBackground image URL.
transition_typestringnoTransition type.
transition_duration_msnumbernoTransition duration, ms.

delete_slide

Delete a slide. By default this is a SOFT delete (recoverable via restore_slide; slide_elements/slide_media are kept).

Delete Slide

Delete a slide. By default this is a SOFT delete (recoverable via restore_slide; slide_elements/slide_media are kept). Pass hard:true for a permanent delete (slide_elements + slide_media cascade).

ParameterTypeRequiredDescription
idstringyesUUID of the slide to delete.
hardbooleannoWhen true, permanently delete the slide (slide_elements + slide_media cascade). Default false → soft delete (recoverable via restore_slide).

restore_slide

Restore a soft-deleted slide (clears deleted_at/deleted_by so it reappears in list_slides).

Restore Slide

Restore a soft-deleted slide (clears deleted_at/deleted_by so it reappears in list_slides). Only works on slides that were soft-deleted via delete_slide; a hard-deleted slide is gone permanently.

ParameterTypeRequiredDescription
idstringyesUUID of the slide to restore.

search_slide_images

Search the stock photo library for an image to put on a slide, and get back candidate URLs.

Search Slide Images

Search the stock photo library for an image to put on a slide, and get back candidate URLs. Use it to fill an image-type template slot without a human opening the editor: pick a result and write its url with update_slide (template_data.image) or create_image_slide (image_url). This tool only reads — it never touches a slide. Match orientation to the project canvas so the crop is not fighting the layout. Results carry photographer and pexels_url: the licence asks for that credit, so keep it with the image.

ParameterTypeRequiredDescription
querystringyesWhat to look for, in English — e.g. "developer at a laptop", "solar panels". Stock search is keyword-based, so a short concrete noun phrase beats a sentence.
orientationenumnoCrop to ask the provider for. Defaults to landscape; pass portrait for a 9:16 project. social-portrait (Feed, 1080×1350 4:5) searches as portrait. One of: landscape, portrait, social-portrait.
per_pagenumbernoHow many candidates to return (default 8, max 20).

The boxes drawn on top of a slide’s template — text, image, or shape — that set_slide_animation’s element-reveal preset animates. Previously an agent could only animate a box the editor UI had already drawn; these four create, list, edit and delete the boxes themselves. create_slide_element/list_slide_elements return animation_index: a box’s position among the slide’s elements, ordered by creation time — the exact index a reveal beat with target: "element" must reference. delete_slide_element also repairs template_data.animation in the same call, so a deleted box’s beat does not silently keep pointing at the wrong one.

Text boxes, images and shapes placed freely on a slide.

create_slide_element

Create a free-canvas box on a slide: a text box, an image, or a shape drawn on top of the slide's template.

Create Slide Element

Create a free-canvas box on a slide: a text box, an image, or a shape drawn on top of the slide's template. position_x/position_y/width/height/rotation are DESIGN pixels — the canvas is 1280x720 for a landscape project and 720x1280 for a portrait one (the parent project's own orientation, not an argument here). A box entirely outside that canvas is rejected; a box that only partly overlaps it is allowed (that is a normal partial-off-canvas placement, not an error). content is validated by type: text.text is at most 2000 characters; image.url must be an http(s) URL; every color (text.color, shape.fill, shape.stroke, ...) must be a hex color (#rgb, #rrggbb, #rrggbbaa) or an rgba()/rgb() function; an unknown content key is rejected. A 'text' box may omit content fields — missing ones are filled from the Studio's own default text style, so { type: 'text', content: { text: 'Hello' } } is enough for a fully-styled box. 'image' and 'shape' boxes must supply every field their type needs. The response carries animation_index: the box's position among the slide's elements, ordered by creation time — THIS is the index a set_slide_animation "element-reveal" beat ({ kind: "reveal", target: "element", index }) must use to animate THIS box, because that index is the box's position in the stored array, never its z_index or the order it was drawn on screen. Example: create_slide_element({ slide_id, type: "text", content: { text: "Welcome" }, position_x: 100, position_y: 80, width: 600, height: 120 }) → { element: {...}, animation_index: 0 }.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide to add the box to.
typeenumyesBox kind: text, image, or shape. One of: text, image, shape.
contentobjectnoType-specific content. text: { text, fontFamily?, fontSize?, fontWeight?, color?, textAlign?, lineHeight?, letterSpacing?, fontStyle?, textTransform?, backgroundColor?, verticalAlign? } — missing fields fall back to the Studio default text style. image: { url, alt, objectFit, crop?, enter?, growFrom? } — url, alt and objectFit are required, no defaults. shape: { shapeType, fill, stroke, strokeWidth, borderRadius? } — all of shapeType/fill/stroke/strokeWidth are required.
position_xnumberyesLeft edge, design pixels.
position_ynumberyesTop edge, design pixels.
widthnumberyesBox width, design pixels.
heightnumberyesBox height, design pixels.
rotationnumbernoDegrees, clockwise (default 0)
opacitynumberno0 (invisible) to 1 (opaque); default 1.
z_indexnumbernoStacking order among the slide's boxes. Default: one above the highest existing box.

list_slide_elements

List a slide's free-canvas boxes (text/image/shape), ordered by creation time.

List Slide Elements

List a slide's free-canvas boxes (text/image/shape), ordered by creation time. Each element carries animation_index: its position in this order, and the exact index a reveal beat with target: "element" must reference to animate it — never the box's z_index or draw order. Example response: { elements: [{ id, type: "text", content: {...}, position_x, position_y, width, height, rotation, opacity, z_index, animation_index: 0 }, ...] }.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide.

update_slide_element

Update a free-canvas box's content and/or geometry (position_x/position_y/width/height/rotation/opacity/z_index).

Update Slide Element

Update a free-canvas box's content and/or geometry (position_x/position_y/width/height/rotation/opacity/z_index). Every field is optional — send only what changes. content is a PARTIAL patch: it is merged over the box's stored content and the merged object is re-validated against its type's rules (same rules as create_slide_element — text max 2000 chars, image url must be http(s), colors must be hex or rgba()/rgb(), unknown keys rejected). Geometry is design pixels, same canvas rule as create_slide_element: a box moved/resized fully outside the canvas is rejected. The box's type and its animation_index (set by create order) cannot be changed here. Example: update_slide_element({ id, content: { text: "Updated headline" }, position_x: 140 }).

ParameterTypeRequiredDescription
idstringyesUUID of the slide element.
contentobjectnoPartial content patch, merged then re-validated.
position_xnumbernoLeft edge, design pixels.
position_ynumbernoTop edge, design pixels.
widthnumbernoBox width, design pixels.
heightnumbernoBox height, design pixels.
rotationnumbernoDegrees, clockwise.
opacitynumberno0 (invisible) to 1 (opaque)
z_indexnumbernoStacking order among the slide's boxes.

delete_slide_element

Delete a free-canvas box and repair the slide's animation in the same call.

Delete Slide Element

Delete a free-canvas box and repair the slide's animation in the same call. A reveal beat (target: "element") addresses a box by its position among the slide's elements (animation_index), so removing a box shifts every later box's beat index down by one — this tool does that shift automatically: the removed box's own beat is dropped, later beats are reindexed keeping their effect/focus, and if no element beat survives template_data.animation is removed entirely (an animation with zero beats is not valid). The response reports removed_beat_count, shifted_beat_count and animation_cleared so the caller knows exactly what changed.

ParameterTypeRequiredDescription
idstringyesUUID of the slide element to delete.

template_data.animation makes a slide play: list items revealed one by one, a chat prompt typed out, a highlight wiped over a definition, an image easing in, a push-in zoom. The timings are a contract shared with dfl-render, so the editor preview and the exported MP4 show the same frames.

create_slide and update_slide already accept and validate a hand-written animation. These two tools exist so nobody has to write one by hand: the beats come from the slide’s own template_data and duration_ms, through the same defaults the editor’s “Add animation” button applies — already fitted to the slide length and valid at 24, 30 and 60 fps.

zoom-focus is the one preset with no template slot — it reads nothing from the slide, so list_animation_presets always reports it as runnable, on any template. A zoom beat also composes with every other preset: mix one into a steps-reveal/chat-typing/highlight/image-enter spec’s beats array and it pushes in on top, because it drives the synthetic slide target no template markup ever declares.

To remove an animation, call update_slide with clear_template_data_keys: ['animation'].

set_slide_animation writes the WHOLE beats array — the right call when applying or re-fitting a preset. These four edit ONE beat of an existing spec without recomputing every other default, and each write marks beat_source: 'manual' so the editor knows a beat was hand-tuned rather than re-derived from the preset.

Animate a slide with a preset, and edit its animation step by step.

list_animation_presets

The slide animation presets set_slide_animation can apply, with the template_data slots each one reads.

List Animation Presets

The slide animation presets set_slide_animation can apply, with the template_data slots each one reads. Pass template_id to also learn which of them that template can actually run: the answer is read from the live template markup (data-anim-target), so it cannot go stale. A preset whose slots the slide does not carry produces no beats — that is what requires describes.

ParameterTypeRequiredDescription
template_idstringnoSlide template id, as published in the dfl-slide-templates registry. When given, each preset also reports runs_on_template and the targets the template declares.

set_slide_animation

Animate a slide with one of the presets from list_animation_presets.

Set Slide Animation

Animate a slide with one of the presets from list_animation_presets. With no beats, the timings are computed from the slide's own template_data and duration_ms — the same defaults the Lesson Studio editor applies, already fitted to the slide length and valid at 24, 30 and 60 fps. Pass beats only for a timing the defaults do not express. "zoom-focus" is the one preset with no template slot — it works on any slide, pushing in and holding until a later zoom beat frames back out. A "zoom" beat also COMPOSES with every other preset (add one to the "beats" array of a steps-reveal/chat-typing/highlight/image-enter spec): it drives the synthetic "slide" target no template markup ever declares, so it never collides with that preset's own beats. Writes template_data.animation and leaves every other template_data key untouched. To remove an animation, call update_slide with clear_template_data_keys: ['animation'].

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide to animate.
presetenumyesAnimation preset. A preset the slide's template cannot drive is REFUSED here, not silently stored: call list_animation_presets("<template_id>") to see which ones run. A preset whose targets the template does not declare drives nothing, and one whose slots the slide does not carry is rejected here. "zoom-focus" always runs_on_template: true — it reads no slot and needs no declared target. "element-reveal" is the same kind of preset for the free-canvas elements of the slide (slide_elements rows, never a template_data slot): it needs at least one element on the slide. One of: steps-reveal, chat-typing, highlight, image-enter, zoom-focus, element-reveal.
beatsobject[]noExplicit beats, sorted by start_ms, every one ending before the slide duration_ms. Omit this to get the preset defaults fitted to the slide — that is the recommended path. Sending beats marks the spec beat_source "manual", so the editor will not silently re-time them. A "zoom" beat ({ kind: "zoom", target: "slide", start_ms, duration_ms?, rect: { x, y, width } }) may be mixed into ANY preset's beats to push in on part of the canvas; rect is fractions of the canvas (same shape as camera_box), width floored at 0.1 (10x zoom); a rect of { x:0, y:0, width:1 } zooms back out to the full canvas. A "reveal" beat on target "element" (one of a slide's free-canvas boxes, see create_slide_element/list_slide_elements for the index) may also carry: effect, one of "fade", "rise", "drop", "slide-left", "slide-right", "zoom-in", "shrink-in", "spin-shrink", "pop", "blur-in", "swing-in", "bounce-in", "scramble-in", "glitch-in", "slide-up", "slide-down", "flip-in", "wipe-right", "wipe-up", "typewriter-in", "count-up", "pulse", "shake", "wobble", "tada", "heartbeat", "spin", "ken-burns", "float", "breathe", "fade-out", "drop-out", "shrink-out", "spin-out", "blur-out", "glitch-out", "rise-out", "slide-left-out", "slide-right-out", "zoom-through-out", "wipe-left-out" — plays instead of the default fade + rise; and focus (boolean) — dims and blurs the rest of the slide while this box is the subject, for a camera-lens feel with no separate zoom beat. Example: { kind: "reveal", target: "element", index: 0, start_ms: 600, duration_ms: 400, effect: "pop", focus: true }.
tracksobject[]noKeyframe tracks for free-canvas boxes (spec version 2). Each track is { index, property, keyframes: [{ t_ms, value, easing }] }: property is one of "x", "y", "scale", "rotate", "opacity", "blur"; easing one of "linear", "in", "out", "inOut", "hold" and shapes the segment that STARTS at that keyframe ("hold" jumps at the next one). Values are relative to the box rest pose: x/y are design-pixel offsets, rotate a degree offset, blur added px, scale and opacity (0..1) multipliers. Keyframes strictly increasing in t_ms and before the slide end; one track per box and property; a box revealed by a beat with no effect cannot take tracks. With tracks and no beats the spec animates the boxes by tracks alone (no default entrances are added). Example: { index: 0, property: "x", keyframes: [{ t_ms: 0, value: 0, easing: "inOut" }, { t_ms: 1200, value: 300, easing: "linear" }] }.
formulasobject[]noFormula-driven box properties (spec version 2): { index, property, source, start_ms, end_ms }. source is an expression (max 200 chars) over t (seconds), p (0..1 across [start_ms, end_ms]), i (box index), w/h (the canvas) with + - * / % ^, unary minus, pi and sin cos abs min max clamp lerp step smoothstep noise(x, seed?). Same units as a track value; outside its window the value holds its end values; a non-finite result is identity. One driver per box and property: a property with a track cannot also take a formula. A parse error names the character position. Example: { index: 0, property: "y", source: "sin(t * 2 * pi) * 20", start_ms: 0, end_ms: 3000 }.
canvasobjectnoDesign canvas a formula reads as w/h. Defaults to the project's design canvas: 1280x720 landscape, 720x1280 portrait.
behavioursobject[]noOptional metadata naming the behaviour that wrote a box's formulas, so the Lesson Studio picker re-opens it: { index, id, params: { name: number }, owned: [properties] }, one per box; every owned property needs a formula on that box. It is NOT expanded here: send the formulas it stands for (the Studio catalog: float → y, orbit → x/y, shake → x/y, pulse → scale, drift → x/y, typewriter-cursor → opacity).

list_slide_beats

List the beats of a slide's template_data.animation, each tagged with the beat_index add_slide_beat/update_slide_beat/remove_slide_beat address.

List Slide Beats

List the beats of a slide's template_data.animation, each tagged with the beat_index add_slide_beat/update_slide_beat/remove_slide_beat address. Returns an empty beats array (not an error) when the slide carries no animation.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide.

add_slide_beat

Insert one beat into a slide's template_data.animation.beats.

Add Slide Beat

Insert one beat into a slide's template_data.animation.beats. The slide must already carry an animation (create one with set_slide_animation first). Beats must stay sorted by start_ms; a beat or at_index that breaks the order is rejected with the same validator set_slide_animation uses. Marks beat_source "manual". Returns the new beat_index.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide.
beatobjectyesThe beat to insert — same shape set_slide_animation's "beats" entries take, e.g. {kind:"zoom", target:"slide", start_ms, rect:{x,y,width}} or {kind:"reveal", target:"step", index, start_ms, duration_ms}.
at_indexnumbernoPosition to insert at (0 = first). Omit to append at the end.

update_slide_beat

Replace one beat of a slide's template_data.animation.beats, addressed by beat_index (from list_slide_beats).

Update Slide Beat

Replace one beat of a slide's template_data.animation.beats, addressed by beat_index (from list_slide_beats). beat replaces the ENTIRE beat, not just the fields you pass — send every field the beat needs, same shape set_slide_animation's "beats" entries take. Beats must stay sorted by start_ms after the replacement. Marks beat_source "manual".

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide.
beat_indexnumberyesIndex of the beat to replace (from list_slide_beats)
beatobjectyesThe full replacement beat, including kind.

remove_slide_beat

Remove one beat from a slide's template_data.animation.beats, addressed by beat_index (from list_slide_beats).

Remove Slide Beat

Remove one beat from a slide's template_data.animation.beats, addressed by beat_index (from list_slide_beats). A spec needs at least one beat, so removing the only beat is rejected — use update_slide with clear_template_data_keys: ['animation'] to drop the animation entirely. Marks beat_source "manual".

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide.
beat_indexnumberyesIndex of the beat to remove (from list_slide_beats)

The presenter’s webcam box: a project-wide default (projects.settings.camera_box / camera_aspect / camera_default) that any slide can override (slides.template_data.camera_box / camera) — the same precedence dfl-render resolves at export time, so the editor preview and the MP4 never disagree about which value won.

A box is either a named preset (size: small/medium/large × corner: one of the four corners), resolved against the project’s orientation and camera aspect, or an exact custom rectangle (x/y/width as canvas fractions, mirroring a zoom rect). A custom box is stored tagged mode: 'custom', mirroring dfl-lesson-studio’s own flag (PR #422) — this is what stops the editor from reading a hand-placed box as though some preset button chose it.

Camera aspect is 16:9 (the legacy PiP shape) or 1:1 (the square crop). Changing a project’s aspect with no explicit box in the same set_project_camera call reshapes the current project box AND every slide’s own camera_box override to fit the new aspect — the same cascade the Lesson Studio editor’s aspect switch performs.

Where the presenter's camera shows on the project or on one slide.

get_camera

Read the camera box + visibility for a project (project_id) and optionally one of its slides (slide_id).

Get Camera

Read the camera box + visibility for a project (project_id) and optionally one of its slides (slide_id). Reports the RAW override at each level (project default, slide override) plus the RESOLVED value that actually applies, following the same precedence dfl-render uses (slide, then project, then the built-in default). Pass slide_id alone to also resolve the project it belongs to.

ParameterTypeRequiredDescription
project_idstringnoUUID of the project. Required unless slide_id is given.
slide_idstringnoUUID of a slide, to also report its resolved/override camera.

set_project_camera

Set the project-wide camera default: aspect (16:9 or 1:1), box (preset size+corner or custom fractions), and default visibility (off/overlay).

Set Project Camera

Set the project-wide camera default: aspect (16:9 or 1:1), box (preset size+corner or custom fractions), and default visibility (off/overlay). Applies to every slide that does not override its own camera via set_slide_camera. Changing aspect with no box in the same call reshapes the current project box (and every slide's own camera_box override) to fit the new aspect, mirroring the Lesson Studio editor. Guarded on updated_at. focus {x, y} (0..1 of the camera frame) sets the project default crop point (settings.camera_focus); a slide focus (set_slide_camera) wins.

ParameterTypeRequiredDescription
project_idstringyesUUID of the project.
aspectenumnoCamera shape. Omit to leave it unchanged. One of: 16:9, 1:1.
boxobject | objectnoNew project-default camera box.
clear_boxbooleannoRemove settings.camera_box, so the project falls back to the built-in default.
default_visibilityenumnoCamera visibility for a slide with no override (default: overlay). One of: off, overlay.
clear_default_visibilitybooleannoRemove settings.camera_default.
focusobjectnoProject default camera crop point (settings.camera_focus).
clear_focusbooleannoRemove settings.camera_focus (the centre).

set_slide_camera

Override this slide's camera box and/or visibility, on top of the project default set_project_camera sets.

Set Slide Camera

Override this slide's camera box and/or visibility, on top of the project default set_project_camera sets. box resolves against the parent project's orientation and camera aspect. clear_box removes the box override (the slide follows the project box again); clear_visibility removes the visibility override. segments limits WHEN the corner (picture-in-picture) camera is drawn — refused on a fullscreen-webcam slide: a list of { start_ms, end_ms } windows in slide time (whole ms, sorted, non-overlapping, within the slide duration, 1-50 of them); the voice keeps playing outside them. clear_segments shows the camera for the whole slide again. To hide it on the whole slide use visibility: 'off'. focus {x, y} (0..1 of the camera frame) moves the crop of the camera off the centre, e.g. {x: 0.3, y: 0.5} for a face left of centre in a 16:9 take on a portrait slide; clear_focus goes back to the project focus / the centre. Preserves every other template_data key. Guarded on updated_at.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide.
boxobject | objectnoNew camera box override for this slide.
clear_boxbooleannoRemove this slide's camera_box override.
visibilityenumnoCamera visibility override for this slide. One of: off, overlay.
clear_visibilitybooleannoRemove this slide's camera visibility override.
segmentsobject[]noWindows (slide ms) in which the camera is drawn; stored as template_data.camera_segments.
clear_segmentsbooleannoRemove this slide's camera_segments (camera on the whole slide).
focusobjectnoThe point of the camera frame the crop keeps (template_data.camera_focus).
clear_focusbooleannoRemove this slide's camera_focus (the project focus, else the centre).

A recorded slide has ONE lesson_studio.slide_media row (the recording URL in storage_path, a trim window [start_trim_ms, end_trim_ms) on the SOURCE clock) and its captions in template_data.caption_cues ({text, startMs, endMs} on the same source clock). template_data.caption_cues_source names the recording the cues came from: the Lesson Studio editor re-transcribes a slide whose stamp is not its recording URL, so a tool that writes cues stamps them. caption_language records the cue language.

Attach a recording to slides, transcribe it, edit the captions, and make a project from one video.

set_slide_captions

Replace a slide's caption cues (template_data.caption_cues).

Set Slide Captions

Replace a slide's caption cues (template_data.caption_cues). Cues use the recording's SOURCE clock in ms (the file, not the slide), sorted and non-overlapping, max 2000. source_stamp 'current' (default) stamps caption_cues_source with the slide's recording URL, so the Studio editor does not re-transcribe and overwrite them; 'none' removes the stamp (the editor re-transcribes). Keeps every other template_data key. Guarded on updated_at.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide.
cuesobject[]yesCues on the recording (SOURCE) clock, ms: sorted, non-overlapping, endMs > startMs, text non-empty.
languagestringnoLanguage of the cues, BCP-47 (pt, en, pt-BR). Stored as caption_language.
source_stampenumno'current' (default): stamp with the slide recording URL. 'none': remove the stamp. One of: current, none.

edit_caption_cue

Edit ONE cue of a slide's caption_cues by its 0-based index: new text and/or startMs/endMs (source clock, ms).

Edit Caption Cue

Edit ONE cue of a slide's caption_cues by its 0-based index: new text and/or startMs/endMs (source clock, ms). The whole list must stay sorted and non-overlapping. Keeps the caption_cues_source stamp and every other key. Guarded on updated_at.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide.
indexnumberyes0-based index of the cue in caption_cues.
textstringnoNew text (non-empty)
startMsnumbernoNew start, source ms.
endMsnumbernoNew end, source ms.

set_slide_layout

Apply a camera/content layout preset to one slide.

Set Slide Layout

Apply a camera/content layout preset to one slide. 'fullscreen-camera': the webcam fills the frame, no slide content. 'overlay-top': webcam fullscreen, slide content drawn OVER it in the top half (content_over_camera). 'overlay-full': webcam fullscreen, slide content drawn OVER it on the WHOLE canvas (no safe band, scale 1) — for a full-canvas image. 'split-top-image': slide content on top, full-width camera band at the bottom (portrait only). The band crop keeps the FACE: it finds the face in the slide's recording and writes camera_focus there (else it keeps the upper part of the frame); an existing camera_focus is kept. On an image-only slide (fullscreen-image, no free elements) it trims the image's flat margins and contains the drawn area in the top band (content_zoom/offset; the trim is recorded in image_trim); a slide with text keeps the plain band fit. 'background-pip': slide content fullscreen, small camera bottom-right. Resolves boxes against the project orientation and camera aspect. One write, guarded on updated_at; returns the written and removed keys. Keeps every other template_data key. caption_position (with or without a preset) sets this slide's caption placement, e.g. 'over-camera' on a split slide so the captions do not cover the image.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide.
presetenumnoThe layout preset. Optional when caption_position is set. One of: fullscreen-camera, overlay-top, overlay-full, split-top-image, background-pip.
caption_positionenumnoWhere THIS slide's burned-in captions sit, over the project setting: top | middle | bottom (project semantics, the camera box is avoided), over-camera (bottom edge, NOT avoiding the camera: on a split slide the captions sit on the camera band, off the image), project (remove the override). One of: top, middle, bottom, over-camera, project.

transcribe_media

Transcribe a video/audio (URL or media_id) with dfl-render, optionally translated.

Transcribe Media

Transcribe a video/audio (URL or media_id) with dfl-render, optionally translated. Returns the text and caption cues {text, startMs, endMs} on the source clock. Waits up to ~45 s; if still running it returns job_id — then call get_media_transcription. Writes nothing; attach_slide_recording transcribes on its own.

ParameterTypeRequiredDescription
sourceobject | objectyesThe source: {url} or {media_id}.
languageenumnoSpoken language; default 'auto'. One of: auto, pt, en.
translate_tostringnoAlso return the cues translated to this language.
project_idstringnoUse this project's caption glossary (set_project_glossary).
glossarystring[]noCanonical terms (names, tickers) the captions must spell right. Default: the project glossary.
fix_cuesbooleannoAlso run the LLM cue-fix pass (word order, misheard words, glossary). Default false.

get_media_transcription

Read a transcription job that transcribe_media started: its status while it runs, else the text, cues and translation.

Get Media Transcription

Read a transcription job that transcribe_media started: its status while it runs, else the text, cues and translation.

ParameterTypeRequiredDescription
job_idstringyesThe job_id transcribe_media returned.

attach_slide_recording

Attach a raw recording (URL or media_id) to a slide: writes its slide_media row (one per slide), sets the slide duration to the trim window, starts the server transcode, and writes captions — transcribed by dfl-render…

Attach Slide Recording

Attach a raw recording (URL or media_id) to a slide: writes its slide_media row (one per slide), sets the slide duration to the trim window, starts the server transcode, and writes captions — transcribed by dfl-render (optionally translated) or the cues you pass — stamped so the editor keeps them. Waits up to ~45 s; if the transcription still runs, it returns transcription_job_id: then call get_slide_recording. WARNING: an open Lesson Studio tab on this project can overwrite the row on autosave; close it first.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide.
sourceobject | objectyesThe source: {url} or {media_id}.
media_typeenumnoDefault 'webcam_video'. One of: webcam_video, screen_recording, audio.
webcam_layoutenumnoAlso set the slide webcam_layout. One of: fullscreen, pip.
trimobjectnoWindow of the recording that plays on the slide, SOURCE ms [start, end).
duration_msnumbernoSource length, ms. Else estimated from the transcription.
transcribebooleannoDefault true. Ignored when captions is given.
languageenumnoSpoken language; default 'auto'. One of: auto, pt, en.
translate_tostringnoWrite the captions translated to this language.
captionsobject[]noWrite these cues (source clock) and skip transcription.
glossarystring[]noCanonical terms (names, tickers) the captions must spell right. Default: the project glossary.
fix_cuesbooleannoAlso run the LLM cue-fix pass (word order, misheard words, glossary). Default false.

get_slide_recording

Read a slide's recording: source URL, trim window, transcode_status (export needs 'done'; 'processing' still runs) and captions.

Get Slide Recording

Read a slide's recording: source URL, trim window, transcode_status (export needs 'done'; 'processing' still runs) and captions. With transcription_job_id (from attach_slide_recording) it finishes the attach once the job is done: fills an unknown length and writes the stamped captions, unless the slide already has captions for this recording.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide.
transcription_job_idstringnoThe job attach_slide_recording returned.

split_recording_across_slides

Spread ONE raw take over several slides.

Split Recording Across Slides

Spread ONE raw take over several slides. mode window (default): every slide points at the same file with its own trim window [start_ms, end_ms), keep ranges become template_data.clips, and ONE transcription of the whole file gives each slide ONLY its own cues (a cue goes to the slide whose window, or keep range, holds its midpoint, clamped to it). mode cut: dfl-render cuts real files (/media/cut, optional fit) and each slide gets its part, with its own cues shifted onto it. create_slides appends new slides for parts without slide_id. Waits ~45 s; if a job still runs it returns job ids to resume with. An open Studio tab on the project can overwrite the rows on autosave.

ParameterTypeRequiredDescription
sourceobject | objectyesThe source: {url} or {media_id}.
partsobject[]yesOne entry per slide, in slide order.
create_slidesobjectno—
modeenumnoDefault 'window'. One of: window, cut.
fitenumnocut mode only. One of: none, portrait-crop, portrait-letterbox.
media_typeenumnoDefault 'webcam_video'. One of: webcam_video, screen_recording, audio.
duration_msnumbernoSource length, ms, when known.
transcribebooleannoDefault true.
languageenumnoOne of: auto, pt, en.
translate_tostringnoLanguage of the cues, BCP-47 (pt, en, pt-BR). Stored as caption_language.
transcription_job_idstringnoResume: the job a previous call returned.
cut_job_idstringnoResume: the cut job a previous call returned.
glossarystring[]noCanonical terms (names, tickers) the captions must spell right. Default: the project glossary.
fix_cuesbooleannoAlso run the LLM cue-fix pass (word order, misheard words, glossary). Default false.

import_media_from_url

Import a clip from a public video URL (e.g. a YouTube/X post) through dfl-render: download, optional [start_s, end_s) cut, optional portrait fit, transcription and translation.

Import Media From URL

Import a clip from a public video URL (e.g. a YouTube/X post) through dfl-render: download, optional [start_s, end_s) cut, optional portrait fit, transcription and translation. register_media makes it a public.media row; attach_to_slide_id attaches it to a slide with its (translated) cues, with no second transcription. as_insert (with attach_to_slide_id) makes it an INSERT slide: the clip on its own slide, contained, no camera treatment, optional insert_label. Waits ~45 s; else returns job_id — then call get_media_import. Use only media you may reuse.

ParameterTypeRequiredDescription
urlstringyesPublic page or file URL of the video.
start_snumbernoClip start, seconds.
end_snumbernoClip end, seconds.
fitenumnoOne of: none, portrait-crop, portrait-letterbox.
transcribebooleannoDefault true.
languageenumnoOne of: auto, pt, en.
translate_tostringnoTranslate the cues to this language.
attach_to_slide_idstringnoAttach the clip to this slide.
webcam_layoutenumnoWith attach_to_slide_id. One of: fullscreen, pip.
as_insertbooleannoWith attach_to_slide_id: make it an INSERT slide (its own slide, contained, no camera treatment).
insert_labelstringnoWith as_insert: a name label under the clip.
register_mediabooleannoAlso create a public.media row (default false).

get_media_import

Read an import_media_from_url job.

Get Media Import

Read an import_media_from_url job. While it runs, its status. When done, it does the same finish: register_media and/or attach_to_slide_id (pass them again). Safe to call again.

ParameterTypeRequiredDescription
job_idstringyesThe job_id import_media_from_url returned.
attach_to_slide_idstringno—
webcam_layoutenumnoOne of: fullscreen, pip.
as_insertbooleanno—
insert_labelstringno—
register_mediabooleanno—

set_slide_insert

Make a slide an INSERT slide: an inserted video on its own slide (the video cuts from the camera to the clip, then back).

Set Slide Insert

Make a slide an INSERT slide: an inserted video on its own slide (the video cuts from the camera to the clip, then back). The slide recording plays as a screen recording — contained, no camera box, no camera focus, no face crop — and the camera keys go. label (e.g. "@rohanpaul_ai") adds a name label box under the clip, drawn above the video in the export; label null removes it. The slide needs a recording (import_media_from_url with as_insert does all of this in one call). Captions stay as they are.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide with the clip.
labelstringnoName label text; null removes it; omit to keep it.

create_project_from_video

Turn one raw take into a new Lesson Studio project in one call: creates the project (portrait by default, no project camera box) with burned-in caption settings, a fullscreen-camera slide per part (default: one slide…

Create Project From Video

Turn one raw take into a new Lesson Studio project in one call: creates the project (portrait by default, no project camera box) with burned-in caption settings, a fullscreen-camera slide per part (default: one slide, the whole take), attaches the recording, transcribes (optionally translates) and writes stamped captions. parts has the split_recording_across_slides shape. Returns project, composition and slide ids; if a job still runs it returns the ids to finish with get_slide_recording or split_recording_across_slides.

ParameterTypeRequiredDescription
titlestringyesProject title.
sourceobject | objectyesThe source: {url} or {media_id}.
orientationenumnoDefault 'portrait'. One of: portrait, landscape.
captionsobjectno—
partsobject[]noDefault: one slide, the whole take.
modeenumnoWith parts. Default 'window'. One of: window, cut.
webcam_layoutenumnoDefault 'fullscreen'. One of: fullscreen, pip.
duration_msnumbernoSource length, ms, when known.
languageenumnoOne of: auto, pt, en.
translate_tostringnoLanguage of the cues, BCP-47 (pt, en, pt-BR). Stored as caption_language.
glossarystring[]noCaption glossary: stored on the new project and applied now.
fix_cuesbooleannoAlso run the LLM cue-fix pass (word order, misheard words, glossary). Default false.

set_project_glossary

Set a Lesson Studio project's caption glossary: the canonical spellings (names, tickers — 'Bessent', 'USDC') that the server transcription applies to the captions (a whole-word near match becomes the term; timings never…

Set Project Caption Glossary

Set a Lesson Studio project's caption glossary: the canonical spellings (names, tickers — 'Bessent', 'USDC') that the server transcription applies to the captions (a whole-word near match becomes the term; timings never change). transcribe_media (with project_id), attach_slide_recording, split_recording_across_slides and create_project_from_video use it by default. Replaces the list; terms: [] clears it. Stored in projects.settings.caption_glossary; guarded on updated_at.

ParameterTypeRequiredDescription
project_idstringyesThe project.
termsstring[]yesThe full list (replaces the old one).

upload_slide_image

Upload an image (base64 PNG, JPEG, WebP or GIF, at most 6 MB; the type is read from the bytes, SVG is refused) to DFL media as the caller, PUBLIC (the editor and the export load slide images by URL).

Upload Slide Image

Upload an image (base64 PNG, JPEG, WebP or GIF, at most 6 MB; the type is read from the bytes, SVG is refused) to DFL media as the caller, PUBLIC (the editor and the export load slide images by URL). Returns the url. With slide_id it also places the image as an image box on that slide: by default the WHOLE canvas (720x1280 portrait, 1280x720 landscape) with fit 'contain', above the other boxes — pair it with set_slide_layout 'overlay-full' for a full-canvas overlay over a fullscreen camera. box {x,y,width,height} in design pixels places it elsewhere. Use it in place of an upload outside the Studio.

ParameterTypeRequiredDescription
file_contentstringyesThe image, base64.
file_namestringyesA name for the file; the extension comes from the bytes.
slide_idstringnoPlace the image on this slide.
altstringnoAlt text for the box (default: the file name).
fitenumnoDefault 'contain'. One of: contain, cover, fill.
boxobjectnoDesign pixels. Default: the whole canvas.

template_data.sfx is a Studio-owned key: a list of {id, sound, src, at_ms, volume?} entries, sound a key from the catalog below, at_ms the played ms offset from the slide start (same clock as animation beats) a sound must START before, volume 0..2 (omitted when 1). create_slide and update_slide accept the short-hand template_data.sfx: [{sound, at_ms, volume?}] directly — id/src are filled in from the catalog.

Add sound effects to a slide.

list_sound_effects

The sound-effect catalog available to template_data.sfx / set_slide_sfx: every valid sound key, its category, use-case hint and duration.

List Sound Effects

The sound-effect catalog available to template_data.sfx / set_slide_sfx: every valid sound key, its category, use-case hint and duration. Read-only, no database access. Pass category to filter.

ParameterTypeRequiredDescription
categorystringnoRestrict to one category id (see the returned categories list).

set_slide_sfx

Write this slide's sound effects (template_data.sfx).

Set Slide Sound Effects

Write this slide's sound effects (template_data.sfx). Each entry is {sound, at_ms, volume?} — sound is a catalog key from list_sound_effects, at_ms must start before the slide duration_ms. mode "replace" (default) discards the stored list; "append" adds to it. An empty array with "replace" removes the key. Preserves every other template_data key. Guarded on updated_at.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide.
sfxobject[]yesThe sound effects to write (or add, in append mode).
modeenumnoDefault "replace". One of: replace, append.

A deck may mark ONE slide as its cover (template_data.is_cover === true, strictly). The cover is captured on its own for the deck’s video thumbnail (capture_cover_slide, see Render below), but dfl-render excludes it from the rendered video entirely: not a frame, not the slide count, not the 01/09 deck index. See dfl-services:services/dfl-render/src/lib/cover-slide.ts for the canonical contract this mirrors.

Pick the cover slide of the deck.

set_cover_slide

Mark a slide as the deck's cover (template_data.is_cover), or unmark it (is_cover: false).

Set Cover Slide

Mark a slide as the deck's cover (template_data.is_cover), or unmark it (is_cover: false). The cover is used for the video thumbnail via capture_cover_slide, but dfl-render EXCLUDES it from the rendered video, its slide count, and the 01/09 deck index. Setting is_cover: true clears the flag from every other slide in the same composition first — a deck has at most one cover. Preserves every other template_data key. To set the composition's thumbnail after capturing it, chain the result into whichever tool writes projects.thumbnail_url (this tool never writes it).

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide to mark or unmark as the cover.
is_coverbooleannotrue (the default) to make this slide the cover; false to unmark it.

Save and list versions of a project.

list_project_versions

List the versions of a Lesson Studio project, ordered by version_number descending (current/highest first).

List Project Versions

List the versions of a Lesson Studio project, ordered by version_number descending (current/highest first).

ParameterTypeRequiredDescription
project_idstringyesUUID of the project.

bump_project_version

Create a new version of a Lesson Studio project (version_number = max existing + 1, starting at 1).

Bump Project Version

Create a new version of a Lesson Studio project (version_number = max existing + 1, starting at 1). Subsequent comments are stamped to this new version. Returns the created version row.

ParameterTypeRequiredDescription
project_idstringyesUUID of the project to bump.
labelstringnoOptional human label for this version (e.g. "after canvas review pass 1")

Write a new lesson in the style of a specific tutor.

generate_lesson_from_profile

Author a NEW lesson topic in a specific tutor's proven pedagogical style.

Generate Lesson From Tutor Profile

Author a NEW lesson topic in a specific tutor's proven pedagogical style. Loads that tutor's stored pedagogy profile (lms.tutor_profiles) and returns a Studio-importable GENERATION SPEC: a typology-biased slide plan (template picks ranked by the professor's dominant lesson-type via search_templates' ranker), the professor's SIGNATURE as few-shot exemplars (verbatim metaphors/openers/cotidiano anchors), and amplify/reduce directives (amplify high-consistency moves, reduce vícios). Deterministic — the tool does NOT call an LLM; the calling agent expands each suggested slide into content, few-shotting the exemplars and honoring the directives. The rubric (HALF A) is never used for generation (de-circularization).

ParameterTypeRequiredDescription
tutor_idstringyeswork.members.id of the tutor whose stored pedagogy profile to author in (matches lms.tutor_profiles.member_id).
topicstringyesThe new lesson topic to author in that tutor's style, e.g. "Introdução a APIs REST".
versionstringnoProfile version to load. Defaults to the newest (most recently updated) profile for the tutor.
n_slidesnumbernoTarget slide count / number of template picks (default 8).
canvasstringnoOnly pick templates that declare this design canvas (e.g. "social-portrait").
project_idstringnoPick only templates that fit this project's canvas. Ignored when canvas is given.

Turn stored slides into PNGs through the deterministic headless capture in dfl-render. Both tools run with the calling user’s own credentials, and both answer with the reproduction key rather than only a URL: the dfl-slide-templates commit revision the template was read at, plus a composition fingerprint of the deck state the derived slide index came from. Two renders are comparable only when both match — the slide index is derived, so inserting a sibling changes an already-rendered slide’s pixels on purpose.

canvas is optional. A canvas the template does not declare is refused and no image is produced; there is deliberately no fallback to the nearest canvas.

Turn slides into PNG images, including the cover image.

render_slide_image

Render one stored slide to a PNG and return its URL.

Render Slide Image

Render one stored slide to a PNG and return its URL. Wraps the deterministic headless capture in dfl-render with the CALLING user's own credentials. Returns the dfl-slide-templates commit revision the template was read at AND a composition fingerprint of the deck state the derived slide index came from — together those two reproduce the render. canvas is optional; a canvas the template does not declare is REFUSED and no image is produced (never a fallback to the nearest canvas).

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide to render.
canvasstringnoDesign canvas id, e.g. "landscape", "portrait", "social-portrait". Omit to use the parent project's orientation. Validated against the declared canvas contract AND against the canvases the slide's template ships; an undeclared canvas errors and produces no image.

render_composition_images

Render every surviving slide of a composition to a PNG and return the URLs in order_index order — the batch form of render_slide_image, and the tool a carousel needs.

Render Composition Images

Render every surviving slide of a composition to a PNG and return the URLs in order_index order — the batch form of render_slide_image, and the tool a carousel needs. Soft-deleted slides are skipped. Captures run with bounded concurrency. Returns the dfl-slide-templates commit revision and ONE deck-level composition fingerprint shared by every image. canvas is optional; a canvas any template in the deck does not declare is REFUSED before anything is captured.

ParameterTypeRequiredDescription
composition_idstringyesUUID of the composition to render.
canvasstringnoDesign canvas id, e.g. "landscape", "portrait", "social-portrait". Omit to use each slide's parent project orientation. Every distinct template in the deck must declare it, or the whole call errors and no image is produced.

capture_cover_slide

Render the composition's cover slide (template_data.is_cover, set by set_cover_slide) to a PNG for the deck's video thumbnail, and return its image_url.

Capture Cover Slide

Render the composition's cover slide (template_data.is_cover, set by set_cover_slide) to a PNG for the deck's video thumbnail, and return its image_url. Errors clearly when the composition has no cover slide. This does NOT write projects.thumbnail_url — no tool in dfl-mcp-studio does yet — so persist the returned image_url yourself once such a tool exists.

ParameterTypeRequiredDescription
composition_idstringyesUUID of the composition whose cover slide to capture.

Render one YouTube thumbnail (1280x720, youtube_gold layout) through the Thumbify renderer on dfl-services. The tool stores nothing: to change a thumbnail, call it again with changed inputs. Every image input must be a public URL.

Make a YouTube thumbnail image.

generate_thumbnail

Render a gold-standard DevFellowship YouTube thumbnail (1280x720) via the youtube_gold layout.

Generate YouTube Thumbnail

Render a gold-standard DevFellowship YouTube thumbnail (1280x720) via the youtube_gold layout. Layers, back to front: background_image -> behind_object (behind the character) -> character (alpha-cut photo on the right) -> tech logos (top-left) -> condensed bold title with green highlight (bottom-left). character_url MUST be a background-removed (alpha) photo; all image inputs must be public URLs the renderer can fetch (upload local files first). Returns the rendered PNG URL.

ParameterTypeRequiredDescription
titlestringyesHeadline / SEO keyword. Rendered UPPERCASE, condensed bold, auto-fit.
character_urlstringyesCharacter photo URL with background ALREADY removed (alpha PNG). Placed large on the right.
background_imagestringnoFull-bleed background image URL. Omit for the brand teal gradient.
behind_objectstringnoNo-bg object/mockup PNG URL, layered ABOVE the background but BEHIND the character.
behind_object_boxobjectnoPlacement box for behind_object in 1280x720 reference px. Omit for full-canvas centered contain.
screenshot_urlstringnoOptional legacy center screenshot layer (above background, below character).
logosenum[]noTech-stack logo keys (top-left tiles). Allowed: react, tailwind, vite, typescript, javascript, nextjs, node, python, supabase, postgresql, docker, go. Inferred from title when omitted.
highlightstring[]noWords to paint green in the title. Heuristic (longest content words) when omitted.
character_brightnessnumbernoBrightness multiplier for the character/person layer (CSS brightness()). Default 1.1. Range ~0.5-2.0; >1 brightens the person to pop against dark backgrounds.
character_contrastnumbernoContrast multiplier for the character/person layer (CSS contrast()). Default 1.15. Range ~0.5-2.0.
widthnumbernoOutput width. Default 1280.
heightnumbernoOutput height. Default 720.

Find, read and change slide templates.

list_templates

List all slide templates available in dfl-slide-templates (reads registry.json from GitHub).

List Templates

List all slide templates available in dfl-slide-templates (reads registry.json from GitHub).

Takes no parameters.

search_templates

Find the best slide templates for an intent.

Search Templates

Find the best slide templates for an intent. Returns a RANKED shortlist (top-N) instead of the full list — give a natural-language intent (e.g. "compare two options head-to-head", "mostrar uma captura de tela", "one big hero number") and get back the templates whose registry "when_to_use"/tags/media_profile best match, each with a relevance score and a one-line reason. Use this to PICK a template, then call get_template(id) for its slots. Deterministic + read-only (no auth needed).

ParameterTypeRequiredDescription
intentstringyesNatural-language description of what the slide should do (English or Portuguese), e.g. "comparar duas opções", "show a screenshot", "single big statistic".
limitnumbernoMax templates to return in the shortlist (default 5).
canvasstringnoOnly templates that declare this design canvas (e.g. "social-portrait"). Omit for no canvas filter.
project_idstringnoFilter by this project's canvas (its orientation). Ignored when canvas is given.

get_template

Fetch all source files (HTML landscape, HTML portrait, CSS landscape, CSS portrait, config YAML) for a specific template.

Get Template

Fetch all source files (HTML landscape, HTML portrait, CSS landscape, CSS portrait, config YAML) for a specific template.

ParameterTypeRequiredDescription
idstringyesTemplate ID (e.g. "title", "bullet-list", "two-column")

update_template

Open a ready-to-merge PR in dfl-slide-templates with updated HTML/CSS and optional config YAML for a template.

Update Template

Open a ready-to-merge PR in dfl-slide-templates with updated HTML/CSS and optional config YAML for a template. All provided fields are written in a single commit. Returns the PR URL. registry.json is edited as a MERGE: any registry_* field you omit keeps its current value, so a version bump can no longer erase the discoverability metadata search_templates ranks on. Pass null to delete a field. CREATING a template (an id not yet in registry.json) requires registry_version, registry_name, registry_when_to_use, registry_media_profile and registry_tags, so the new template is findable from the moment it merges.

ParameterTypeRequiredDescription
idstringyesTemplate ID to update (e.g. "title")
descriptionstringyesHuman-readable description of the change (used as PR/commit title suffix)
html_landscapestringyesNew landscape.html content.
css_landscapestringyesNew landscape.css content.
html_portraitstringyesNew portrait.html content.
css_portraitstringyesNew portrait.css content.
config_yamlstringnoNew config.yaml content (optional — skip to leave unchanged)
registry_versionstringnoBump the registry version (e.g. "1.1.0") — omit to leave unchanged. REQUIRED when creating a new template.
registry_categorystringnoUpdate the registry category (content | layout | data | …) — omit to leave unchanged. Defaults to "content" on create only.
registry_namestringnoHuman display name, e.g. "Title Slide". search_templates ranks on it. Omit to leave unchanged; null to delete. REQUIRED when creating.
registry_when_to_usestringnoWhen an authoring agent SHOULD reach for this template — the highest-signal ranking field. Omit to leave unchanged; null to delete. REQUIRED when creating.
registry_avoid_whenstringnoAnti-patterns: when NOT to use this template. Omit to leave unchanged; null to delete.
registry_media_profileenumnoWhat kind of content this template is shaped for. Omit to leave unchanged; null to delete. REQUIRED when creating. One of: text-heavy, balanced, image-first, image-only, video, data, code, diagram.
registry_text_densityenumnoHow much copy the layout carries. Omit to leave unchanged; null to delete. One of: none, low, medium, high.
registry_layoutstringnoOne-line shape description, e.g. "centered big headline + subtitle, no media". Omit to leave unchanged; null to delete.
registry_tagsstring[]noFree keywords for matching — the heaviest-weighted ranking field. Replaces the whole list (never merged element-wise). Omit to leave unchanged; null to delete. REQUIRED when creating.

Read and change the CSS themes of slides.

list_themes

List the CSS themes that actually exist in dfl-slide-templates at the pinned revision.

List Themes

List the CSS themes that actually exist in dfl-slide-templates at the pinned revision. The set is DERIVED from the repo (registry.json themes[]), so a theme added upstream appears here with no code change. Each entry reports whether its stylesheet resolved, plus its display name and light/dark mode when declared. The response names the commit it was read from and flags a degraded read.

Takes no parameters.

get_theme

Fetch the CSS source for a specific theme.

Get Theme

Fetch the CSS source for a specific theme.

ParameterTypeRequiredDescription
idstringyesTheme ID as reported by list_themes (e.g. "default", "devfellowship", "itera", "revera")

update_theme

Open a ready-to-merge PR in dfl-slide-templates with updated CSS for a theme.

Update Theme

Open a ready-to-merge PR in dfl-slide-templates with updated CSS for a theme. Returns the PR URL. The PR also REGISTERS the theme in registry.json's themes[] — the array themes_doc calls the source of truth and list_themes reads — so a theme published here is discoverable instead of being a stylesheet nothing lists. Registering a NEW theme requires theme_name and theme_mode: neither a brand's display name nor its light/dark mode can be inferred, and a guess would become the source of truth. A new theme also needs its forbidden-colour contract in scripts/theme.config.json, or lint:css fails — that file is human-gated, so a brand-new theme cannot merge unattended.

ParameterTypeRequiredDescription
idstringyesTheme ID to update, as reported by list_themes (e.g. "devfellowship", "itera")
descriptionstringyesHuman-readable description of the change (used as PR/commit title suffix)
cssstringyesNew CSS content for the theme file.
theme_namestringnoDisplay name for the registry themes[] entry, e.g. "Itera". Omit to leave an existing theme unchanged. REQUIRED when registering a new theme.
theme_modeenumnoAdvisory light/dark hint for the host chrome around the slide (the slide always paints its own surface). Omit to leave an existing theme unchanged. REQUIRED when registering a new theme. One of: light, dark.