How it works

Motioness is an edge-hosted image-to-motion proxy. One URL replaces an <img src> and the response is either a poster jpg (immediate) or a looping mp4 (generated once, cached forever).

Request flow

text
Browser ──→ /v1/{projectKey}/{src}.{fmt} Cloudflare Worker ┌──────────┴──────────┐ │ Already cached? │── yes ──→ Stream from edge cache (~5ms) │ │ └──────────┬──────────┘ │ no ensureAsset(srcUrl, opts) ┌──────────┴──────────┐ │ Asset row in D1? │── yes (pending) ──→ 202 + Retry-After │ │── yes (ready) ──→ stream from R2 └──────────┬──────────┘ │ no Insert asset row, enqueue: 1. Claude vision → motion prompt 2. Replicate i2v → mp4 (single OR two-stage loop) 3. R2 write → durable bytes 4. Webhook fire → asset.completed Return 202 to browser

The three pipelines

The mode parameter (or project default) picks one of three pipelines:

ModePipelineUse case
loopTwo-stage stitch — generate forward + return-to-start, merge in fixed timelineBackgrounds, hero accents, ambient motion
animate-inSingle i2v call, played once on entryReveal animations, build-ups, story beats
animate-on-hoverSame as animate-in but client-paused until hoverInteractive cards, gallery items

loop is two Replicate calls; animate-in and animate-on-hover are one. See motion modes.

Tier-based model selection

The image-to-video model is picked from the project owner's plan, not from the request:

TierModelQualityLoop-capable
Freewan-fastlowno — uses crossfade
Pro ($19/mo)seedancestandardyes (native)
Team ($79/mo)p-videohighyes (native)

Customers can't request a higher-tier model than their plan. Quality scales with the plan.

Edge cache + content-addressing

Asset IDs are content-addressed: same (src, motion, mode, dur, prompt, seed, idem, aspect) produces the same asset_id, which produces the same R2 path, which produces the same cached URL.

text
asset_id = sha256(src_url || motion || mode || dur_seconds || prompt || seed || idem || aspect)[0:16]

This means:

  • Two browsers hitting the same proxy URL never re-generate.
  • Adding ?idem=variant-a and ?idem=variant-b produces two distinct assets from the same source.
  • Removing query params doesn't break cache — non-content params (wait_ms, sig, exp) are stripped before hashing.

Storage

TierBackendWhat's there
1Edge cachemp4 + jpg responses, ~5ms hot reads
2KV (CACHE)asset:{id} index pointing at R2 path — skips D1 on cache miss
3R2 (R2_ASSETS)Durable mp4s (assets/{id}.mp4), posters (posters/{srcHash}.jpg)
4D1 (DB)Rows for assets, generations, asset_events, webhook_deliveries, projects, users

Reliability surface

Three independent recovery mechanisms keep generation honest:

  1. Per-stage retry envelope — transient errors (Replicate 5xx/429, R2 5xx, timeouts) retry up to 3 attempts per stage with [0s, 1s, 4s] backoff.
  2. Replicate webhook + cron reconciler — if the webhook never lands, a cron job runs every minute and queries Replicate directly to finalize stuck assets after 5 minutes. After 10 minutes it marks them failed with pipeline_stalled.
  3. Standard Webhooks delivery — outbound webhooks retry on 30s, 2m, 10m, 30m, 2h and dead-letter after 5 attempts.

You can re-enqueue a dead delivery from the dashboard or via POST /api/projects/:id/webhooks/deliveries/:deliveryId/redeliver.

Brand context

When configured, a project's brand context (colors, fonts, tagline, content inventory) is injected into the vision prompt. The vision model gets:

text
Brand: Acme — fast SaaS observabilityTone: technical, confidentColors: #0d9373 (primary), #f59e0b (accent), dark schemeFonts: Inter / IBM Plex Mono

This biases motion choices toward what fits the brand — subtle for editorial sites, kinetic for consumer brands, etc. See brand context for how to populate it (manual fields, brand-detect, or deep-crawl).

What you don't have to think about

  • Prompt engineering — the vision model writes the prompt for you.
  • Model selection — tier picks it; quality= is a hint when you want explicit control.
  • Aspect handlingaspect=preserve (default) matches the source.
  • CDN / cache invalidation — content-addressed URLs never need invalidation.
  • Format negotiation — request .mp4 for video, .jpg for poster fallback.

What you should know

  • First request to an mp4 returns 202 while generation runs. Use ?wait_ms=8000 to long-poll.
  • Free-tier mp4s use crossfade loop mode (visible seam); Pro+ mp4s loop natively.
  • The proxy is read-only for the source URL — Motioness never modifies your source images.
  • Generation costs apply to the project owner, not the embedding site.
Ask a question... ⌘I