Asset lifecycle

Every Motioness asset goes through the same finite-state machine, regardless of mode or model.

text
queued ──→ vision_started ──→ vision_completed stage_started ──→ stage_completed │ (loop mode: stage1 → stage2 → merge) r2_write_completed ready ──┬──→ webhook_dispatched ──→ webhook_delivered └──→ (cached at edge forever) Any stage can emit *_failed → asset.status = failed → asset.failed event

States

StatusMeaning
pendingGeneration in flight. Worker may still be in vision / stage1 / stage2 / merge / R2-write.
readymp4 exists in R2, accessible via the proxy URL. Terminal success.
failedPipeline gave up. Inspect error_code in the asset.failed event payload.

Event types

The full event log is emitted to D1 (asset_events), surfaced via SSE, and dispatched to webhooks. The closed enum:

EventWhen
queuedWorker accepted the request, asset row inserted
vision_startedClaude vision call dispatched
vision_completedVision returned a motion prompt
vision_failedVision rejected the source (NSFW, oversize, broken URL)
stage_startedReplicate prediction submitted (stagestage1, stage2, merge, single)
stage_completedPrediction returned an mp4
stage_failedPrediction errored or timed out
retry_scheduledTransient error, retry queued (observational in v1.1)
r2_write_completedmp4 stored in R2
webhook_dispatchedOutbound delivery enqueued
webhook_deliveredCustomer endpoint responded 2xx
webhook_failedCustomer endpoint errored or timed out
webhook_dead_letter5 failed attempts, won't retry automatically
readyTerminal success — proxy URL is now hot
failedTerminal failure — error_code field tells you why
cancelledManual cancel via dashboard

Each event carries ts, attempt, stage (when applicable), latency_ms (on *_completed), error_code + error_msg (on *_failed), request_id.

Watching events

Three ways to observe asset state:

Browser-friendly, no auth required (uses project key + asset id):

bash
curl -N "https://motioness.com/v1/pk_abc/events/asset_xyz"

Each event arrives as:

text
id: 1714801234567event: stage_completeddata: {"stage":"stage1","latency_ms":4820,"asset_id":"a25b…","ts":1714801234567}

Reconnect resumes from Last-Event-ID.

Stale-pending recovery

If a Replicate webhook never lands (network blip, signature mismatch, etc.), two safety nets kick in:

  1. Opportunistic reconcileGET /api/assets/:id triggers a Replicate state query if the asset has been pending for >25 seconds.
  2. Cron reconciler — runs every minute, finalizes stuck assets after 5 minutes, marks them failed with pipeline_stalled after 10 minutes.

You don't have to do anything; just don't assume pending is permanent. Poll the asset endpoint or listen on SSE.

Idempotency

Asset IDs are content-addressed, so calling the proxy twice for the same (src, motion, mode, dur, prompt, seed, idem, aspect) returns the same asset_id — no duplicate generation.

To force a distinct asset from the same source, vary idem:

text
?idem=variant-a → asset_id A?idem=variant-b → asset_id B

Cancellation

There's no public cancel endpoint. The dashboard exposes a Cancel button on pending assets that emits cancelled and stops billing — generations that have already submitted to Replicate will run to completion but won't count as ready.

Ask a question... ⌘I