edets APIv1.0

edets API

Upload a still, describe the motion, and receive a clip. Billing is 1:1: a 5-second clip costs 5 seconds of prepaid balance. An 8-second clip costs 8 seconds.

Base URL

Base URLhttps://edets.com/api/v1

Auth

Bearer edets_live_… minted at /api/keys. Every /api/v1 call sends that header.

Billing

1:1 seconds — a 5-second clip burns 5 seconds of prepaid balance.

Endpoints

MethodPathWhat it does
POST/api/v1/generationsCreate a single clip
GET/api/v1/generations/{id}Poll queued → running → succeeded/failed
GET/api/v1/generations/{id}/clipDownload MP4
POST/api/v1/campaignsMulti-shot IG/TT/FB
GET/api/v1/campaigns/{id}Poll campaign
POST/api/v1/campaigns/{id}/composeStitch master + packs
GET/api/v1/campaigns/{id}/masterDownload master
GET/api/v1/campaigns/{id}/pack/{format}Platform pack
GET/api/v1/balancePrepaid seconds remaining + dollar equivalent
Duration 5–15sQuality max · motionSize 480 or 720Aspect 9:16, 1:1, 4:5PNG / JPG / WEBP · 20 MB

Your first request

curl -sS -X POST "https://edets.com/api/v1/generations" \
  -H "Authorization: Bearer edets_live_…" \
  -F "still=@portrait.jpg" \
  -F "prompt=She peels the cream sheet down off her face" \
  -F "seconds=5" \
  -F "quality=max" \
  -F "aspect=9:16" \
  -F "size=720"

The handler returns this JSON. id is a UUID. poll_url is relative to the host.

{
  "id": "7c1e4d2a-9b0f-4c3e-8a11-2f6d9e4b81c0",
  "status": "queued",
  "poll_url": "/api/v1/generations/7c1e4d2a-9b0f-4c3e-8a11-2f6d9e4b81c0"
}

Poll GET /api/v1/generations/{id} until status is succeeded, then download the clip.

Authentication

Every /api/v1 call needs a Bearer key. Mint it at /api/keys. The secret is shown once. Prefix is edets_live_.

Authorization: Bearer edets_live_…

The key burns that account’s prepaid seconds 1:1. Buy packs at /api/seconds: Starter 45s $8, Pro 120s $20, Studio 300s $45.

Concepts

NameValuesNotes
Seconds5–15 integerPrepaid balance. A 5-second clip burns 5 seconds; an 8-second clip burns 8.
Qualitymax · motionmax is edets Max. motion is edets Motion.
Aspect9:16 · 1:1 · 4:5Default 9:16. The still is padded to this frame before submit, not cropped.
Size480 · 720Public API sizes are 480 and 720. On edets Max, size=720 is 768P (short edge 768, not 720). On edets Motion, size=720 is 720P. Default 720. Short-edge resolution request.

Generate clip

POST/api/v1/generations

Creates a job and returns id, status: "queued", and poll_url.

FieldRequiredNotes
stillyes*Multipart file. PNG, JPG, or WEBP. Max 20 MB.
still_urlyes*HTTPS URL if you are not uploading a file.
promptyesFull shot sentence. A mood word is not enough.
qualityyesmax (edets Max) or motion (edets Motion).
secondsyesInteger 5–15. Burns 1:1.
aspectno9:16, 1:1, or 4:5. Defaults to 9:16. The first-frame still is letterboxed to this aspect before submit.
sizenoPublic API sizes are 480 and 720. On edets Max, size=720 is 768P (short edge 768, not 720). On edets Motion, size=720 is 720P. Default 720.

* Send a file or a URL. Not both required.

Legacy aliases image and file (multipart) and JSON url/still as a URL string are accepted but not recommended.

curl -sS -X POST "https://edets.com/api/v1/generations" \
  -H "Authorization: Bearer edets_live_…" \
  -F "still=@portrait.jpg" \
  -F "prompt=She peels the cream sheet down off her face" \
  -F "seconds=5" \
  -F "quality=max" \
  -F "aspect=9:16" \
  -F "size=720"
curl -sS -X POST "https://edets.com/api/v1/generations" \
  -H "Authorization: Bearer edets_live_…" \
  -H "Content-Type: application/json" \
  -d '{"still_url":"https://example.com/img.jpg","prompt":"She peels the cream sheet down off her face","seconds":5,"quality":"max"}'

Response

{
  "id": "7c1e4d2a-9b0f-4c3e-8a11-2f6d9e4b81c0",
  "status": "queued",
  "poll_url": "/api/v1/generations/7c1e4d2a-9b0f-4c3e-8a11-2f6d9e4b81c0"
}

MCP equivalent: generate_video. edets MCP server.

Check status

GET/api/v1/generations/{id}

Poll until queuedrunningsucceeded or failed. clip_url is a relative path and stays null until succeeded. JSON includes width, height, and aspect. Dimensions stay null until the mp4 is measured. A clip whose frame is not the requested aspect is failed and seconds go back to the pool.

Webhooks are not supported. Poll GET /api/v1/generations/{id} every 2–5 seconds. Typical generation time is 30–60 seconds.

curl -sS "https://edets.com/api/v1/generations/7c1e4d2a-9b0f-4c3e-8a11-2f6d9e4b81c0" \
  -H "Authorization: Bearer edets_live_…"

Queued

{
  "id": "7c1e4d2a-9b0f-4c3e-8a11-2f6d9e4b81c0",
  "status": "queued",
  "clip_url": null,
  "seconds_burned": 5,
  "error": null,
  "width": null,
  "height": null,
  "aspect": "9: 16"
}

Running

{
  "id": "7c1e4d2a-9b0f-4c3e-8a11-2f6d9e4b81c0",
  "status": "running",
  "clip_url": null,
  "seconds_burned": 5,
  "error": null,
  "width": null,
  "height": null,
  "aspect": "9: 16"
}

Succeeded

{
  "id": "7c1e4d2a-9b0f-4c3e-8a11-2f6d9e4b81c0",
  "status": "succeeded",
  "clip_url": "/api/v1/generations/7c1e4d2a-9b0f-4c3e-8a11-2f6d9e4b81c0/clip",
  "seconds_burned": 5,
  "error": null,
  "width": 768,
  "height": 1366,
  "aspect": "9: 16"
}

Failed

{
  "id": "7c1e4d2a-9b0f-4c3e-8a11-2f6d9e4b81c0",
  "status": "failed",
  "clip_url": null,
  "seconds_burned": 5,
  "error": "Generation failed. Upstream rejected the job.",
  "width": null,
  "height": null,
  "aspect": "9: 16"
}

Failed jobs return reserved seconds to the pool. seconds_burned is the attempt duration, not a leftover charge.

MCP equivalent: check_status. edets MCP server.

Download clip

GET/api/v1/generations/{id}/clip

Returns the mp4 bytes. Not a CDN redirect. Send the same Bearer header. Response headers include Cache-Control: private, max-age=3600. If the clip is not ready, JSON 404: { "error": "Clip is not ready yet." }

curl -sS "https://edets.com/api/v1/generations/7c1e4d2a-9b0f-4c3e-8a11-2f6d9e4b81c0/clip" \
  -H "Authorization: Bearer edets_live_…" \
  -o clip.mp4

MCP equivalent: after check_status succeeds, download clip_url. edets MCP server.

Campaigns

POST/api/v1/campaigns

Several shots on the same generate pipe. Campaigns do not auto-compose. QC the clips, then POST compose. Stitch is pad-not-crop. Each shot burns seconds 1:1.

  1. CreatePOST /api/v1/campaigns
  2. Poll clipsGET /api/v1/campaigns/{id} until succeeded-with-clips
  3. Rerun one shot if neededPOST /api/v1/campaigns/{id}/shots/{index}/rerun or /replace
  4. ComposePOST /api/v1/campaigns/{id}/compose
CreatePoll clipsRerun one shotCompose
FieldRequiredNotes
idyes1–64 letters, numbers, dots, underscores, or hyphens. Not a UUID.
laneyesig, tt, or fb.
intentyesproduct, character, or make_something.
aspectno9:16, 1:1, or 4:5. IG/TT default 9:16, FB 4:5.
sizenoPublic API sizes are 480 and 720. On edets Max, size=720 is 768P (short edge 768, not 720). On edets Motion, size=720 is 720P. Default 720.
stillsconditionalRequired when intent is character (talent_url or a talent file). Optional for product and make_something.
shotsyes{ t, seconds, quality, prompt, still? | still_url? }. Seconds 5–15 each.
tyesTimeline position label (e.g. 0-5, 5-10). It does not affect rendering order — shots render in array order.
IntentUse for
productProduct shots, objects, items
characterPeople, talent, portraits
make_somethingAbstract scenes, environments, concepts
curl -sS -X POST "https://edets.com/api/v1/campaigns" \
  -H "Authorization: Bearer edets_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "id": "IG-01", "lane": "ig", "intent": "character", "aspect": "9:16", "size": "720", "stills": { "talent_url": "https://example.com/talent.jpg" }, "shots": [ { "t": "0-5", "seconds": 5, "quality": "max", "prompt": "She peels the cream sheet down off her face", "still_url": "https://example.com/hold-a.jpg" }, { "t": "5-10", "seconds": 5, "quality": "max", "prompt": "She lifts the sheet toward camera", "still_url": "https://example.com/hold-b.jpg" } ] }'

Create response

{
  "id": "IG-01",
  "status": "queued"
}

Rerun or replace one shot

Index is 0-based. Does not compose. After the new clip succeeds, status is succeeded-with-clips.

curl -sS -X POST "https://edets.com/api/v1/campaigns/IG-01/shots/0/rerun" \
  -H "Authorization: Bearer edets_live_…" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"She peels the cream sheet down off her face"}'

Poll campaign

GET/api/v1/campaigns/{id}

Poll until succeeded-with-clips. Then QC, rerun a shot if needed, and compose. Typical wait is 30–60 seconds per shot. Poll every 2–5 seconds.

curl -sS "https://edets.com/api/v1/campaigns/IG-01" \
  -H "Authorization: Bearer edets_live_…"
{
  "id": "IG-01",
  "status": "succeeded-with-clips",
  "shot_generation_ids": [
    "7c1e4d2a-9b0f-4c3e-8a11-2f6d9e4b81c0",
    "9a2b8c71-4d0e-41f6-b3aa-0c5e7d18f902"
  ],
  "shot_clip_urls": [
    "/api/v1/generations/7c1e4d2a-9b0f-4c3e-8a11-2f6d9e4b81c0/clip",
    "/api/v1/generations/9a2b8c71-4d0e-41f6-b3aa-0c5e7d18f902/clip"
  ],
  "clip_urls": [
    "/api/v1/generations/7c1e4d2a-9b0f-4c3e-8a11-2f6d9e4b81c0/clip",
    "/api/v1/generations/9a2b8c71-4d0e-41f6-b3aa-0c5e7d18f902/clip"
  ],
  "master_url": null,
  "pack": {
    "ig_9x16": null,
    "tt_9x16": null,
    "fb_4x5": null
  },
  "seconds_burned": 10,
  "error": null
}

clip_urls is an alias for shot_clip_urls — same relative paths. Prefer shot_clip_urls.

Compose

POST/api/v1/campaigns/{id}/compose

Stitch the master and platform packs. Campaigns do not auto-compose — call this after clips succeed.

curl -sS -X POST "https://edets.com/api/v1/campaigns/IG-01/compose" \
  -H "Authorization: Bearer edets_live_…"
{
  "id": "IG-01",
  "status": "succeeded",
  "shot_generation_ids": [
    "7c1e4d2a-9b0f-4c3e-8a11-2f6d9e4b81c0",
    "9a2b8c71-4d0e-41f6-b3aa-0c5e7d18f902"
  ],
  "shot_clip_urls": [
    "/api/v1/generations/7c1e4d2a-9b0f-4c3e-8a11-2f6d9e4b81c0/clip",
    "/api/v1/generations/9a2b8c71-4d0e-41f6-b3aa-0c5e7d18f902/clip"
  ],
  "clip_urls": [
    "/api/v1/generations/7c1e4d2a-9b0f-4c3e-8a11-2f6d9e4b81c0/clip",
    "/api/v1/generations/9a2b8c71-4d0e-41f6-b3aa-0c5e7d18f902/clip"
  ],
  "master_url": "/api/v1/campaigns/IG-01/master",
  "pack": {
    "ig_9x16": "/api/v1/campaigns/IG-01/pack/ig_9x16",
    "tt_9x16": "/api/v1/campaigns/IG-01/pack/tt_9x16",
    "fb_4x5": "/api/v1/campaigns/IG-01/pack/fb_4x5"
  },
  "seconds_burned": 10,
  "error": null
}

Download master

GET/api/v1/campaigns/{id}/master

Returns the stitched master MP4. Same Bearer. Write the file with curl -o.

curl -sS "https://edets.com/api/v1/campaigns/IG-01/master" \
  -H "Authorization: Bearer edets_live_…" \
  -o master.mp4

Platform pack

GET/api/v1/campaigns/{id}/pack/{format}

Formats: ig_9x16, tt_9x16, fb_4x5. Same Bearer.

curl -sS "https://edets.com/api/v1/campaigns/IG-01/pack/ig_9x16" \
  -H "Authorization: Bearer edets_live_…" \
  -o ig_9x16.mp4

Errors

Bodies are { "error": "…" } strings. These are the live handler messages.

StatuserrorFix
401Unauthorized.Send Authorization: Bearer edets_live_…. Mint a key at /api/keys.
400quality must be max or motion.Send max or motion.
400size must be 480 or 720.Public API accepts 480 or 720. size=1080 returns this 400.
400aspect must be 9:16, 1:1, or 4:5.Send one of those three values.
400Choose a length between 5 and 15 seconds.Integer 5–15.
400Only PNG, JPG, or WEBP stills are supported.Convert the still.
400The still exceeds the 20 MB limit.Shrink the still.
400Send a still file or a still URL.Attach still or still_url.
400Not enough seconds. Buy credits at /api/seconds.Buy seconds at /api/seconds.
429Rate limit exceeded.10 generations/minute, 100/hour per API key. GET poll and download do not count.
400Say what happens in the shot. A mood word is not enough.Send a full shot sentence.
404Not found.Wrong id, or the key does not own that job.
404Clip is not ready yet.Poll until succeeded, then GET …/clip.
200Clip aspect did not match 9:16.Poll failed. The mp4 DAR did not match. Seconds returned. The still is padded, not cropped.

Limits

LimitValue
Clip length5–15 seconds
Size480 or 720. Max 720 = 768P.
Aspect9:16, 1:1, 4:5
StillPNG, JPG, or WEBP. 20 MB max.
Qualitymax (edets Max) or motion (edets Motion)
Seconds packsStarter 45s $8 · Pro 120s $20 · Studio 300s $45
Writes10 generations/minute, 100/hour per API key
BalanceGET /api/v1/balance — seconds remaining and dollar equivalent.