Skip to content
emitDocs

API reference

Jobs

Create, read, list, follow and cancel jobs over HTTP.

A job lives in a space, and every path here starts with it: /v1/spaces/{space}/jobs. See Jobs for what each field does in practice.

Create jobs

POST /v1/spaces/{space}/jobs · scope create

Send one job, an array of jobs, or {"jobs": [...]}. The space is created by its first job.

namestringrequired

What kind of work it is. Workers claim jobs by name. At most 256 bytes.

payloadJSON

What the worker needs, as any JSON value. At most 4 MiB.

keystring

An idempotency key. A job with a key an earlier job in the space has isn't made again: the answer is the earlier job's id, with outcome DUPLICATE. A key holds while its job is unfinished, and for 10 minutes after it finishes.

delayMsinteger

Don't hand the job out before this many milliseconds from now. Up to a year.

runAtMsinteger

Don't hand the job out before this Unix time, in milliseconds. Up to a year from now.

maxAttemptsintegerdefault: the space's (4)

Tries in all, counting the first. From 1 to 100.

timeoutMsintegerdefault: the space's (300000)

The lease a worker gets on each attempt, unless the worker asks for another.

backoffMsintegerdefault: the space's (1000)

The wait after the first failure.

backoffobject

How the waits after that grow, over the space's policy field by field: kind (EXPONENTIAL, LINEAR or CONSTANT), maxMs (the longest wait, at most an hour), multiplierPercent (100 to 1000; 200 doubles), jitter (NONE, FULL or EQUAL).

callbackobject

A webhook sent when the job finishes: url, and optional headers, an object of header names and values. See Callbacks.

singletonstring

Only one unfinished job with this value can exist in the space. A second create answers DUPLICATE with the open job's id. Once it finishes, the next create makes a new job. Not with key, and not in a FIFO space.

workflowboolean

The job is a workflow run.

curl -s "$EMIT_URL/v1/spaces/billing/jobs" \
  -H "Authorization: Bearer $EMIT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[
    {"name": "charge-invoice", "payload": {"invoiceId": "inv_1042"}, "key": "charge-inv_1042"},
    {"name": "charge-invoice", "payload": {"invoiceId": "inv_1043"}, "key": "charge-inv_1043", "delayMs": 60000}
  ]'
const created = await emit.jobs.createMany([
  { name: "charge-invoice", payload: { invoiceId: "inv_1042" }, key: "charge-inv_1042" },
  { name: "charge-invoice", payload: { invoiceId: "inv_1043" }, key: "charge-inv_1043", delayMs: 60_000 },
]);
created, err := c.Jobs.CreateMany(ctx, []emit.NewJob{
	{Name: "charge-invoice", Payload: map[string]string{"invoiceId": "inv_1042"}, Key: "charge-inv_1042"},
	{Name: "charge-invoice", Payload: map[string]string{"invoiceId": "inv_1043"}, Key: "charge-inv_1043", DelayMs: 60_000},
})
JSON
{
  "created": [
    { "id": "01M3QSGN0G01ZVECFQ48X49EVM", "outcome": "NEW", "attempt": 0 },
    { "id": "01M3QSGN0G024F4C1PA48D5EVM", "outcome": "NEW", "attempt": 0 }
  ]
}

One entry per job, in the order sent. outcome is NEW, or DUPLICATE when the key or singleton already made a job, whose id this is. A space at its in-flight limit refuses the whole request with 429: see Errors.

Get a job

GET /v1/spaces/{space}/jobs/{id} · scope read

Shell
curl -s "$EMIT_URL/v1/spaces/billing/jobs/01M3QSGN0G01ZVECFQ48X49EVM" -H "Authorization: Bearer $EMIT_TOKEN"
idstring

The job's id: 26 characters.

spacestring
namestring
payloadJSON
statusstring

PENDING, RUNNING, COMPLETED, FAILED or CANCELLED.

attemptinteger

The current or last attempt: 0 before the first claim.

maxAttemptsinteger
resultJSON

What the worker completed it with, or the error of the last failure. null until then.

progressobject

The latest progress: percent, message, and stages, the same per named stage.

eventsJSON[]

Every event the workers reported, in order.

createdAtMsstring

When it was created.

finishedAtMsstring

When it finished; "0" until then.

leaseUntilMsstring

While it runs, when the worker's lease ends.

notBeforeMsstring

While it waits for a time (a delay, or the wait before a retry), when that wait ends. "0" otherwise.

callbackobject

Its callback, if it has one. Header values are shown empty.

flowobject

For a workflow run: what it's up to. See Workflow runs.

List jobs

GET /v1/spaces/{space}/jobs · scope read

Newest first. Each entry is a summary: id, name, status, attempt, maxAttempts, createdAtMs, finishedAtMs, leaseUntilMs, notBeforeMs and progress. Get a job for its payload and result.

namestring

Only jobs of this name.

statusstring

Only jobs in this status: PENDING, RUNNING, COMPLETED, FAILED or CANCELLED.

fromMsinteger

Only jobs created at or after this Unix time, in milliseconds.

toMsinteger

Only jobs created before this time.

limitinteger

At most this many; up to 1,000.

pagestring

The nextPage of the page before.

runsboolean

Only workflow runs, at most 100 a page. Each unfinished one has run: its node in the run's tree, without children or notes. Needs emit 0.6.

curl -s "$EMIT_URL/v1/spaces/billing/jobs?name=charge-invoice&status=FAILED&limit=50" \
  -H "Authorization: Bearer $EMIT_TOKEN"
const page = await emit.jobs.list({ name: "charge-invoice", status: "failed", limit: 50 });
page, err := c.Jobs.List(ctx, emit.ListJobs{Name: "charge-invoice", Status: emit.Failed, Limit: 50})
JSON
{ "jobs": [{ "id": "01M3QSGN0103P7EGVBX9D5NTSN", "name": "charge-invoice", "status": "FAILED", "attempt": 5, "maxAttempts": 5, "createdAtMs": "1790726132737", "finishedAtMs": "1790726139553", "leaseUntilMs": "0", "notBeforeMs": "0" }], "nextPage": "" }

A page can be shorter than limit, even empty, and still have a nextPage: keep asking until nextPage is empty. A narrow time range is the cheapest way to look through a large history.

Read a job's log lines

GET /v1/spaces/{space}/jobs/{id}/lines · scope read

minLevelstring

Only lines at this level or above: TRACE, DEBUG, INFO, WARN, ERROR or FATAL.

sourcestring

Only lines from this source. Notes have the source note.

afterstring

The cursor of the last line you read, to go on from there.

limitinteger

At most this many; up to 1,000.

Shell
curl -s "$EMIT_URL/v1/spaces/billing/jobs/01M3QSGN0103P7EGVBX9D5NTSN/lines?minLevel=WARN" \
  -H "Authorization: Bearer $EMIT_TOKEN"
JSON
{ "lines": [{ "cursor": "9.0", "attempt": 1, "receivedAtMs": "1790726139530", "line": { "level": "WARN", "message": "card issuer slow to answer", "source": "", "atMs": "0", "metadata": null } }] }

Lines come in the order they were written, from every attempt.

Follow a job

GET /v1/spaces/{space}/jobs/{id}/events · scope read, or a follow token

A stream of the job's changes: server-sent events, or a WebSocket if the request asks to upgrade. Each message is one JSON event, with atMs and one of these:

Event When
now First: the job as it is, as Get a job shows it.
claimed A worker took it: id, attempt, name, leaseUntilMs.
progress New progress.
event A new event.
lines New log lines: their count and cursor, and the lines themselves with ?lines=true.
retrying An attempt failed and another is coming: attempt, nextAtMs, error.
released A worker gave it back unfinished.
finished Last: its status and result. The stream then ends.
linesboolean

true to send log lines themselves, not only that there are new ones.

treeboolean

For a workflow run: send the whole run as a tree on every change instead. See Read a run's tree.

tokenstring

A follow token, for a browser with no API token.

Shell
curl -s -N "$EMIT_URL/v1/spaces/billing/jobs/01M3QSGN0103P7EGVBX9D5NTSN/events" -H "Authorization: Bearer $EMIT_TOKEN"
text
data: {"atMs":"1790726139614","now":{"id":"01M3QSGN0103P7EGVBX9D5NTSN","status":"RUNNING",...}}

data: {"atMs":"1790726139702","progress":{"percent":50.0,"message":"charging","stages":{}}}

data: {"atMs":"1790726140031","finished":{"status":"COMPLETED","result":{"chargeId":"ch_3PQ"}}}

When two changes land close together, a follower may see only the later one. Treat the stream as the job's latest state, not as an exact log.

Make a follow token

POST /v1/spaces/{space}/jobs/{id}:followToken · scope read

A token that lets a browser follow one job without an API token, for the time you ask.

expiresInMsintegerrequired

How long the token works: from a second to a year.

Shell
curl -s -X POST "$EMIT_URL/v1/spaces/billing/jobs/01M3QSGN0103P7EGVBX9D5NTSN:followToken" \
  -H "Authorization: Bearer $EMIT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"expiresInMs": 3600000}'
# {"token":"t=1790729747943,v1=09360a4d2d6ccf9a..."}

Cancel jobs

POST /v1/spaces/{space}/jobs:cancel · scope admin

Send an array of ids, or {"ids": [...]}. Add "tree": true to cancel workflow runs with everything they started.

curl -s "$EMIT_URL/v1/spaces/billing/jobs:cancel" \
  -H "Authorization: Bearer $EMIT_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '["01M3QSGN0G01ZVECFQ48X49EVM", "01M3QSGN0G024F4C1PA48D5EVM"]'
const outcomes = await emit.jobs.cancel("01M3QSGN0G01ZVECFQ48X49EVM", "01M3QSGN0G024F4C1PA48D5EVM");
outcomes, err := c.Jobs.Cancel(ctx, "01M3QSGN0G01ZVECFQ48X49EVM", "01M3QSGN0G024F4C1PA48D5EVM")
JSON
{ "outcomes": [{ "status": "CANCELLED" }, { "rejection": { "reason": "ALREADY_FINISHED", "message": "" } }] }

One outcome per id: the job's status now, or why it wasn't cancelled (NOT_FOUND or ALREADY_FINISHED).