Integration

How an execution machine joins the fleet and drains the queue.

Lifecycle

An executor enrols itself with a one-time token, lands in PENDING, and stays inert until an operator approves it. After approval it loops: pull a job, run it, heartbeat while running, submit the result.

  1. 1Operator mints an enrollment token (Executors → Add node)
  2. 2Worker POSTs /executors/register → receives its bearer token, status PENDING
  3. 3Operator approves the node
  4. 4Worker polls /pull → claims one job at a time
  5. 5Worker heartbeats to extend the lease and report progress
  6. 6Worker submits result + files; job becomes SUCCEEDED or FAILED

Authentication

Every call below carries the bearer token issued at registration: Authorization: Bearer ufx_…. The plaintext is returned exactly once — only its SHA-256 hash is stored, so a lost token means re-enrolling.

A node that is not APPROVED gets 403 with a code field naming its state. That is expected while waiting for approval — keep polling rather than exiting.

Endpoints

POST/api/integration/executors/registerauth: enrollment token in body
{
  "enrollmentToken": "ufe_…",
  "name": "studio-mac",
  "hostname": "timmys-mbp.local",
  "backend": "MLX_SWIFT",
  "capabilities": ["T2I", "EDIT", "INTERLEAVE", "VQA"],
  "maxPixels": 4194304,
  "concurrency": 1,
  "specs": { "chip": "Apple M5 Pro", "memoryBytes": 68719476736, "quantization": "8bit-g64" }
}
201 { "executorId": "…", "token": "ufx_…", "status": "PENDING" }

The enrollment token is burned atomically, so two machines racing the same token cannot both enrol.

POST/api/integration/pullauth: bearer
{ "capabilities": ["T2I","VQA"], "maxPixels": 4194304 }   // optional
200 {
  "jobId": "…", "kind": "T2I", "params": { … },
  "inputs": [{ "id": "…", "url": "/api/integration/assets/…", "filename": "a.png", "mimeType": "image/png" }],
  "leaseUntil": "2026-08-27T…Z", "attempt": 1
}
204   // nothing this node can run

Capabilities re-advertised here take effect immediately — lower maxPixels after an OOM without re-enrolling. Claiming is atomic; two executors pulling at once never get the same job.

POST/api/integration/jobs/:jobId/heartbeatauth: bearer
{ "progress": 0.42, "message": "step 21/50" }   // both optional
200 { "leaseUntil": "…", "canceled": false }

Extends the lease by 1200s. This is also the only way to learn about cancellation — when "canceled" is true, abort and stop; do not submit.

POST/api/integration/jobs/:jobId/submitauth: bearer
multipart/form-data
  payload = {"status":"SUCCEEDED","text":"…","metrics":{"durationMs":332000,"peakMemoryBytes":24030000000}}
  files   = <one or more binary parts>
200 { "ok": true, "assets": 1 }

Terminal — a job accepts exactly one submit. Image kinds attach files; VQA sends text only. If the lease was reclaimed while you were working, this returns 409 and the result is discarded.

GET/api/integration/assets/:assetIdauth: bearer
200 binary

Fetch a job's input images. Scoped to INPUT assets only.

Leases and failure

A claimed job carries a lease of 1200s. Heartbeat to extend it — a 2048² render measured 332s on MLX 8-bit and 623s on PyTorch/MPS, so a long job outlives its initial lease without one.

If the lease expires, the platform reclaims the job on the next pull from any node and re-queues it. After maxAttempts it is failed outright rather than looped forever — a machine that OOMs on a job will OOM on the retry too.

Crashing mid-run is therefore safe: do nothing, and the job returns to the queue. What you must not do is submit and then crash — submit is the commit point.

Job kinds

KindParamsSubmits
T2Iprompt, width, height, think, steps, cfgScale, timestepShift, seedone image
EDITprompt, inputAssetIds[], imgCfgScale, + samplingone image
INTERLEAVEprompt, resolution, + samplingseveral images + text
VQAquestion, inputAssetIds[], maxNewTokens, temperature, topP, thinktext only
kilndeck — unified multimodal fleet