> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nsfw-api.net/llms.txt
> Use this file to discover all available pages before exploring further.

# Private files

> Upload private media and attach it safely to one generation job.

Image, video, and audio inputs may be public HTTPS URLs or private files uploaded directly to our private storage.

## Upload flow

<Steps>
  <Step title="Create an upload">
    Call `POST /v1/uploads` with `purpose`, `content_type`, and the exact `size_bytes`. The response contains a `media_id`, a single-use `upload_url`, and required headers.
  </Step>

  <Step title="Upload the bytes">
    Send a `PUT` request directly to `upload_url` using the returned `required_headers`. The bytes do not pass through the generation API.
  </Step>

  <Step title="Complete the upload">
    Call `POST /v1/uploads/{media_id}/complete`. We verify the stored object's content type, size, and hash before it can be used.
  </Step>

  <Step title="Use the media ID">
    Pass the ready `media_id` in a supported media input field.
  </Step>
</Steps>

```bash theme={null}
curl https://nsfw-api.net/v1/images/edits \
  -H "Authorization: Bearer $NSFW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"qwen-image-edit-spicy","image":"media_...","prompt":"Replace the background with a forest"}'
```

## Limits

| Purpose | Maximum size |
| ------- | -----------: |
| Image   |       10 MiB |
| Video   |       50 MiB |
| Audio   |       15 MiB |

Only content types listed by `POST /v1/uploads` are accepted.

A `media_id` can be attached to exactly one job. Reusing it returns `400 invalid_media_input`.

An account may hold up to 5 GiB of live inputs. Incomplete uploads count against the quota at the maximum size for their purpose. When the quota is exceeded, `POST /v1/uploads` returns `413 storage_quota_exceeded`.

Uploads that are never attached expire after 24 hours.
