Ruby Net::HTTP: POST /v1/videos with callback_url, for a Sora port

A 15-line Net::HTTP submit for /v1/videos that sends an HTTPS callback_url and an Idempotency-Key, checks for 202, and stores the id before any callback.

4 min readSume
All posts

Post to /v1/videos with a callback_url and an Idempotency-Key, require a 202, and write the returned id to your database before you do anything else. OpenAI's deprecations page lists the Videos API and the sora-2 model ids as removed on September 24, 2026, so a Ruby job that posted there needs a new target, and Sume's /v1/videos is a bare-object, async route that fits the same submit-then-wait shape.

What the response and callback look like

The 202 body is a plain object with id, polling_url, status and model. Status values on this route are pending, in_progress, completed, failed and cancelled. The callback carries Sume's standard job webhook envelope, not an OpenRouter-style event, and is signed with x-sume-webhook-signature.

Submit and callback facts for POST /v1/videos (Sume docs, read 2026-10-07)
ItemValue
Success status202
callback_urlHTTPS only
Eventsjob.completed, job.failed, job.canceled
Signature headerx-sume-webhook-signature: sume-v1=...
Retry safetyIdempotency-Key; a replay returns the original job

The submit

Run it with SUME_API_KEY set. The prompt, model and hooks.example.com URL are placeholders. abort on anything but 202 keeps a failed submit from storing a bad id.

require "json"
require "net/http"
require "uri"

base = URI(ENV.fetch("SUME_BASE", "https://api.sume.com"))
http = Net::HTTP.new(base.host, base.port)
http.use_ssl = base.scheme == "https"
req = Net::HTTP::Post.new("/v1/videos", "Authorization" => "Bearer #{ENV.fetch('SUME_API_KEY')}",
                          "Content-Type" => "application/json", "Idempotency-Key" => "sora-port-0001")
req.body = JSON.generate(model: "seedance-2.5", prompt: "paper boat on a rain puddle", duration: 8,
                         resolution: "720p", aspect_ratio: "9:16", callback_url: "https://hooks.example.com/sume")
res = http.request(req)
abort "submit failed: #{res.code}" unless res.code == "202"
job = JSON.parse(res.body)
puts job["id"], job["polling_url"] # store the id first, then wait for the callback

What a callback does not replace

Delivery is at least once, with up to 10 attempts, so your receiver should dedupe on job_id. Keep polling_url as a fallback for a delivery that never arrives; the docs say a webhook is a delivery optimization, not your only recovery path.

Nothing here ports Sora prompts or settings. Sume does not accept seed or size on this route, and a model's duration and resolution come from GET /v1/videos/models, so expect to adjust the body, not just the URL.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume