Endpoints

Get a job

Returns a job's status and, once it succeeds, its clips. Call it to poll, or hold the request open until the job is done.

GET/v1/jobs/{id}

Request

Pass the job id you got from POST /v1/clips. The job's poll_url is this same URL.

idstringIn pathRequired
The job id, like job_q8w2e4r6t1y3u5i7o9p0.
waitintegerIn query
Seconds to hold the request open while the job runs, 0 to 300. It returns as soon as the job finishes.Default 0

Unknown ids, and jobs created with another API key, return 404 NOT_FOUND.

The job object

Every endpoint and webhook returns the job in this shape, wrapped as { job }.

idstring
Job id: job_ plus 20 lowercase letters and digits.
opstring
Always "clips" for jobs from POST /v1/clips.
statusstring
Where the job is. See statuses.One of"queued""running""succeeded""failed"
stagestring | null
The step a running job is on. null while queued, while the job's machine starts and once finished.One of"fetching""transcribing""ranking""rendering"
created_atstring
When the job was created, in ISO 8601 UTC.
finished_atstring | null
When it succeeded or failed. null until then.
resultobject | null
Set only when status is succeeded. There are no partial results while the job runs.
result.source.duration_snumber
Length of the source video in seconds.
result.source.languagestring | null
Spoken language code, as you set it or as we detected it.
result.source.speakersnull
Always null for now.
result.clipsarray
The delivered clips, best first. See the clip object.
usage.source_minutesnumber
Minutes of source video, which is what you pay for. 0 unless the job succeeded: failed jobs cost nothing.
usage.clipsinteger
How many clips were delivered.
usage.output_secondsnumber
Total length of all clips, in seconds.
errorobject | null
Set only when status is failed: a code and a message.
poll_urlstring
This job's URL. GET it to check on the job.

Statuses

A job starts queued, runs through its stages and ends succeeded or failed. Both ends are final.

GETapi.clipsdk.com/v1/jobs/{id}

HTTP 202 Accepted

"status": "queued",

"stage": null,

"result": null

job_q8w2e4r6t1y3u5i7o9p0not started

queuedHTTP 202
Accepted, not started yet.
runningHTTP 202
Being worked on. stage says which step, or null while the job's machine starts.
succeededHTTP 200
Done. result holds the clips.
failedHTTP 200
Stopped. error says why. See failed jobs.

Stages

A running job moves through 4 stages, in order. Use them to show progress; how long each one takes depends on the video.

1fetching
Downloading the video from video_url.
2transcribing
Turning speech into timed words.
3ranking
Scoring candidate moments and picking the best ones.
4rendering
Cropping each clip to your aspect, burning in captions, and writing the poster and caption files.

Waiting

Both POST /v1/clips and GET /v1/jobs/{id} can hold the request open until the job is done, so a script can skip polling. Ask for it either way:

?wait=Nquery
Wait up to N seconds, 0 to 300.
Prefer: wait=Nheader
The same, as a header.
Prefer: respond-asyncheader
Return right away, without waiting.

Without either, create waits 60 seconds and GET doesn't wait. A wait outside 0 to 300 gets 400 INVALID_REQUEST. The answer comes as soon as the job finishes. If the wait runs out first, you get 202 Accepted with the job as it is now, a Location header that points at it, and Retry-After: 5:

202 Accepted
Location:
https://api.clipsdk.com/v1/jobs/job_q8w2e4r6t1y3u5i7o9p0
Retry-After:
5
{
  "job": {
    "id": "job_q8w2e4r6t1y3u5i7o9p0",
    "op": "clips",
    "status": "running",
    "stage": "ranking",
    "created_at": "2026-10-26T09:14:02Z",
    "finished_at": null,
    "result": null,
    "usage": {
      "source_minutes": 0,
      "clips": 0,
      "output_seconds": 0
    },
    "error": null,
    "poll_url": "https://api.clipsdk.com/v1/jobs/job_q8w2e4r6t1y3u5i7o9p0"
  }
}

To start a job without waiting, then hold a GET open until it's done:

# Start a job and return right away
curl https://api.clipsdk.com/v1/clips \
  -H "Authorization: Bearer $CLIPSDK_KEY" \
  -H "Content-Type: application/json" \
  -H "Prefer: respond-async" \
  -d '{"video_url": "https://cdn.example.com/off-script-112.mp4"}'

# Later: hold a request open until the job is done, for up to 300 s
curl "https://api.clipsdk.com/v1/jobs/job_q8w2e4r6t1y3u5i7o9p0?wait=300" \
  -H "Authorization: Bearer $CLIPSDK_KEY"

Mind your client timeout

Set your HTTP client's timeout above the wait you ask for. Node's built-in fetch gives up after 300 seconds without response headers, so ask for 290 or less there.

Polling

Without a webhook, check the job until status is succeeded or failed. Wait the Retry-After seconds between calls. Adding ?wait=60 to each call means fewer requests, and you hear about the result the moment it's ready.

JOB=job_q8w2e4r6t1y3u5i7o9p0

# Each call waits up to 60 s. The loop ends when the job finishes or a call fails
while true; do
  STATUS=$(curl -sSf "https://api.clipsdk.com/v1/jobs/$JOB?wait=60" \
    -H "Authorization: Bearer $CLIPSDK_KEY" | jq -r .job.status)
  echo "$STATUS"
  case "$STATUS" in succeeded|failed|"") break ;; esac
  sleep 5
done

With many jobs in flight, a webhook beats polling.

Failed jobs

A job that can't finish doesn't fail the HTTP request. It ends with status failed, the GET still returns 200, and error holds a code and a message. stage and result are null and usage is all zeros:

200 OK
{
  "job": {
    "id": "job_f3k9m2p7x4c8v1b6n5z0",
    "op": "clips",
    "status": "failed",
    "stage": null,
    "created_at": "2026-10-26T09:14:02Z",
    "finished_at": "2026-10-26T09:14:09Z",
    "result": null,
    "usage": {
      "source_minutes": 0,
      "clips": 0,
      "output_seconds": 0
    },
    "error": {
      "code": "INPUT_FETCH_FAILED",
      "message": "Could not fetch video_url: HTTP 404"
    },
    "poll_url": "https://api.clipsdk.com/v1/jobs/job_f3k9m2p7x4c8v1b6n5z0"
  }
}

The codes are INPUT_FETCH_FAILED, INPUT_UNSUPPORTED, INPUT_TOO_LONG, INPUT_TOO_LARGE, NO_SPEECH, and INTERNAL when something breaks on our side. A failed job costs nothing, so fix the cause and send the same request again. Errors explains each code.

Files

Clip, poster and caption files are kept for 7 days. After that their URLs return 404, so copy the clips you want to keep. Use the URLs in the job rather than building them yourself.

File URLs need no API key and support Range requests, so video players can stream and seek, and downloads can resume.