Files
Effects take images as input. There are two ways to provide one.
Upload with POST /files
Section titled “Upload with POST /files”curl https://api.moan.hot/v1/files \ -H "Authorization: Bearer $MOANHOT_API_KEY" \ -F "file=@photo.jpg"{ "id": "file_3kq9c0m2x7z1b8v4n6p5r2t0", "object": "file", "bytes": 482113, "mime_type": "image/jpeg", "width": 1080, "height": 1440, "sha256": "9b1c...", "created_at": "2026-10-20T12:00:00Z", "expires_at": "2026-10-21T12:00:00Z"}Use the id as the value of an input slot in POST /generations. A file expires 24 hours after upload and can be used by any number of generations until then. GET /files/{id} returns its metadata.
Upload when the image lives on your user’s device or behind authentication. The file is checked at upload, so a bad image fails fast, before you try to create anything.
Pass a URL
Section titled “Pass a URL”Instead of a file_ id, put a public https:// URL in inputs:
{ "inputs": { "image": "https://cdn.example.com/u/123.jpg" } }We fetch it once, at create. The URL must answer directly with the image bytes, without a login.
Limits
Section titled “Limits”| Formats | JPEG, PNG, WebP |
| Size | 20 MB at most |
| Dimensions | 8192 px at most on the long side. Each effect sets a minimum short side in inputs[].min_short_side_px |
| Lifetime | 24 hours for uploads |
Errors
Section titled “Errors”| Code | Status | When |
|---|---|---|
invalid_request |
400 | The body is not multipart/form-data or has no file part |
image_too_large |
413 | Over 20 MB or over 8192 px on the long side |
unsupported_media_type |
415 | HEIC, GIF or another format. Convert to JPEG, PNG or WebP |
image_unreadable |
422 | The bytes do not decode, the image is too small, or a URL fetch failed |
file_not_found |
404 | Unknown id, another account’s or mode’s file, or past its 24 hours |
For URL inputs, the image_* codes come from POST /generations instead.