Guides

    Submitting and polling LTX batch jobs

    LTX batch work has its own submit, inspect, cancel, and queue-depth endpoints. Submit a job, track it to completion, and read the queue first.

    Versely Team8 min read

    LTX batch jobs do not complete through GET /api/v1/status/:requestId. They have their own namespace under /api/v1/ltx-batch: submit, list, fetch one, cancel if it is still queued, and read queue depth before you pile more work on. Polling the unified status endpoint for an LTX batch id is how you wait on an object that endpoint does not own.

    This is the batch video queue, not the in-app agent tool generate_via_queue (that tool is gone). The REST surface is what remains, and it is enough: one POST to enqueue, one GET to watch the row, one GET to see whether the queue has enough depth to start.

    The endpoints

    All of them require auth. The generate scope covers /ltx-batch. A key that only has read will 403.

    Method Path What it does
    POST /api/v1/ltx-batch/jobs Charge credits, enqueue, return 202 with the job row
    GET /api/v1/ltx-batch/jobs List your jobs. Query: limit (default 50, max 200), offset, status
    GET /api/v1/ltx-batch/jobs/:id One job, including output_url once status is completed
    DELETE /api/v1/ltx-batch/jobs/:id Cancel, but only while status is queued. Credits charged for that job are refunded
    GET /api/v1/ltx-batch/queue/stats Queue depth, processing count, how many more jobs until a batch can start, your recent jobs

    There is also POST /api/v1/ltx-batch/hook-pack for a branded hook batch charged once. That is a different product path from these job endpoints. The in-app branded hook pack is the agent-facing version of the same idea — a planned set of openings, one charge — not a substitute for GET /jobs/:id. This post is the job queue.

    Statuses you will see on a job: queued, processing, completed, failed, cancelled. List and fetch are scoped to your user id. You cannot inspect someone else's row.

    There is no customer webhook. Do not send webhook_url. Nothing will POST you the mp4. Batch generation as a concept still applies. Delivery is pull.

    Submit a job

    curl -sS -X POST "$VERSELY_API_URL/api/v1/ltx-batch/jobs" \
      -H "Authorization: Bearer $VERSELY_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "prompt": "A ceramic pour-over on a walnut counter, morning window light, slow push-in, 9:16",
        "workflow_type": "t2v",
        "audio_enabled": true,
        "width": 720,
        "height": 1280,
        "length": 121,
        "fps": 25,
        "tier": "720p"
      }'
    

    A successful submit is 202 with { success: true, job: { … } }. Save job.id. That is the handle for every later call.

    Fields the controller actually accepts:

    • prompt (required). A string of at least three characters.
    • negative_prompt (optional). If omitted, the queue applies its default negative.
    • workflow_type. "t2v" or "i2v". Default t2v. Anything else is 400. Image-to-video requires input_image_url as an http… URL.
    • audio_enabled. Boolean, default true.
    • width / height. Integers 64–2048, divisible by 32. Defaults are 720×1280 (portrait).
    • length. Frame count. Must be in 1, 9, 17, …, 257 (that is (length - 1) % 8 === 0). Default 121.
    • fps. 8–60. Default 25, which is Versely's default frame rate, not 24.
    • seed. Optional integer.
    • tier. "720p" or "1080p". Default 720p. Billing uses this tier plus duration derived from (length - 1) / fps, not a guess from the pixel dimensions, so set it on purpose.
    • priority. Integer, default 0. Higher goes sooner.
    • batch_id. Optional string if you are grouping rows.

    Duration from the defaults: 121 frames at 25 fps is (121 - 1) / 25 = 4.8 seconds, rounded to 5 for billing. If you want a different length, keep the 8n+1 rule. 97 frames is 3.84s at 25 fps. 161 is 6.4s. Do not send 120.

    Credits are charged up front. Insufficient balance is 402 with credits_required. The job row carries credits_charged. Failed insert refunds. There is no free pass through this queue: every accepted job costs credits.

    For prompting, the LTX 2.3 family notes on the prompting guide and Fast / Pro / Retake still apply to what you put in prompt. The batch endpoint is how those prompts wait in line. The interactive AI video generator is the non-batch door for the same catalog.

    Poll it, and read the queue first

    # How deep is the queue right now?
    curl -sS "$VERSELY_API_URL/api/v1/ltx-batch/queue/stats" \
      -H "Authorization: Bearer $VERSELY_API_KEY"
    
    # Your recent jobs
    curl -sS "$VERSELY_API_URL/api/v1/ltx-batch/jobs?limit=20&status=queued" \
      -H "Authorization: Bearer $VERSELY_API_KEY"
    
    # One job, until it leaves queued/processing
    JOB_ID="…"
    for i in $(seq 1 120); do
      RESP=$(curl -sS "$VERSELY_API_URL/api/v1/ltx-batch/jobs/$JOB_ID" \
        -H "Authorization: Bearer $VERSELY_API_KEY")
      STATE=$(echo "$RESP" | jq -r '.job.status')
      echo "[$i] $STATE"
      case "$STATE" in
        completed) echo "$RESP" | jq -r '.job.output_url'; break ;;
        failed|cancelled) echo "$RESP"; break ;;
      esac
      sleep 15
    done
    

    GET /queue/stats returns a stats object. Read these fields, not folklore:

    • queueDepth. Jobs currently queued across the queue.
    • processing. Jobs currently rendering.
    • minBatchSize. Depth at which a batch can start.
    • remainingForStart. max(0, minBatchSize - queueDepth). If this is greater than zero, a new pod has not been triggered yet.
    • activePodCount / activePodSummary. Provisioning, ready, draining.
    • myJobs. Your recent queued, processing, completed, and failed rows (id, status, workflow_type, queued_at, output_url).

    That payload is why you read the queue before submitting the next twenty prompts. If remainingForStart is 8 and you were about to fire one more clip "to see," you now know one clip will sit until other work fills the gap, or until you submit the rest of a real batch. Piling on blindly is how you pay for twenty jobs that all wait on the same start condition.

    Poll GET /jobs/:id on an interval measured in tens of seconds, not hundreds of milliseconds. Per-key RPM default is 60; status-style polling is the cheap wait. Re-POSTing /jobs because you are impatient is a second charge.

    When status is completed, output_url is the mp4. The queue also mirrors the job into your generations list, so the same file shows up in the app without a second download path.

    Compare this to polling a dub queue: same idea, different endpoints. Do not mix them.

    Cancel only while it is queued

    curl -sS -X DELETE "$VERSELY_API_URL/api/v1/ltx-batch/jobs/$JOB_ID" \
      -H "Authorization: Bearer $VERSELY_API_KEY"
    

    If the row is still queued, you get the updated job and the credits charged for it are refunded. If it has moved to processing (or anything else), you get 409 with Job is not in a cancellable state. There is no abort of a render already on a pod. Plan the batch before you submit if you are not sure you want all of it.

    Hook packs are the other batch primitive: one brand brief, N distinct hooks, one charge for the pack. Use that when the job is a set of branded openings. Use /ltx-batch/jobs when you have prompts of your own and you want them in this queue. Generating a hook pack before the video exists is the production-order argument for the pack. It is not a substitute for these endpoints.

    The developer page is the index of how you authenticate. Same key, same generate scope, same rule that an empty balance is a 402 rather than a free queued job.

    FAQ

    Can I poll GET /api/v1/status/:requestId with the batch job id?

    Do not. That endpoint is the unified generate/status path. LTX batch rows live in ltx_batch_queue and are fetched from /api/v1/ltx-batch/jobs/:id. Mixing the two is the most common way a "it never finished" report turns out to be a 404 on the wrong namespace.

    Why is my job still queued?

    Read /queue/stats. If remainingForStart is greater than zero, the queue has not reached minBatchSize yet and your job is waiting on depth, not on a stuck worker. Submit the rest of the batch you actually wanted, or wait for other users' jobs to fill it. If processing is already non-zero and your row stays queued, you are behind work that started ahead of you. Watch myJobs.

    What workflow types are valid?

    t2v and i2v. First-last-frame and audio-driven variants are not accepted on this route; sending them is a 400. For i2v, input_image_url must be an HTTP URL. Local file:// paths are rejected the way they are everywhere else on the API.

    Does cancel work once rendering has started?

    No. DELETE is a queued-only operation. 409 means you are too late. Credits for a job that is already processing follow that job's success or failure path, not the cancel path.