waitForJob times out at 20 minutes: why it does not fit a Vercel route

Sume SDK waitForJob waits 20 minutes by default and the job keeps billing if it throws. Vercel functions default to 300 s, so wait in a worker.

4 min readSume
All posts

waitForJob in @sume-com/sdk 0.2.0 waits up to 20 minutes before it throws SumeJobTimeoutError, which is far longer than a default Vercel function. The Vercel duration page (last updated 2026-08-24) gives 300 seconds as the default on every plan. Call waitForJob from a long-lived worker, not from a request handler, and let a route only submit and return.

Defaults side by side

The helper polls every 2 seconds at minimum, and when next_poll_after_seconds in the status payload is longer, the server value wins. waitForRun waits 10 minutes and subscribeFormatRun 20 minutes by default; waitForRun needs family.

Timeouts, read 2026-10-08
ItemDefaultMaximum
Vercel function (Hobby)300 s300 s
Vercel function (Pro, Enterprise)300 s800 s; 1,800 s beta
waitForJob20 minSet with timeout
waitForRun10 minSet with timeout
subscribeFormatRun20 minSet with timeout
Sume sync waitUp to 30 s30 s

A timeout does not cancel the job

The helper throws and the job continues to run, and it still bills. Both errors, SumeJobTimeoutError and SumeJobRequestError, carry the jobId, so store it and read the job again later. Do not submit a second job; if you must retry a submit, send the same Idempotency-Key.

import { createSumeClient, waitForJob, SumeJobTimeoutError } from "@sume-com/sdk";

const client = createSumeClient({ apiKey: process.env.SUME_API_KEY! });

export async function wait(jobId: string, signal?: AbortSignal) {
  try {
    return await waitForJob(jobId, { client, timeout: 10 * 60_000, signal });
  } catch (err) {
    if (err instanceof SumeJobTimeoutError) {
      return { jobId, status: "still_running" as const };
    }
    throw err;
  }
}

Where to put the wait

Pick one of three shapes and keep the route short.

  • Webhook: submit with mode: "webhook", return, and verify the signed event.
  • Worker: a queue consumer or a Railway or Modal service runs waitForJob.
  • Browser polling: the route reads one status per request and returns next_poll_after_seconds.

Passing an abort signal

waitForJob accepts a signal that aborts both the wait and the request in flight. Wire it to your platform shutdown so a deploy does not leave a loop running. Aborting does not cancel the job; it only stops your reading. To cancel, use the cancel URL on the job envelope, which works only before generation starts.

The onStatus callback runs on every read, including the last one, with the status and a snapshot that has next_action. Use it to update a progress row in your own database, so a restart can resume from the stored job id.

Formats are different

subscribeFormatRun creates a Format run and waits, with the same 20 minute default, and it generates an idempotencyKey for you when you do not pass one. Pass your own when the run belongs to an order, so a retry cannot start a second run. There is no SSE stream in this SDK version, so every wait is a poll.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume