DEV Community

LION ZHANL
LION ZHANL

Posted on

From Upload to Deletion: An Auditable Lifecycle for AI Image Tasks

An AI image request should not be a black box between an upload button and a result URL. If a product cannot explain what happened to one request without opening the user's image, it has an observability problem.

This article describes a practical task lifecycle for a consumer face-swap service. The same pattern applies to background removal, restoration, avatar generation, and other asynchronous image workflows.

1. Validate before storage

File validation should happen before a request enters the generation queue. At minimum, check:

  • the actual file signature rather than trusting the extension;
  • the decoded media type;
  • pixel dimensions and total byte size;
  • whether the image contains a decodable frame; and
  • whether the request satisfies the product's consent and acceptable-use controls.

The validation response should use stable error categories. IMAGE_TOO_LARGE and UNSUPPORTED_MEDIA_TYPE are more useful than forwarding an object-storage or model-provider exception to the browser.

Do not retain rejected uploads merely to simplify analytics. Record the validation category, timestamp, and non-sensitive file properties instead.

2. Create an internal task before calling the provider

The application should create its own task record before making an external generation request. A minimal record includes:

task_id
user_id or anonymous session id
operation
created_at
status
provider
provider_task_id
input_object_ids
output_object_ids
credits_reserved
credits_charged
error_category
expires_at
Enter fullscreen mode Exit fullscreen mode

The internal identifier is the product's source of truth. A provider task ID is an integration detail and should not become the only handle available to support staff or users.

This separation also makes provider migration possible. The public status endpoint can keep a stable contract even if the service changes processing vendors.

3. Treat provider processing as an asynchronous state machine

An external image API usually accepts a job and returns before processing finishes. The application therefore needs explicit states such as:

created -> submitted -> processing -> succeeded
                               \-> failed
                               \-> timed_out
                               \-> cancelled
Enter fullscreen mode Exit fullscreen mode

State transitions should be idempotent. A repeated webhook or poll response must not charge credits twice, create duplicate output rows, or move a completed task back to processing.

Store the raw provider response only when it is genuinely required for debugging and can be sanitized. In most cases, a normalized status, provider request ID, response timestamp, and bounded error summary are enough.

4. Separate object storage from task records

The database should identify inputs and outputs without embedding image binaries in task rows. Object storage is better suited to large files, signed access URLs, lifecycle expiration, and deletion.

Use non-guessable object keys and private buckets. A result URL should expire or be protected by an authenticated application route. Publicly readable bucket paths turn an application-layer authorization decision into a permanent storage-layer leak.

The task record should preserve enough metadata to answer operational questions:

  • When was the input stored?
  • Which provider request used it?
  • When was the output created?
  • When should each object expire?
  • Was deletion requested and completed?

5. Make polling bounded and observable

Client polling should use increasing intervals and a terminal timeout. Polling every second forever wastes browser, server, and provider resources.

A status response should expose only what the interface needs: task status, progress when meaningful, a safe result reference, a stable error category, and retry guidance. Provider credentials, storage keys, stack traces, and full request payloads never belong in the client response.

On the server, track task age and the time since the last provider update. Those two values help distinguish a slow task from a broken poller.

6. Charge credits exactly once

Credit handling belongs in a transaction or another atomic mechanism. Reserve credits before submission, then either finalize the charge on an accepted task or release the reservation when submission fails.

For terminal provider failures, the refund rule should be explicit and testable. A task retry must receive a new attempt identifier so support can distinguish a retry from a duplicate callback.

7. Define deletion as a verifiable operation

"We delete images quickly" is not an implementation rule. A useful retention policy names:

  • which input and output objects are covered;
  • the normal retention duration;
  • whether users can request earlier deletion;
  • whether provider-side copies have a separate policy;
  • how deletion failures are retried; and
  • what non-image task metadata remains for billing or abuse prevention.

A deletion request should create an auditable event without preserving the deleted image. Record the object identifier, request time, completion time, and outcome.

8. Give users a task-level explanation

Users do not need an infrastructure diagram, but they should be able to see whether a request is queued, processing, complete, failed, or expired. Product documentation should distinguish observed application behavior from assumptions about an external provider.

For a concrete walkthrough of an upload, PiAPI processing request, task record, storage path, and deletion boundary, see the AI face-swap photo lifecycle guide.

The engineering standard is straightforward: one task should be reconstructable from sanitized records, while the original image remains private. When that is true, support, billing, retention, and incident response all become easier to verify.

Top comments (0)