Technical referenceAPI signatures
Timeline and production API
Access these methods through project.production. Board mutations use global integer frames. Some inspection methods also accept shot-animation IDs and return local-frame data; see timeline queries and drawing queries. Read values are inspection copies. For mutations use board animation, shot-local edits, camera, board audio, and components.
Use the signatures below to check arguments and return types. A value after = is the default; ? marks an optional input. Named data shapes are listed in API types.
ProductionTools
captureComponent
captureComponent(layerId: Id, name: string, options: MutationOptions & {
id?: Id;
} = {}): Id;reviseComponent
reviseComponent(id: Id, sourceLayerId: Id, options: MutationOptions = {}): void;replaceComponentSource
replaceComponentSource(componentId: Id, layers: readonly Layer[], options: MutationOptions & {
expectedComponentVersion: number;
}): void;replaceComponentElement
replaceComponentElement(componentId: Id, layerId: Id, element: DrawingElement, options: MutationOptions & {
expectedComponentVersion: number;
}): void;instantiateComponent
instantiateComponent(componentId: Id, panelId: Id, transform: Partial<Transform> = {}, options: MutationOptions & {
id?: Id;
} = {}): Id;upgradeComponentInstance
upgradeComponentInstance(instanceId: Id, options: ComponentUpgradeOptions & MutationOptions & {
expectedInputHash: string;
}): void;refreshComponentInstance
refreshComponentInstance(layerId: Id, options: MutationOptions & {
comments?: "reject" | "anchor-to-instance";
} = {}): void;moveLayer
moveLayer(layerId: Id, beforeLayerId?: Id, options: MutationOptions = {}): void;reparentLayer
reparentLayer(layerId: Id, parentId: Id | null, options: MutationOptions & {
beforeLayerId?: Id;
} = {}): void;removeLayer
removeLayer(layerId: Id, options: MutationOptions = {}): void;find
find(query: ObjectQuery = {}): ObjectSummary[];query
query(query: ObjectPageQuery = {}): ObjectPage;summary
summary(): {
studio: { animations: number; editorialSequences: number; editorialClips: number; audioTracks: number; audioClips: number; };
schemaVersion: 5;
version: number;
canvas: { width: number; height: number; background: string; };
frameRate: number;
durationFrames: number;
counts: { sequences: number; scenes: number; shots: number; panels: number; components: number; assets: number; audioTracks: number; comments: number; locks: number; };
titleTruncated?: boolean | undefined;
id: string;
title: string;
};coordinates
coordinates(targetId: Id, options: CoordinateOptions = {}): CoordinateSpace;layer
layer(id: Id): Layer;layerKeyframes
layerKeyframes(id: Id, options: PageOptions = {}): LayerKeyframe[];cameraKeyframes
cameraKeyframes(shotId: Id, options: PageOptions = {}): CameraKeyframe[];element
element(id: Id): DrawingElement;drawingSequence
drawingSequence(groupId: Id): {
keys: DrawingExposure[] | null;
drawings: { id: Id; name: string; kind: Layer["kind"]; }[];
};drawingExposures
drawingExposures(groupId: Id, options: PageOptions = {}): DrawingExposure[] | null;drawingAlternatives
drawingAlternatives(groupId: Id, options: PageOptions = {}): { id: Id; name: string; kind: Layer["kind"]; }[];setPlaneDepth
setPlaneDepth(layerId: Id, depth: number, options: MutationOptions = {}): void;drawingNeighbors
drawingNeighbors(groupId: Id, frame: number, options: {
skipBlank?: boolean;
} = {}): DrawingNeighbors;twoBoneRig
twoBoneRig(rootId: Id): TwoBoneRig | null;setTwoBoneRig
setTwoBoneRig(rootId: Id, definition: TwoBoneRig | null, options: MutationOptions = {}): void;poseTwoBoneRig
poseTwoBoneRig(rootId: Id, frame: number, target: {
x: number;
y: number;
}, options: MutationOptions & {
bend?: 1 | -1;
easing?: Easing;
unreachable?: "reject" | "clamp";
} = {}): TwoBoneSolution;setDrawingSequence
setDrawingSequence(groupId: Id, keys: readonly DrawingExposure[] | null, options: MutationOptions = {}): void;duplicateDrawing
duplicateDrawing(groupId: Id, drawingId: Id, name: string, options: MutationOptions = {}): Id;setDrawingRange
setDrawingRange(groupId: Id, startFrame: number, endFrame: number, drawingId: Id | null, options: MutationOptions = {}): void;setExposure
setExposure(layerId: Id, exposure: Layer["exposure"], options: MutationOptions = {}): void;inspect
inspect(): {
readonly schemaVersion: 5;
readonly version: number;
readonly frameRate: number;
readonly durationFrames: number;
readonly scenes: { shots: (Shot | undefined)[]; sequenceId: Id; id: Id; name: string; shotIds: Id[]; }[];
readonly sequences: Sequence[];
readonly assets: Asset[];
readonly audioTracks: AudioTrack[];
readonly locks: ProjectLock[];
readonly openComments: ReviewComment[];
readonly capabilities: { readonly vectorFill: "solid-linear-radial-local-coordinates"; readonly rasterPainting: "working"; readonly vectorStrokeEditing: "working"; readonly vectorBooleans: "closed-contours-skia"; readonly vectorStrokeOutlining: "explicit-editable-contour-conversion"; readonly pixelRegionEditing: "rgba8-source-rectangles"; readonly pixelSelections: "polygon-color-flood-combination-gaussian-feather"; readonly pixelFill: "source-over-copy-destination-out-source-atop"; readonly timeline: "working"; readonly camera2d: "independent-property-keys-and-easing"; readonly layerAnimation: "independent-property-keys-and-easing"; readonly animationEasing: "linear-smoothstep-hold-bounded-cubic-bezier"; readonly layerPivots: "permanent-local-joints"; readonly twoBoneIK: "stored-cutout-rig-baked-rotation-keys"; readonly drawingSequences: "reusable-drawings-holds-blanks"; readonly audioPlacement: "working"; readonly multiplane: "independent-root-depth-keys-2d-parallax"; readonly audioMixdown: "ffmpeg"; readonly audioInspection: "paged-tracks-clips-and-frame-filter"; readonly animaticFrameExport: "working"; readonly movieExport: "ffmpeg"; readonly referenceAssets: "partial"; readonly onionSkin: "layer-selection-tint-frame-samples"; readonly coordinateInspection: "animated-local-frame-matrices"; readonly renderComparison: "premultiplied-pixel-deltas"; readonly compositionGuides: "frame-space-review-overlay"; readonly reviewLocks: "working"; };
};audioTracks
audioTracks(options: PageOptions = {}): AudioTrackSummary[];audioClips
audioClips(trackId: Id, options: AudioClipQuery = {}): AudioClip[];audioClip
audioClip(id: Id): AudioClip & {
trackId: Id;
};changesSince
changesSince(version: number, options: PageOptions = {}): ChangeEntry[];brush
brush(id: Id): BrushPreset;createBrush
createBrush(definition: Omit<BrushPreset, "id" | "version"> & {
id?: Id;
}, options: MutationOptions = {}): Id;reviseBrush
reviseBrush(id: Id, changes: Partial<Omit<BrushPreset, "id" | "version">>, options: MutationOptions = {}): number;duplicateBrush
duplicateBrush(id: Id, name: string, options: MutationOptions = {}): Id;setPanelDuration
setPanelDuration(panelId: Id, durationFrames: number, mode: "ripple" | "preserve" = "ripple", options: MutationOptions = {}): void;setTransition
setTransition(panelId: Id, transition: Transition, options: MutationOptions = {}): void;movePanel
movePanel(panelId: Id, beforePanelId?: Id, options: MutationOptions = {}): void;duplicatePanel
duplicatePanel(panelId: Id, options: MutationOptions = {}): Id;deletePanel
deletePanel(panelId: Id, options: MutationOptions = {}): void;setPanelStatus
setPanelStatus(panelId: Id, status: Panel["status"], options: MutationOptions = {}): void;setPanelNumber
setPanelNumber(panelId: Id, number: string, options: MutationOptions = {}): void;addCameraKeyframe
addCameraKeyframe(shotId: Id, frame: number, value: CameraKeyframeInput, options: MutationOptions = {}): Id;updateCameraKeyframe
updateCameraKeyframe(shotId: Id, keyframeId: Id, changes: CameraKeyframeChanges, options: MutationOptions = {}): void;removeCameraKeyframe
removeCameraKeyframe(shotId: Id, keyframeId: Id, options: MutationOptions = {}): void;removeCameraKeyframeChannels
removeCameraKeyframeChannels(shotId: Id, keyframeId: Id, channels: readonly CameraChannel[], options: MutationOptions = {}): void;addLayerKeyframe
addLayerKeyframe(layerId: Id, frame: number, value: LayerKeyframeInput, options: MutationOptions = {}): Id;updateLayerKeyframe
updateLayerKeyframe(layerId: Id, keyframeId: Id, changes: LayerKeyframeChanges, options: MutationOptions = {}): void;removeLayerKeyframe
removeLayerKeyframe(layerId: Id, keyframeId: Id, options: MutationOptions = {}): void;removeLayerKeyframeChannels
removeLayerKeyframeChannels(layerId: Id, keyframeId: Id, channels: readonly LayerChannel[], options: MutationOptions = {}): void;addAsset
addAsset(asset: Omit<Asset, "id"> & {
id?: Id;
}, options: MutationOptions = {}): Id;updateAsset
updateAsset(id: Id, changes: Partial<Omit<Asset, "id">>, options: MutationOptions = {}): void;addAudioTrack
addAudioTrack(name: string, options: MutationOptions & {
id?: Id;
} = {}): Id;updateAudioTrack
updateAudioTrack(trackId: Id, changes: AudioTrackChanges, options: MutationOptions = {}): void;removeAudioTrack
removeAudioTrack(trackId: Id, options: MutationOptions = {}): void;addAudioClip
addAudioClip(trackId: Id, clip: AudioClipInput, options: MutationOptions = {}): Id;updateAudioClip
updateAudioClip(trackId: Id, clipId: Id, changes: AudioClipChanges, options: MutationOptions = {}): void;removeAudioClip
removeAudioClip(trackId: Id, clipId: Id, options: MutationOptions = {}): void;moveAudioClip
moveAudioClip(clipId: Id, targetTrackId: Id, options: MutationOptions & {
startFrame?: number;
} = {}): void;splitAudioClip
splitAudioClip(clipId: Id, frame: number, options: MutationOptions = {}): Id;comment
comment(body: string, anchor: ReviewComment["anchor"], options: MutationOptions & {
author?: string;
} = {}): Id;resolveComment
resolveComment(commentId: Id, options: MutationOptions = {}): void;lock
lock(targetType: ProjectLock["targetType"], targetId: Id, reason: string, options: MutationOptions = {}): Id;unlock
unlock(lockId: Id, options: MutationOptions = {}): void;normalizeRate
function normalizeRate(value: number | RationalRate): RationalRate;rescaleTime
/** Integer tick conversion with exact intermediate arithmetic; nearest ties round toward +infinity. */
export function rescaleTime(value: number, sourceRate: number | RationalRate, targetRate: number | RationalRate, rounding: TimeRounding = "nearest"): TimeConversion;defineShotAnimation
function defineShotAnimation(input: unknown): ShotAnimation;defineEditorialSequence
function defineEditorialSequence(input: unknown, animations: readonly ShotAnimation[]): EditorialSequence;resolveEditorialFrame
function resolveEditorialFrame(sequence: EditorialSequence, animations: readonly ShotAnimation[], frame: number): ResolvedEditorialFrame;createEditorialResolver
/** Validate and copy inputs once; returned frame mappings never expose the snapshot. */
export function createEditorialResolver(sequence: EditorialSequence, animations: readonly ShotAnimation[]): {
durationFrames: number;
frameRate: { numerator: number; denominator: number; };
resolve: (frame: number) => ResolvedEditorialFrame;
};reviseEditorialSequence
/** Apply ordered edits to an isolated sequence, then ripple positions and validate its final state. */
export function reviseEditorialSequence(sequence: EditorialSequence, animations: readonly ShotAnimation[], edits: readonly EditorialEdit[]): EditorialSequence;reviseShotAnimation
function reviseShotAnimation(animation: ShotAnimation, edits: readonly ShotAnimationEdit[]): ShotAnimation;shotCoordinates
function shotCoordinates(input: ShotAnimation, targetId: string, options: CoordinateOptions = {}): ShotCoordinateSpace;mergeShotAnimation
/** Merge snapshots with shared IDs; unresolved conflicts produce no animation. */
export function mergeShotAnimation(baseInput: ShotAnimation, localInput: ShotAnimation, incomingInput: ShotAnimation, options: ShotMergeOptions = {}): ShotMergeReport & {
animation: ShotAnimation | null;
};planShotMerge
/** Preview a worker revision against the current shot and prepare its native, version-bound edit plan. */
export function planShotMerge(project: StoryboardProject, base: ShotAnimation, incoming: ShotAnimation, options: ShotMergeOptions = {}): {
plan: EditPlan | null;
conflicts: ValueMergeConflict[];
incomingChanges: string[];
retainedLocalChanges: string[];
};planShotHandoffMerge
/** Check native handoff provenance and resource compatibility before preparing a worker merge. */
export function planShotHandoffMerge(assembly: StoryboardProject, baseline: StoryboardProject, worker: StoryboardProject, options: ShotMergeOptions & {
animationId: string;
}): {
plan: EditPlan | null;
dependencyConflicts: { kind: "component" | "origin" | "palette" | "swatch" | "asset" | "font"; id: string; baseline: ShotDependency | null; local: ShotDependency | null; incoming: ShotDependency; }[];
source: { projectId: string; version: number; };
worker: { projectId: string; version: number; };
conflicts: ValueMergeConflict[];
incomingChanges: string[];
retainedLocalChanges: string[];
};planPaletteMerge
/** Resolve one palette against the current project and prepare its existing native command. */
export function planPaletteMerge(project: StoryboardProject, base: Palette | null, incoming: Palette | null, options: PaletteMergeOptions = {}): {
plan: EditPlan | null;
conflictsResolved: boolean;
palette: Palette | null;
conflicts: ValueMergeConflict[];
incomingChanges: string[];
retainedLocalChanges: string[];
id: string;
};mergePalette
/** Merge one shared palette identity; null snapshots represent absence, not inferred matches. */
export function mergePalette(baseInput: Palette | null, localInput: Palette | null, incomingInput: Palette | null, options: PaletteMergeOptions = {}): {
conflictsResolved: boolean;
palette: Palette | null;
conflicts: ValueMergeConflict[];
incomingChanges: string[];
retainedLocalChanges: string[];
id: string;
};shotPointCoordinates
/** Map geometry through the ordered deformation stack; preserve every face candidate. */
export function shotPointCoordinates(input: ShotAnimation, targetId: string, point: {
x: number;
y: number;
}, options: ShotPointOptions): {
animationId: string;
targetId: string;
frame: number;
direction: "localToFrame" | "frameToLocal";
candidates: ShotPointCandidate[];
};retimeShotAnimation
/** Retime every shot-local frame collection on a detached snapshot; never stretch audio samples. */
export function retimeShotAnimation(input: ShotAnimation, options: ShotRetimeOptions): {
animation: ShotAnimation;
report: ShotRetimeReport;
};shotMeshData
/** Page detached mesh geometry or key metadata without returning the entire pose track. */
export function shotMeshData(input: ShotAnimation, layerId: string, query: ShotMeshQuery = { collection: "keyframes" }): { animationId: string; layerId: string; bindingKind: "skin" | "mesh" | "curve" | "envelope"; curveRest: CurveMeshPose | null; curveSegments: number | null; envelopeRest: EnvelopeMeshPose | null; envelopeGrid: { columns: number; rows: number; } | null; collection: "joints"; offset: number; limit: number; total: number; nextOffset: number | null; counts: { joints: number; weights: number; vertices: number; triangles: number; keyframes: number; }; items: { layerId: string; id: string; bind: Readonly<AffineMatrix>; index: number; }[]; } | { animationId: string; layerId: string; bindingKind: "skin" | "mesh" | "curve" | "envelope"; curveRest: CurveMeshPose | null; curveSegments: number | null; envelopeRest: EnvelopeMeshPose | null; envelopeGrid: { columns: number; rows: number; } | null; collection: "weights"; offset: number; limit: number; total: number; nextOffset: number | null; counts: { joints: number; weights: number; vertices: number; triangles: number; keyframes: number; }; items: { index: number; influences: readonly { jointId: string; weight: number; }[]; }[]; } | { animationId: string; layerId: string; bindingKind: "skin" | "mesh" | "curve" | "envelope"; curveRest: CurveMeshPose | null; curveSegments: number | null; envelopeRest: EnvelopeMeshPose | null; envelopeGrid: { columns: number; rows: number; } | null; collection: "triangles"; offset: number; limit: number; total: number; nextOffset: number | null; counts: { joints: number; weights: number; vertices: number; triangles: number; keyframes: number; }; items: { index: number; vertices: number[]; }[]; } | { animationId: string; layerId: string; bindingKind: "skin" | "mesh" | "curve" | "envelope"; curveRest: CurveMeshPose | null; curveSegments: number | null; envelopeRest: EnvelopeMeshPose | null; envelopeGrid: { columns: number; rows: number; } | null; collection: "keyframes"; offset: number; limit: number; total: number; nextOffset: number | null; counts: { joints: number; weights: number; vertices: number; triangles: number; keyframes: number; }; items: { index: number; frame: number; easing: Easing; }[]; } | { frame: number | null; animationId: string; layerId: string; bindingKind: "skin" | "mesh" | "curve" | "envelope"; curveRest: CurveMeshPose | null; curveSegments: number | null; envelopeRest: EnvelopeMeshPose | null; envelopeGrid: { columns: number; rows: number; } | null; collection: "vertices"; offset: number; limit: number; total: number; nextOffset: number | null; counts: { joints: number; weights: number; vertices: number; triangles: number; keyframes: number; }; items: { x: number; y: number; index: number; }[]; };bakeCurveMesh
/** Bake sampled curve ribbons into editable mesh keys; interpolation remains vertex-based. */
export function bakeCurveMesh(input: CurveMeshInput): MeshAnimation;createCurveMeshEvaluator
/** Evaluate controls first, then sample normals; no previous frame contributes to the pose. */
export function createCurveMeshEvaluator(input: CurveMeshInput): (frame: number) => IndexedMeshWarp;bakeEnvelopeMesh
/** Tessellate a four-boundary Coons patch into an editable vertex animation. */
export function bakeEnvelopeMesh(input: EnvelopeMeshInput): MeshAnimation;createSkinMeshEvaluator
/** Bind explicit weights once; joint poses and output are in the mesh's local coordinate space. */
export function createSkinMeshEvaluator(input: SkinMeshInput): (poses: readonly SkinJointPose[]) => IndexedMeshWarp;shotControllerData
/** Inspect detached controller records and optional final, fully blended layer states. */
export function shotControllerData(input: ShotAnimation, controllerId: string, query: ShotControllerQuery = { collection: "keyframes" }): { collection: "targets" | "keyframes"; total: number; nextOffset: number | null; items: { frame: number; weight: number; easing: Easing; index: number; }[]; animationId: string; controllerId: string; name: string; mode: "replace" | "additive"; stackIndex: number; staticWeight: number; activeRange: { startFrame: number; endFrame: number; } | null; frame: number | null; evaluatedWeight: number | null; counts: { targets: number; keyframes: number; }; offset: number; limit: number; } | { collection: "targets" | "keyframes"; total: number; nextOffset: number | null; items: { evaluatedState: EvaluatedLayerState | null; layerId: string; values: Partial<Record<LayerChannel, number>>; index: number; }[]; animationId: string; controllerId: string; name: string; mode: "replace" | "additive"; stackIndex: number; staticWeight: number; activeRange: { startFrame: number; endFrame: number; } | null; frame: number | null; evaluatedWeight: number | null; counts: { targets: number; keyframes: number; }; offset: number; limit: number; };compileControllerTransfer
/** Prepare new controller definitions; preserve destination base animation and existing controllers. */
export function compileControllerTransfer(sourceInput: ShotAnimation, targetInput: ShotAnimation, input: ControllerTransferOptions): ShotAnimationEdit[];captureShotController
/** Capture explicit local channels into an inactive controller without changing the source. */
export function captureShotController(input: ShotAnimation, options: ControllerCaptureOptions): ShotController;readControllerPerformance
/** Validate the versioned JSON envelope, logical payload checksum and controller invariants. */
export function readControllerPerformance(input: unknown): ControllerPerformance;createControllerPerformance
/** Capture selected controllers in their source stack order; no artwork or base keys are included. */
export function createControllerPerformance(input: ShotAnimation, options: {
id: string;
name: string;
controllerIds: readonly string[];
}): ControllerPerformance;compileControllerPerformance
function compileControllerPerformance(input: unknown, target: ShotAnimation, options: ControllerTransferOptions): ShotAnimationEdit[];importOTIO
/** Conform one cut-only video track to existing animations without reading external media. */
export function importOTIO(json: string, animations: readonly ShotAnimation[], options: OTIOImportOptions): {
sequence: EditorialSequence;
losses: OTIOLoss[];
};exportOTIO
/** Export a single video cut list. Media bindings describe already-rendered media, not artwork. */
export function exportOTIO(input: EditorialSequence, animations: readonly ShotAnimation[], options: OTIOOptions): {
json: string;
losses: OTIOLoss[];
};defineStudioAudio
function defineStudioAudio(input: unknown): StudioAudioTrack[];compileStudioAudio
/** Resolve audible placements to a chosen sample clock without resampling or reading media. */
export function compileStudioAudio(tracks: readonly StudioAudioTrack[], sampleRate: number, options: {
rounding?: AudioSampleRounding;
} = {}): CompiledAudioClip[];reviseStudioAudio
/** Ordered metadata edits; sample fades/ranges are validated on the complete final state. */
export function reviseStudioAudio(tracks: readonly StudioAudioTrack[], edits: readonly StudioAudioEdit[]): StudioAudioTrack[];conformShotAudio
function conformShotAudio(animation: ShotAnimation, sampleRate = 48000, options: {
tracks?: readonly AudioTrackRef[];
rounding?: AudioSampleRounding;
} = {}): AudioConform;conformEditorialAudio
/** Conform at normal playback speed; source audio is cropped, never frame-duplicated or time-stretched. */
export function conformEditorialAudio(sequence: EditorialSequence, animations: readonly ShotAnimation[], options: {
sampleRate: number;
transitions: "sum" | "linear";
tracks?: readonly AudioTrackRef[];
rounding?: AudioSampleRounding;
}): AudioConform;mixShotAudio
async function mixShotAudio(animation: ShotAnimation, decode: StudioAudioDecoder, input: AudioMixOptions = {}): Promise<AudioMixResult>;mixEditorialAudio
async function mixEditorialAudio(sequence: EditorialSequence, animations: readonly ShotAnimation[], decode: StudioAudioDecoder, input: AudioMixOptions & {
transitions: "sum" | "linear";
}): Promise<AudioMixResult>;createFFmpegAudioDecoder
/** Asset byte identity is pinned on first use for the lifetime of the returned decoder. */
export function createFFmpegAudioDecoder(readAsset: (id: string) => Promise<Uint8Array> | Uint8Array, options: FFmpegAudioDecoderOptions = {}): StudioAudioDecoder;Codeboard by Nonom Friedman
Explore Nonom Library