---
title: "Why imgd.dev Serves a Blurred Placeholder During Image Moderation"
description: "Explain imgd.dev quarantine, processing blur, terminal review blur, promotion, cache control, and public URL behavior."
slug: blurred-placeholder-image-moderation
date: 2026-08-20
updated: 2026-08-20
last_tested: 2026-08-20
summary: "imgd.dev returns the final public URL immediately, but it does not serve unreviewed recognizable bytes. Processing is temporary; review is terminal."
cluster: Engineering
intent: engineering
sources:
  - title: "imgd.dev agent reference"
    url: https://imgd.dev/llms.txt
  - title: "imgd.dev OpenAPI specification"
    url: https://imgd.dev/openapi.json
---

# Why imgd.dev Serves a Blurred Placeholder During Image Moderation

imgd.dev returns the final public URL as soon as it accepts an upload.
While the status is `processing`, that URL serves a strong blurred placeholder.
**Unreviewed recognizable bytes are not served.**
The temporary `processing` state can change automatically, but terminal `review` does not clear without a person.

Every imgd.dev image is public.
A blurred state is not private storage.
Do not upload private, confidential, or secret material.

## The state model

| State | Public URL behavior | Automatic transition | Cache behavior |
| --- | --- | --- | --- |
| `processing` | Strong blurred JPEG placeholder | Yes | `no-store` |
| `live` | Original or requested transform | No further moderation transition | Public cache policy |
| `review` | Blurred image with a review header | No. This state is terminal. | Public cache policy |
| `blocked` | `404` JSON error | No | `no-store` |
| `error` | `404` JSON error | No | `no-store` |

A caller must not use `review` and `processing` as synonyms.
Only `processing` means that the automated check still has work to do.

## Current implementation facts

A new image follows this path:

1. The upload route validates authentication, quota, size, and media type.
2. It computes the SHA-256 content hash.
3. It writes new bytes under a quarantine object key.
4. It writes image metadata with `status = processing`.
5. It sends a moderation queue message.
6. It returns HTTP `202` with the final image URL.

The public serving route checks the database state before it reads a public object.
For `processing`, it reads the quarantined bytes and applies the strongest supported blur.
It returns JPEG bytes with this header:

```text
X-Imgd-Moderation: processing-blurred
```

The response also uses:

```text
Cache-Control: no-store, no-cache, must-revalidate, max-age=0
Retry-After: 3
```

The processing placeholder never enters the Worker cache.
The test suite checks that no `cache.put` call exists in this branch.

Repository evidence:

- `worker/index.ts`
- `worker/moderation.ts`
- `worker/lib.ts`
- `tests/moderation.test.mjs`
- Git commit `b0d8d093313a5fca0b7eff3ffa15d4982b68bd0b`

## Promotion after the check

The moderation consumer reads the quarantine object and creates the moderation thumbnail.
It then selects `live`, `review`, or `blocked` through the current confirmation rules.

For `live` and `review`, the worker performs these operations:

1. Write the original bytes to the internal public object path.
2. Delete the quarantine object.
3. Store the final status and generated alt text.

A `live` request can serve the original bytes or a requested transform.
A `review` request always applies the review blur before it serves an image.
The response includes:

```text
X-Imgd-Moderation: review-blurred
```

For `blocked`, the worker deletes the quarantine bytes.
The public URL then returns `404` and a contest instruction.

## Measured production facts

This page does not claim a moderation latency percentile or a cache-hit rate.
The current repository records state behavior in code and tests, not a timing sample for this page.

The article therefore separates implementation facts from design rationale.
Do not infer a fixed moderation duration from `Retry-After: 3`.
That header tells a JSON caller when to try another status request.

## Design rationale

The URL must work immediately for documents, pull requests, and agent messages.
A broken URL forces every caller to build a wait-before-embed workflow.

The placeholder design keeps the final URL usable without exposing recognizable unreviewed content.
The processing blur is stronger because no moderation result exists yet.
The review blur is less strong because an automated check already placed the image in a non-destructive hold.

The `no-store` policy is essential during `processing`.
A cached placeholder could remain blurred after the image reaches `live`.
The serving path therefore forbids storage of that temporary response.

The JSON branch supports programs that need state instead of image bytes.
It preserves machine-readable polling without breaking normal image clients.

## Image clients and JSON clients see different processing responses

A normal image client requests the URL without an explicit JSON preference.
On the normal `processing` path, it receives HTTP `200` image bytes for the blurred placeholder.

A status client sends `Accept: application/json` to the same URL.
During `processing`, it receives HTTP `409`:

```json
{
  "error": "moderation_pending",
  "fix": "this image is still clearing moderation; the same url already serves a blurred placeholder to image clients, and sharpens once the check completes",
  "status": "processing"
}
```

The URL and hash do not change when the image reaches `live`.
The response type changes because the stored state changes.

## Practical verification procedure

imgd.dev charges a one-time $1 per GB storage price and has no free tier.
Use an existing funded account and a synthetic public image.

Upload the image:

```sh
curl -X POST https://imgd.dev/v1/upload \
  -H "Authorization: Bearer $IMGD_KEY" \
  -F "file=@public-sample.png"
```

Save the returned `hash` and `url` without storing the key in a log.
Fetch the image response immediately:

```sh
curl -sS -D /tmp/imgd-headers.txt -o /tmp/imgd-body.jpg "$URL"
grep -iE 'HTTP/|content-type|cache-control|x-imgd-moderation' /tmp/imgd-headers.txt
```

If the state is still `processing`, expect an image response and the `processing-blurred` header.
The cache header must include `no-store`.
If moderation already finished, expect the final state response instead.

Request JSON state from the same public URL:

```sh
curl -i -H "Accept: application/json" "$URL"
```

During `processing`, expect HTTP `409`, `Retry-After: 3`, and `moderation_pending`.
After the state changes, the public URL can return image bytes instead.

Check owner metadata:

```sh
curl "https://imgd.dev/v1/images/$HASH" \
  -H "Authorization: Bearer $IMGD_KEY"
```

Stop on `live`, `review`, `blocked`, or `error`.
Do not poll a `review` result because it is terminal.

## Upload recovery rules

- `401`: correct the missing or invalid key before another request.
- `402`: buy storage or delete stored images before another request.
- `413`: resize the image before another request.
- `415`: convert the image or correct its media type before another request.
- `429`: wait before a bounded retry.

Do not retry every `4xx` response.
Read [how actionable API errors help agents recover](/blog/actionable-api-errors-ai-agents/) for the full branch policy.

## Security rules

- Treat every returned URL as public.
- Remove secrets and personal data before upload.
- Keep `$IMGD_KEY` outside URLs, source files, and logs.
- Do not use the processing blur as a privacy control.
- Stop on terminal states.
- Use the human contest path for a review or block that appears wrong.

## When this design fits

This design fits public-image workflows that need a stable URL while moderation runs.
It fits image clients that can accept a temporary blur and agents that can poll the same URL.
imgd.dev does not fit private images, a free trial, video, or full digital-asset management.
Use another service when an image needs access control.

Read [the terminal screenshot moderation incident](/blog/moderate-terminal-screenshots-text-false-positives/) for the confirmation rule.
Read [the moderation thumbnail size decision](/blog/image-moderation-thumbnail-size-cost-accuracy/) for the 768-pixel production edge.

For an agent-led setup, use [`/integrate.md`](/integrate.md).
For a human setup, open [Get started](/#start).
