A motion artifact has body_format application/vnd.allternit.motion+json. The body is data only; nothing in it is run as code. Create and update it through the Artifacts API or the artifact_create and artifact_update tools.

Body

Props by type

Text is limited to 400 characters per field. The parser fills defaults, clamps numbers to the limits above, and keeps going on bad values, so a hand-written or model-written body still plays. It refuses a body that isn’t JSON, has no scenes, or has a version newer than it knows. The model-side tools refuse a body without a non-empty scenes array.

Renderer

renderFrame(ctx, composition, t, assets?) draws the frame at time t seconds. It is a pure function: it reads no clock and no random numbers and keeps no state between calls. The preview, the scrubber, the reduced-motion poster frame and the video export all call it, so the same t gives the same picture.
  • Playback only moves t forward from a frame timer. What is drawn never depends on that timer.
  • Motion per object: text rises out of a mask word by word, numbers count up on an ease-out curve, bars grow one after another, lines draw along their length, logos scale in with a small overshoot, list rows slide in in turn.
  • A transition draws the previous scene’s last frame, then the incoming scene over it (faded, clipped by a moving edge, or slid in).
  • Logo images are decoded before drawing. A logo that fails to load shows its name as a wordmark.

Export

Frames are drawn by seeking: frame i is drawn at t = i / fps. Exported videos have no audio track.

Cloud render

There is one renderer. The host runs the app’s own renderFrame (src/lib/artifacts/motion/render.ts) bundled for Node: it draws each frame into an @napi-rs/canvas canvas and pipes raw frames to ffmpeg, which writes H.264 (yuv420p, faststart) at the composition’s frame rate and size. The bundle and its runtime package live together in cmd/allternit-cloud-api/render/motion/. The bundle is checked in and regenerated with node scripts/build-motion-render.mjs --ai <allternit-ai> (see that folder’s README); dist/SOURCE.json records the allternit-ai commit it came from. All routes need the same sign-in as the rest of /api/v2/artifacts. A caller needs at least view access to the artifact; a job belongs to the person who started it, and other callers get 404.

Start a job

POST /api/v2/artifacts/{id}/render
version is optional. Without it the job renders the version the caller sees (the latest for editors and the owner, the shared version for everyone else). Only editors and the owner may ask for another version. Answers 202 with the job:

Read a job

GET /api/v2/artifacts/{id}/render/{job} Returns the same object. status is queued, running, done, failed, canceled or expired. progress runs from 0 to 1; ahead counts the jobs queued or running before this one. When status is failed, error is { "code", "message" }. When it is done, file_url is the path of the file and expires_at is 24 hours after it finished. Poll about every 1.5 seconds.

Download

GET /api/v2/artifacts/{id}/render/{job}/file Streams video/mp4 as an attachment. 409 render_not_ready while the job is unfinished; 410 render_expired after the 24 hours.

Cancel

DELETE /api/v2/artifacts/{id}/render/{job} Cancels a queued or running job (the renderer process is stopped), or deletes a finished file early. Answers 204.

Limits and errors

A job can also end failed with render_timeout, render_failed, render_too_large (the output file) or render_interrupted (the service restarted while it ran).

Storage and host setup

Finished files go to the private R2 bucket when ALLTERNIT_R2_* is set (key motion-render/{user}/{job}.mp4), otherwise to the host’s temp folder. A sweep every ten minutes deletes files 24 hours after they finished. Jobs are rows in motion_render_jobs (migration 087_motion_render_jobs.sql). deploy-contabo.sh ships the render folder with the binary and runs npm ci --omit=dev into /opt/allternit-cloud-api/render/motion. Settings: ALLTERNIT_MOTION_RENDER_DIR, ALLTERNIT_NODE_BIN (default /usr/bin/node), ALLTERNIT_FFMPEG_BIN (default /usr/bin/ffmpeg) and ALLTERNIT_MOTION_RENDER_TMP. Logos given as https addresses are not fetched by the server; those scenes draw the wordmark. Embedded data:image/... logos are drawn. Fonts are the bundled Inter, Source Serif 4 and JetBrains Mono.