API reference
Jobs

Jobs

A job is one source file processed with one preset.

Create a job

POST /api/v1/jobs

Request body

FieldTypeRequiredDescription
inputUrlstringyesPublicly reachable HTTP or HTTPS URL of the source file
presetstringyesPreset identifier, for example mp4_264_720p
metadataobjectnoArbitrary key and value pairs stored with the job

There is no webhookUrl field. The destination for notifications is the webhook configured on your account, not something you set per job. Sending webhookUrl in the body has no effect.

curl -X POST https://videotranscode.cloud/api/v1/jobs \
  -H "Authorization: Bearer $VT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "inputUrl": "https://cdn.example.com/master.mov",
    "preset": "hls_abr_264_standard"
  }'

Response

201 Created.

{
  "id": "9d1f5c0a-4e7b-4a61-9b2e-8f1c3d5a7e42",
  "status": "queued",
  "preset": "hls_abr_264_standard",
  "inputUrl": "https://cdn.example.com/master.mov",
  "webhookUrl": "https://yourapp.example.com/hooks/video",
  "createdAt": "2026-09-10T14:08:11.204Z",
  "estimatedProcessingTime": "8-18 minutes",
  "links": {
    "self": "/api/v1/jobs/9d1f5c0a-4e7b-4a61-9b2e-8f1c3d5a7e42",
    "cancel": "/api/v1/jobs/9d1f5c0a-4e7b-4a61-9b2e-8f1c3d5a7e42"
  },
  "user": {
    "remainingJobs": 97,
    "plan": "Indie/Pro"
  }
}

webhookUrl echoes the webhook currently configured on your account, or null if you have not configured one. user.plan is the plan's display name, which is not always the same as the plan identifier used elsewhere; the free plan is displayed as Starter.

What happens on acceptance

The quota slot is claimed before anything else, so two simultaneous requests cannot both slip past your monthly limit. If the job cannot be handed to the queue, the slot is released and the job is marked failed, so a queueing problem does not cost you a job.

The source is not fetched during this request. URL reachability, file format and duration are all checked later, by the worker. A job that is accepted here can still fail. See Handling failures.

Errors

400 for malformed JSON, a missing or non-URL inputUrl, a missing preset, or a preset name that does not exist. 403 when the preset exists but your plan does not include it. 429 when your monthly quota is used up. Bodies for all of these are in Errors.

Read a job

GET /api/v1/jobs/{id}
curl https://videotranscode.cloud/api/v1/jobs/9d1f5c0a-4e7b-4a61-9b2e-8f1c3d5a7e42 \
  -H "Authorization: Bearer $VT_API_KEY"
{
  "id": "9d1f5c0a-4e7b-4a61-9b2e-8f1c3d5a7e42",
  "status": "completed",
  "preset": "hls_abr_264_standard",
  "inputUrl": "https://cdn.example.com/master.mov",
  "outputUrl": "https://sfo3.digitaloceanspaces.com/...&X-Amz-Signature=...",
  "expiration": "2026-09-17T14:12:47.118Z",
  "duration": 742,
  "errorMessage": null,
  "retryCount": 0,
  "metadata": {
    "createdAt": "2026-09-10T14:08:11.204Z",
    "startedAt": "2026-09-10T14:08:19.663Z",
    "completedAt": "2026-09-10T14:12:47.118Z",
    "lastCheckedAt": "2026-09-10T14:12:47.118Z"
  },
  "links": {
    "self": "/api/v1/jobs/9d1f5c0a-4e7b-4a61-9b2e-8f1c3d5a7e42",
    "output": "/api/v1/jobs/9d1f5c0a-4e7b-4a61-9b2e-8f1c3d5a7e42/download-url"
  }
}
FieldDescription
statusqueued, processing, completed, failed or cancelled
outputUrlSigned download link, or null when there is nothing to download
expirationWhen that specific link stops working, or null
durationSource duration in seconds, measured during processing
errorMessageWhy the job failed, or null
retryCountHow many times the worker retried this job
metadataLifecycle timestamps; null for stages not reached yet

outputUrl is signed at read time, so it is fresh on every call. It is null when the job is not complete, when the output is no longer in storage, and for a small number of old jobs stored before the current storage layout, which can no longer be signed.

The links.cancel entry appears only while the job is queued or processing. The links.output entry appears only when there is a signed link to give you.

List jobs

GET /api/v1/jobs

Newest first.

ParameterTypeDefaultDescription
pageinteger1Page number; anything that is not a positive integer falls back to the default
limitinteger10Items per page, capped at 100
statusstringnoneExact match on job status
presetstringnoneExact match on preset identifier
curl "https://videotranscode.cloud/api/v1/jobs?status=completed&limit=25" \
  -H "Authorization: Bearer $VT_API_KEY"
{
  "jobs": [
    {
      "id": "9d1f5c0a-4e7b-4a61-9b2e-8f1c3d5a7e42",
      "status": "completed",
      "preset": "hls_abr_264_standard",
      "inputUrl": "https://cdn.example.com/master.mov",
      "outputUrl": "https://sfo3.digitaloceanspaces.com/...&X-Amz-Signature=...",
      "expiration": "2026-09-17T14:12:47.118Z",
      "duration": 742,
      "errorMessage": null,
      "createdAt": "2026-09-10T14:08:11.204Z",
      "startedAt": "2026-09-10T14:08:19.663Z",
      "completedAt": "2026-09-10T14:12:47.118Z",
      "links": {
        "self": "/api/v1/jobs/9d1f5c0a-4e7b-4a61-9b2e-8f1c3d5a7e42"
      }
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 25,
    "totalPages": 2,
    "totalCount": 34,
    "hasNext": true,
    "hasPrev": false
  },
  "links": {
    "self": "/api/v1/jobs?page=1&limit=25",
    "next": "/api/v1/jobs?page=2&limit=25",
    "prev": null
  }
}

Listing signs a download link for every completed job on the page. Listing 100 completed jobs therefore does 100 signatures. If you only need statuses, ask for a smaller page.

The list omits retryCount, and it puts the lifecycle timestamps at the top level rather than under metadata. Reading a single job gives you the fuller shape.

Cancel a job

DELETE /api/v1/jobs/{id}
curl -X DELETE https://videotranscode.cloud/api/v1/jobs/9d1f5c0a-4e7b-4a61-9b2e-8f1c3d5a7e42 \
  -H "Authorization: Bearer $VT_API_KEY"
{
  "id": "9d1f5c0a-4e7b-4a61-9b2e-8f1c3d5a7e42",
  "status": "cancelled",
  "message": "Job cancelled successfully",
  "cancelledAt": "2026-09-10T14:09:02.551Z"
}

Only queued and processing jobs can be cancelled; anything else returns 400. The job row is marked cancelled first and then pulled from the queue, so a job that had not started never starts. A job already running is marked cancelled and stops shortly afterwards, and the response says so:

{
  "id": "9d1f5c0a-4e7b-4a61-9b2e-8f1c3d5a7e42",
  "status": "cancelled",
  "message": "Job cancelled; it is currently processing and will stop shortly",
  "cancelledAt": "2026-09-10T14:09:02.551Z"
}

Cancelling does not return the quota slot. The slot is claimed when the job is created.

Sign a download link

GET /api/v1/jobs/{id}/download-url

Reading a job already gives you a signed outputUrl. This endpoint exists for when you want a link without the rest of the job.

{
  "downloadUrl": "https://sfo3.digitaloceanspaces.com/...&X-Amz-Signature=...",
  "expiresAt": "2026-09-17T14:12:47.118Z",
  "message": "Download URL generated successfully"
}

Its errors use a different shape from the rest of the API: a bare message field with no error. 404 when the job does not exist or is not yours, 400 when the job is not completed or has no output, and 410 when the output can no longer be signed.

{ "message": "Output file is not available or job is not completed" }