Errors and retries
Every failure mode the proxy can produce and what to do about each.
Errors and retries
Motioness fails loud — every error has a stable error_code you can branch on, and a request_id for support tickets.
HTTP status codes
| Status | When | Body |
|---|---|---|
202 | Generation in flight | { status: 'pending', asset_id, eta_seconds } |
400 | Malformed proxy URL or body validation failed | { error: '...' } |
403 | Unknown project key, origin not allowlisted, or invalid signature | { error: '...' } |
404 | Asset not found / not owned | { error: 'not found' } |
413 | Source > 10 MB | { error: 'file exceeds 10 MB limit' } |
415 | Source not png/jpg/webp | { error: 'unsupported type: ...' } |
429 | Monthly quota exhausted | { error: 'monthly limit reached', upgrade_url } |
502 | Last generation failed terminally | { error: 'last generation failed', asset_id, hint: 'POST /api/v1/regenerate/...' } |
Error codes (in asset.failed events)
error_code is the stable identifier; error_msg is human-readable.
| Code | Meaning | Action |
|---|---|---|
vision_blocked | Source rejected by vision model (NSFW, copyright) | Use a different source |
vision_timeout | Vision call exceeded 30s | Retry; if persistent, simplify the source |
source_unfetchable | 4xx/5xx fetching the source URL | Check the URL is publicly reachable |
source_too_large | Source > 10 MB | Resize or upload via /api/v1/upload |
source_unsupported | Source is svg / mp4 / unknown content-type | Re-encode to png/jpg/webp |
i2v_failed | Image-to-video model errored | Retry with regenerate; if persistent, lower motion |
i2v_timeout | i2v exceeded 5 minutes | Retry; lower dur_seconds |
merge_failed | Loop stitching failed | Try mode=animate-in instead |
r2_write_failed | R2 write errored | Auto-retried; if persistent, contact support |
quota_exceeded | Monthly limit hit mid-flight | Upgrade or wait for reset |
pipeline_stalled | Cron reconciler gave up after 10 min | Regenerate; if persistent, contact support |
Built-in retry mechanisms
You don't need to handle transient errors yourself. Three independent mechanisms recover automatically:
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. Permanent errors (Replicate 4xx, content-type issues, quota) fail loud.
You'll see retry_scheduled events on SSE and asset.retry_scheduled webhooks when this fires.
2. Cron reconciler
A cron job runs every minute:
- Reconcile assets stuck
pendingfor 5–10 minutes by querying Replicate directly. - Mark assets stuck
pendingfor >10 minutes asfailedwitherror_code: pipeline_stalled.
If a Replicate webhook never lands (network blip, signature mismatch on Motioness's side, etc.), the cron is the safety net.
3. Webhook delivery retries
Outbound webhooks retry on 30s, 2m, 10m, 30m, 2h. After 5 failed attempts the delivery is dead; redeliver from the dashboard or via POST /api/projects/:id/webhooks/deliveries/:deliveryId/redeliver.
What to do on each status
Normal first-hit response. The Retry-After header tells you how many seconds the server estimates for completion. The X-Motioness-Eta-Ms header gives a more precise estimate.
Either:
- Poll the same URL again after
Retry-After. - Subscribe via SSE:
GET /v1/{key}/events/{asset_id}. - Use
?wait_ms=8000to long-poll up to 8 seconds.
Add your origin to allowed_origins (Project → Settings → Proxy), or switch to signed mode.
Subdomain matching is automatic: https://acme.com accepts *.acme.com.
Signing-mode-only. Common causes:
- You're signing with the wrong tuple. Must be
${projectKey}:${srcUrl}:${exp}exactly. expalready passed. Stick to TTLs >= 60s.- You used base64 instead of base64url for the source. Check for
+//characters. - The signing secret was rotated and the URL is signed with the old one.
Free tier: 5 generations/month. Pro: 100. Team: 500.
The error response includes upgrade_url pointing to the pricing page. After upgrade, generations resume immediately.
If you're on Pro+ and seeing this with error: "overage disabled", enable overage in Project → Settings → Billing.
The asset's last generation hit a permanent error (vision-blocked, i2v failure, etc.). The hint in the response tells you to call regenerate. Inspect asset.failed event to see error_code, then either:
- Adjust the source (most
vision_*andsource_*errors require this). - Retry with different opts:
POST /api/assets/:id/regeneratewith new motion / prompt / seed.
Debugging with X-Request-Id
Every response carries X-Request-Id. Echo it back to support tickets — we can pull the full event log with one query.
To set your own, send X-Request-Id: req_my_correlation_id on the request. The server echoes it if it matches ^[\w-]{4,64}$, otherwise mints req_<8-hex>.