Bash for loop with curl: one API request per line

Loop over a file with while IFS= read -r, build each JSON body with jq --arg, send it with curl --fail-with-body, and pace it under the API's rate limit.

5 min readSume
All posts

To send one curl request per item in bash, run curl inside a loop: for id in a b c; do curl ...; done for a short list, or while IFS= read -r line; do ...; done < file.txt for a file with one item per line. Build each JSON body with jq -n --arg instead of pasting text into quotes, and send it with curl --fail-with-body so a failed request shows up in your script.

The shell, jq and curl facts come from the Bash manual (Looping Constructs, Word Splitting, Builtins), the jq manual and the curl man page. The example API is Sume's, from Authentication, Video Generation and Errors and rate limits. All were read on 2026-09-29.

Why not loop with for line in $(cat file)?

A for loop runs its commands once for each word in the expanded list. An unquoted $(cat file) goes through word splitting, and when IFS is unset, space, tab and newline all split. A prompt like a red bike becomes three requests. A for loop is fine for ids without spaces; for lines, use while read:

  • IFS= for the read: when IFS is null, no word splitting occurs, so the line stays whole.
  • -r: backslash is not an escape character and stays part of the line.
  • || [ -n "$line" ]: read exits non-zero at end-of-file, which would drop a last line with no trailing newline.

How do I loop over a file of prompts with curl?

This script reads prompts.txt, starts one Sume video job per line, and saves each job id. Keep the key in SUME_API_KEY, never in the script; curl bearer token covers the Authorization header itself.

n=0
while IFS= read -r prompt || [ -n "$prompt" ]; do
  n=$((n + 1))
  body=$(jq -n --arg p "$prompt" \
    '{model: "sume/auto", prompt: $p, aspect_ratio: "9:16", duration: 5}')
  if resp=$(curl -sS --fail-with-body -X POST "https://api.sume.com/v1/videos" \
      -H "Authorization: Bearer $SUME_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: batch-2026-09-29-line-$n" \
      -d "$body"); then
    echo "$resp" | jq -r '.id' >> jobs.txt
  else
    echo "line $n failed: $resp" >&2
  fi
  sleep 1
done < prompts.txt

What does each flag do?

Each part of the script, as the jq and curl docs describe it:

  • jq -n --arg p "$prompt": -n builds JSON from scratch without reading input, and --arg passes the value in as a string, so quotes and backslashes in a prompt can't break the body. A broken body is a 400 invalid_request; see 400 Bad Request invalid JSON.
  • --fail-with-body: curl returns error 22 on an HTTP code of 400 or greater and still outputs the body, so you keep the error message. By default curl doesn't treat HTTP codes as failures. The option was added in curl 7.76.0.
  • -sS: hides the progress meter but still shows errors.
  • Idempotency-Key: one fixed key per line. If you rerun the script after a crash, the same line with the same body returns the original job on /v1/videos instead of a second one. Reuse a key only for the same body: change the batch name when you edit the file or want new jobs.
  • jq -r '.id': writes the string without JSON quotes.

How fast can a bash loop call the API?

As fast as the API's rate limit allows. Sume counts requests per key per minute, with writes (creates) and reads (status polls) in separate budgets. sleep 1 keeps the loop at about 60 creates a minute. For a faster loop, read the ratelimit-remaining header, and on a 429 wait for retry-after.

From Authentication, read 2026-09-29.
PlanWrites per minuteReads per minute
Free1204800
Pro30012000
Startup60024000
Scale120048000

Should the loop wait for each job to finish?

No. Each create answers at once with a job id and a polling_url, and the video is generated asynchronously. Send the whole file first, then poll the saved ids in a second loop with GET /v1/videos/{id} until each status is no longer pending or in_progress. curl's --retry also exists, but it treats every 429 and 503 alike; a 429 queue_full means no new paid job until an existing one finishes or is canceled, so read the saved error body instead. For one scheduled call rather than a batch, see Crontab curl.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume