uncloth.app Guides

How to add adult photo filters to your app

Adding an adult photo filter to a product looks like an image problem and turns out to be a plumbing problem. The model work is bought in a single call. What consumes the sprint is everything around it: uploads that time out, jobs that finish while nobody is listening, retries that quietly bill you twice, and a support queue asking where a result went. This guide walks through the integration end to end and points out the places teams reliably lose time.

What the integration actually consists of

A photo transformation API is asynchronous by nature. Generation takes long enough that holding an HTTP connection open for it is a bad idea: mobile networks drop, load balancers cut idle connections, and your own request timeouts start fighting you. So the shape is always the same — you submit work, you receive an identifier, and you collect the result later.

  1. Submit the photo and the preset you want, and get back a job identifier.
  2. Track the job, either by polling or by subscribing to events.
  3. Fetch the finished image when the job reports success.
  4. Handle the failure and retry paths, which is where most of the real work lives.

Step one: submitting the job

The submission is a multipart request carrying the image, the preset identifier, and a confirmation that you have the rights and consent to process the photo. Send an idempotency key with it. This single header is the difference between a clean integration and a support problem, and it costs nothing to add.

curl -X POST https://api.example/api/v1/jobs \
  -H "X-API-Key: $API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -F image=@photo.jpg \
  -F feature=bikini \
  -F consent=confirmed

The response carries the job identifier and its initial status. Persist that identifier against your own user or session record immediately, before you do anything else. If your process dies in the next second, that row is the only thing that lets you find the work again.

Step two: tracking progress

There are two ways to follow a job, and mature integrations use both. A WebSocket connection gives you live status changes and progress, which is what you want driving a progress bar in the interface. Polling the job endpoint is the fallback that makes the system correct rather than merely pleasant: sockets drop, phones sleep, and a poll every few seconds costs almost nothing.

Treat the WebSocket as an optimisation and polling as the source of truth. Teams that invert this end up with jobs that finished perfectly on the server and appear stuck forever in the client, because the one event that mattered arrived while the socket was reconnecting.

Step three: collecting the result

When the job reports success it carries a list of outputs. Each output is fetched with the same API key that created the job, so results are never publicly addressable and never guessable from a URL. Download the image to your own storage on receipt if your product needs to show it later — an image API is a processing service, not a photo library, and results are not retained indefinitely.

Step four: failures, retries and the double-billing trap

The interesting failure is not the one where the API returns an error. It is the ambiguous one: your request went out, the network dropped, and you have no idea whether the job was created. Retry it naively and you have paid twice and produced two results for one user action.

This is what the idempotency key solves. Repeat the request with the same key and you get the original job back rather than a new one, no matter how many times the retry fires. Generate the key when the user action happens, store it with your job record, and reuse it for every retry of that same action — not a fresh one per attempt, which defeats the entire mechanism.

Where integrations usually stall

  • Validating uploads only on the client. Check the file server-side by content, not by extension, before spending an API call on it.
  • Building the whole flow synchronously, then discovering that mobile clients drop connections long before generation finishes.
  • Treating the WebSocket as the only progress channel, with no polling fallback.
  • Storing nothing locally, so a restart loses the mapping between your users and in-flight jobs.
  • Forgetting that results are transient and expecting the API to act as permanent storage.

A sensible order of work

Get one photo through the whole path with curl before you write any application code — submit, poll, download. Then wire the same four calls into your backend with persistence. Add the WebSocket last, purely as a progress-bar improvement on top of a system that already works without it. In that order the integration is a day of work; in the reverse order it is a fortnight of debugging.

Frequently asked

How long does a request take?

Generation is asynchronous and completes in the background. Design the interface around a progress state rather than a blocking call, and the exact duration stops mattering to your architecture.

Do I need a WebSocket to use the API?

No. Everything works over REST alone. The WebSocket exists to make progress feel live; polling the job endpoint gives you the same information.

What happens if I submit the same request twice?

With the same idempotency key you receive the original job back, and no second generation is produced or billed.

Build on uncloth.app

One REST endpoint, eleven presets, results returned to your backend. Tell us what you are building and we will send access details.

Request access

All guides