Rails Active Job that polls an AI video API: retry_job and wait

An Active Job on Solid Queue reads Sume's job status once and calls retry_job with wait from next_poll_after_seconds until the video job is terminal.

5 min readSume
All posts

On Rails 8 with Solid Queue (the default Active Job backend), poll an AI video the Active Job way: PollVideoJob.set(wait: 15.seconds).perform_later(job_id), and inside perform, retry_job(wait: n) or self.class.set(wait: n).perform_later(...) when the job is still running. Active Job is backend-neutral, so the same class runs on Sidekiq or GoodJob unchanged. The wait comes from Sume's next_poll_after_seconds on GET /v1/jobs/{id}/status (Sume jobs guide, read 2026-10-06).

If you want Sidekiq's native API instead, see the Sidekiq version of this poller.

What does the job class look like?

The class below re-enqueues itself with an attempt counter, so Active Job's own error retries (retry_on) only count real failures such as network errors.

require "net/http"
require "json"

class PollVideoJob < ApplicationJob
  queue_as :default
  retry_on Net::ReadTimeout, Net::OpenTimeout, wait: 30.seconds, attempts: 5

  def perform(job_id, n = 0)
    uri = URI("https://api.sume.com/v1/jobs/#{job_id}/status")
    req = Net::HTTP::Get.new(uri, "Authorization" => "Bearer #{ENV.fetch('SUME_API_KEY')}")
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    raise "sume status #{res.code}" unless res.is_a?(Net::HTTPSuccess)

    s = JSON.parse(res.body)
    if s["terminal"]
      Video.find_by!(sume_job_id: job_id).update!(state: s["sume_status"])
    elsif n < 120
      wait = (s["next_poll_after_seconds"] || 15).to_i
      self.class.set(wait: wait.seconds).perform_later(job_id, n + 1)
    end
  end
end

Where does the paid request go?

Into a separate job or a controller action. Send Idempotency-Key: video-#{video.id} on POST /v1/videos and store the returned id on the row before you enqueue the first poll. If that save fails after the POST succeeded, re-sending with the same key returns the original job rather than a second charge.

Does a retry of the poller bill a second clip?

No, as long as the poller only reads. Keep the paid POST /v1/videos in a separate step with an Idempotency-Key you own, and let the poller use GET /v1/jobs/{id}/status. A client-side give-up never cancels the render; Sume's jobs guide says the job keeps running, so store the job id and read it again later instead of submitting a second time.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume