emit.run

Worker API

Poll, claim, acknowledge, complete, fail, and kill jobs

Worker API

Endpoints for the worker lifecycle: claiming jobs from the queue and reporting final outcomes.


Poll for Jobs

POST /api/v1/spaces/:spaceId/jobs/poll

Scope: jobs:poll

Claim pending jobs from the dispatch queue. Jobs are atomically dequeued — no two workers will receive the same job.

Request body:

FieldTypeRequiredDefaultDescription
countnumberNo1Number of jobs to claim (max: 50)
typestring | string[]NoPreferred job-type filter. Pass one type or an array of types.
typesstring[]NoAlias for type (array form).
namestring | string[]NoBackward-compatible alias for type.
namesstring[]NoAlias for name (array form).
curl -X POST https://emit.run/api/v1/spaces/$SPACE_ID/jobs/poll \
  -H "x-api-key: $EMIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"count": 5, "type": ["process-video", "generate-thumbnail"]}'
const res = await fetch(`${API}/spaces/${spaceId}/jobs/poll`, {
  method: "POST",
  headers: { "x-api-key": key, "Content-Type": "application/json" },
  body: JSON.stringify({ count: 5, type: ["process-video", "generate-thumbnail"] }),
});
const { jobs } = await res.json();
res = requests.post(
    f"{API}/spaces/{space_id}/jobs/poll",
    headers={"x-api-key": key, "Content-Type": "application/json"},
    json={"count": 5, "type": ["process-video", "generate-thumbnail"]},
)
jobs = res.json()["jobs"]

If you send more than one filter field, precedence is types -> type -> names -> name.

Response:

{
  "jobs": [
    {
      "id": "01JLQX...",
      "name": "process-video",
      "payload": { "url": "s3://bucket/video.mp4", "format": "720p" },
      "timeoutSeconds": 600,
      "maxRetries": 5,
      "attemptNumber": 0,
      "createdAt": "2025-02-24T10:30:00.000Z"
    }
  ]
}

Returns {"jobs":[]} if no jobs are available. Your worker should back off and retry.


Claim Specific Job

POST /api/v1/jobs/:jobId/claim

Scope: jobs:poll

Atomically claim one specific pending job by ID. Useful with push/webhook wake signals where your worker receives a job ID and claims that exact job.

If the job is no longer pending (already claimed, running, completed, etc.), the endpoint returns claimed: false and the current status.

curl -X POST https://emit.run/api/v1/jobs/$JOB_ID/claim \
  -H "x-api-key: $EMIT_KEY"
const res = await fetch(`${API}/jobs/${jobId}/claim`, {
  method: "POST",
  headers: { "x-api-key": key },
});
const claim = await res.json();

if (claim.claimed) {
  const job = claim.job;
}
res = requests.post(
    f"{API}/jobs/{job_id}/claim",
    headers={"x-api-key": key},
)
claim = res.json()

if claim.get("claimed"):
    job = claim["job"]

Response:

{
  "claimed": true,
  "job": {
    "id": "01JLQX...",
    "name": "process-video",
    "payload": { "url": "s3://bucket/video.mp4" },
    "timeoutSeconds": 600,
    "maxRetries": 5,
    "attemptNumber": 0,
    "createdAt": "2025-02-24T10:30:00.000Z"
  }
}
{
  "claimed": false,
  "reason": "not_pending",
  "status": "delivered"
}
{ "error": "Job not found" }

Acknowledge Job

POST /api/v1/jobs/:jobId/ack

Scope: jobs:ack

Tell the system your worker has received the job and is starting work. This moves the job from delivered to running and starts the execution timeout timer.

curl -X POST https://emit.run/api/v1/jobs/$JOB_ID/ack \
  -H "x-api-key: $EMIT_KEY"
await fetch(`${API}/jobs/${jobId}/ack`, {
  method: "POST",
  headers: { "x-api-key": key },
});

Complete Job

POST /api/v1/jobs/:jobId/complete

Scope: jobs:complete

Mark the job as successfully completed. Optionally include a result payload.

curl -X POST https://emit.run/api/v1/jobs/$JOB_ID/complete \
  -H "x-api-key: $EMIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"result": {"output_url": "s3://bucket/output.mp4", "duration_ms": 12340}}'
await fetch(`${API}/jobs/${jobId}/complete`, {
  method: "POST",
  headers: { "x-api-key": key, "Content-Type": "application/json" },
  body: JSON.stringify({
    result: { output_url: "s3://bucket/output.mp4", duration_ms: 12340 },
  }),
});

Fail Job

POST /api/v1/jobs/:jobId/fail

Scope: jobs:fail

Mark the job as failed. If the job has retries remaining, it will be re-queued as pending. Include an error message or object for debugging.

curl -X POST https://emit.run/api/v1/jobs/$JOB_ID/fail \
  -H "x-api-key: $EMIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"error": "FFmpeg exited with code 1: out of memory"}'
await fetch(`${API}/jobs/${jobId}/fail`, {
  method: "POST",
  headers: { "x-api-key": key, "Content-Type": "application/json" },
  body: JSON.stringify({
    error: "FFmpeg exited with code 1: out of memory",
  }),
});

Kill Job

POST /api/v1/jobs/:jobId/kill

Scope: jobs:kill

Force-stop a job and mark it as killed. Useful for operator intervention, cancellation flows, or control-plane shutdowns.

If the job is already killed, this endpoint is idempotent and returns success.

curl -X POST https://emit.run/api/v1/jobs/$JOB_ID/kill \
  -H "x-api-key: $EMIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"reason": "manual stop"}'
await fetch(`${API}/jobs/${jobId}/kill`, {
  method: "POST",
  headers: { "x-api-key": key, "Content-Type": "application/json" },
  body: JSON.stringify({ reason: "manual stop" }),
});

Worker Stop Signal

If a job is force-stopped via POST /jobs/:id/kill, worker mutation endpoints (progress, checkpoint, event, keepalive) return:

{
  "error": "Job has been killed",
  "code": "JOB_KILLED",
  "jobStatus": "killed"
}

The HTTP status is 409. Workers should treat this as an immediate stop signal for that job.

POST /jobs/:id/logs remains available after kill so workers can publish teardown diagnostics.


Scope Quick Reference

EndpointRequired scope
POST /spaces/:id/jobsjobs:create
GET /spaces/:id/jobsjobs:read
POST /spaces/:id/jobs/polljobs:poll
POST /jobs/:id/claimjobs:poll
GET /jobs/:idjobs:read
GET /jobs/:id/logsjobs:read
GET /jobs/:id/progressjobs:read:progress or jobs:read
POST /jobs/:id/ackjobs:ack
POST /jobs/:id/progressjobs:progress
POST /jobs/:id/logsjobs:event
POST /jobs/:id/checkpointjobs:event
POST /jobs/:id/eventjobs:event
POST /jobs/:id/completejobs:complete
POST /jobs/:id/failjobs:fail
POST /jobs/:id/killjobs:kill
POST /jobs/:id/keepalivejobs:keepalive

A token with jobs:worker covers all worker scopes.

On this page