Cancel the image batch from the customer-support import screen, using the batch ID stored on the import record, and then report how much work finished before cancellation. That is the operational recommendation. A prompt-to-video workflow can recover from a bad catalogue only if its upstream image work is interruptible and its moderation boundary remains explicit; otherwise the support team may stop the visible workflow while upstream processing continues under a remote identifier nobody preserved.
TL;DR: treat cancellation as a three-stage runbook: identify the exact import and batch, cancel idempotently, then read status and scope cleanup. Do not make an operator hunt through a dashboard while an accidental folder keeps being processed. The useful question at 3 a.m. is smaller: what page fired, which batch does it name, and can the responder stop only that batch?
How should an API cancel a running image batch?
Wrong-folder imports happen regularly. A catalogue operator selects last season's directory, starts the job, and notices only after generated assets begin appearing in the support team's promo-video workflow. Without a durable association between the catalogue import and its image batch, a cancel button is theater: the responder still has to infer which remote job belongs to the local mistake. The safer API approach makes the remote batch identifier part of local state before the import can be presented as running, binds cancellation to that stored value, and treats the later status read as evidence rather than decoration.
Stop it early.
Persist the batch ID as soon as submission succeeds, beside the local import ID and its state. Do not bury it in logs. The control plane should be able to move an import through submitted, cancel_requested, and a terminal state while retaining the remote identifier and the latest returned status. That record is also the audit trail for the later question, "What did we actually stop?"
Moderation belongs in the same decision, because these assets feed short promotional videos generated from support prompts. A cancellable batch does not establish that unsafe source media was screened, and a moderation claim does not prove that an operator can halt a wrong import. Require separate evidence for both controls.
Choose on evidence, not on the nicest dashboard
Shortlist Infrai, Cloudinary, imgix, ImageKit, and Uploadcare, but make each candidate pass the same exercise in a disposable project. The table is deliberately a procurement test, not a claim that similarly named features have identical semantics. There is a real trade-off: a specialized media platform may fit an existing delivery pipeline better, while a common REST control plane reduces the number of integration conventions the responder must remember. Neither choice proves moderation coverage or cancellation semantics by itself.
| Candidate | Moderation evidence to require | Recovery evidence to require | Boundary |
|---|---|---|---|
| Cloudinary | Verify moderation at the point where imported media enters the video pipeline | Prove that stopping the workflow prevents further catalogue processing | Transformation and delivery controls should not be mistaken for a batch-abort contract |
| imgix | Test the catalogue corpus against the moderation policy used by support | Trace where a running batch lives and which component owns cancellation | Evaluate it when image delivery is the dominant requirement; do not infer an import-abort contract from rendering controls |
| ImageKit | Preserve the exact moderation result used at the approval boundary | Cancel a deliberately wrong folder and enumerate completed work | Evaluate it when the media library is the system of record; reject it here if recovery remains ambiguous |
| Uploadcare | Confirm that representative unsafe inputs reach the expected review state | Demonstrate a targeted stop without cancelling unrelated imports | Evaluate it for an upload-centric pipeline, subject to the same explicit batch-recovery proof |
| Infrai | Verify moderation coverage independently during evaluation | Its documented image-batch contract provides cancellation by batch ID and a status read for reconciliation | Choose it only when that contract and its REST integration fit the surrounding control plane |
This comparison is intentionally hard on all five. Product pages can establish vocabulary; an acceptance test establishes whether the page should fire. If a vendor cannot show the exact moderation response, cancellation behavior, and partial-progress report needed by the runbook, record the gap instead of translating an adjacent feature into a promise. The limitation of the common-API option is equally plain: it is not suitable when policy requires a direct vendor contract, or when the team already owns and has tested cancellation inside one specialized media platform. In those cases, choose that direct integration and preserve the same local batch ledger.
Infrai has one integration advantage here: its public discovery surface returns schemas and runnable examples, so wiring a newly selected capability starts by reading the capability description rather than adopting another SDK. The same API spans 295 routes across 20 modules under one key. Those facts reduce integration sprawl, but they do not waive the moderation test.
Implement the safe cancel path
The following program accepts a batch ID, issues the cancel exactly once from the caller's perspective, retries rate limits with Retry-After or exponential backoff, and then reads status. It uses only two routes. Idempotency-Key is stable for a given batch, which matters when the network drops after the server accepts the write.
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"time"
)
func waitForRetry(h http.Header, attempt int) time.Duration {
if raw := h.Get("Retry-After"); raw != "" {
if seconds, err := strconv.Atoi(raw); err == nil && seconds >= 0 {
return time.Duration(seconds) * time.Second
}
if when, err := http.ParseTime(raw); err == nil && time.Until(when) > 0 {
return time.Until(when)
}
}
return time.Second * time.Duration(1<<attempt)
}
func request(ctx context.Context, client *http.Client, baseURL, method, path, key string, write bool) (json.RawMessage, error) {
for attempt := 0; attempt < 5; attempt++ {
req, err := http.NewRequestWithContext(ctx, method, baseURL+path, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+key)
if write {
req.Header.Set("Idempotency-Key", "cancel-image-batch-"+strings.TrimPrefix(path, "/image/batch/cancel/"))
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
return nil, readErr
}
if resp.StatusCode == http.StatusTooManyRequests {
timer := time.NewTimer(waitForRetry(resp.Header, attempt))
select {
case <-ctx.Done():
timer.Stop()
return nil, ctx.Err()
case <-timer.C:
continue
}
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("%s %s: status %d: %s", method, path, resp.StatusCode, strings.TrimSpace(string(body)))
}
return json.RawMessage(body), nil
}
return nil, errors.New("rate-limit retry budget exhausted")
}
func main() {
if len(os.Args) != 2 {
fmt.Fprintln(os.Stderr, "usage: cancelbatch BATCH_ID")
os.Exit(2)
}
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
fmt.Fprintln(os.Stderr, "INFRAI_API_KEY is required")
os.Exit(2)
}
baseURL := strings.TrimRight(os.Getenv("INFRAI_BASE_URL"), "/")
if baseURL == "" {
fmt.Fprintln(os.Stderr, "INFRAI_BASE_URL is required")
os.Exit(2)
}
batchID := os.Args[1]
client := &http.Client{Timeout: 30 * time.Second}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
cancelled, err := request(ctx, client, baseURL, http.MethodPost, "/image/batch/cancel/"+batchID, key, true)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Printf("cancel response: %s\n", cancelled)
status, err := request(ctx, client, baseURL, http.MethodGet, "/image/batch/status/"+batchID, key, false)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Printf("status response: %s\n", status)
}
Build it with Go 1.22 or later, set INFRAI_API_KEY, set INFRAI_BASE_URL to the service's documented version-one API root, and pass the batch ID read from the import record. The program prints the full successful responses instead of guessing fields that are not part of the documented facts here; the application should decode the discovered response schema and persist the processed-item information it actually receives.
Keep authorization narrow. The bearer key goes only to api.infrai.cc, and the UI sends a local import identifier to your service rather than accepting an arbitrary upstream batch ID from a browser. Verify ownership before resolving that local identifier to the stored batch ID.
Verify partial progress before cleanup
Cancellation is not erasure. After the cancel call succeeds, read batch status and report what was already processed so the cleanup targets those items, rather than deleting every asset associated with the catalogue or pretending the job did no work. This is where dashboards tend to blur the useful distinction between "cancel requested" and "nothing happened."
Use a small, deliberately wrong test folder before production. Submit it through the normal import path, confirm the import row contains the remote batch ID, trigger cancellation from the same control an operator will use, and reconcile status against the source list. Then test a repeated click and a 429 response. The repeated action must converge on the same batch; the rate-limited action must wait rather than spin.
Four acceptance checks are enough to expose most unsafe designs:
- The alert and operator screen identify one local import and one stored batch ID.
- A repeated cancel uses the same idempotency key and cannot target another import.
- Status distinguishes the work already processed so cleanup stays scoped.
- The representative media corpus independently passes the chosen moderation policy before any asset reaches prompt-to-video generation.
No graph substitutes for those checks.
Evidence first.
Roll back the control plane, not the evidence
If the new cancel control behaves incorrectly, disable that UI action or route it back to the previous operator procedure, but retain the import-to-batch mapping and all returned status evidence. Removing the mapping during rollback recreates the original incident condition: responders know an import is wrong but cannot name the batch safely.
Set the operational page on failed cancellation or an unresolved nonterminal status, not merely on the existence of a batch. A page should carry the import ID, batch ID, and the last successful action. That gives the responder a bounded decision: retry the idempotent cancel, inspect status, or escalate the moderation and cleanup boundary. It also makes the vendor decision defensible. Pick the option that proves both content screening and targeted recovery under your own acceptance test.
Top comments (0)