Skip to content
Foldbun StackDocs
中Get an API keyComing soon

Every image input is an object with exactly one of three members.

Member Value
id A handle returned by POST /v1/images, in the form img_<sha256>.
base64 Standard base64 of a JPEG, PNG or WebP file. A data: prefix is tolerated.
url Reserved. An https:// URL is not supported yet and answers 400 unsupported.
{ "image": { "base64": "/9j/4AAQSkZJRgABAQ..." } }
{ "image": { "id": "img_312c4e3a0ef23fa991baec515316f6b5897d4d534ad7058c57d8563cfcbdc6e2" } }

POST /v1/images stores an image and returns a handle. It is free of charge. Handles avoid sending the same bytes repeatedly, for example when several prompts, refinements or models work on one picture.

The file is sent either as multipart/form-data with the field file, or as the raw request body with Content-Type: image/jpeg, image/png or image/webp.

curl https://api.stack.foldbun.com/v1/images \
-H "Authorization: Bearer $FOLDBUN_API_KEY" \
-H "Content-Type: image/jpeg" \
--data-binary @photo.jpg
{
"id": "img_312c4e3a0ef23fa991baec515316f6b5897d4d534ad7058c57d8563cfcbdc6e2",
"width": 1343,
"height": 1008,
"bytes": 388375,
"contentType": "image/jpeg"
}

The handle is content-addressed: uploading the same picture again returns the same id, with status 200 instead of 201. width and height are the size of the oriented original (EXIF orientation applied), which is the coordinate space of every later call.

A handle is valid only inside the account that uploaded it. Handles of other accounts do not resolve.

The image itself is kept on a best-effort basis and deleted after at most 7 days, counted from the first time the account stored the image. Stack also evicts the least recently used images earlier when storage capacity is needed, so there is no minimum retention. When a call answers 404 image_not_found, upload the image again and repeat the call.

When image storage is temporarily unavailable, a call answers a retryable 503 unavailable with Retry-After.

Property Limit
Formats JPEG, PNG, static WebP
File size At most 20 MiB per upload
Pixels At most 40 megapixels, and 16384 px per side
JSON request body At most 28 MiB, so that a base64 image fits

A file or request body above the size limits is refused with 413 payload_too_large. An unsupported or malformed file, or an image above the pixel limits, is refused with 400 invalid_request. A file that passes these checks but cannot be decoded is refused with 400 invalid_image the first time a model needs it, and the same handle keeps answering 400 invalid_image. Such a call is never charged. When image processing is busy, the answer is a retryable 503 unavailable.

An image given inline as base64 is handled like an upload.

Uploads are sanitized when they arrive: metadata (EXIF, XMP, comments, embedded thumbnails) and any data appended after the image are removed. The pixels are not changed or re-encoded, so a JPEG stays a JPEG, a PNG a PNG and a WebP a WebP.

The image itself is kept for at most 7 days (see Retention). It is never forwarded to a GPU node. Nodes only receive resized copies that Stack generates for each model.

img_<sha256> is the SHA-256 of the sanitized bytes, not of the uploaded file.

All coordinates in requests and responses are pixels of the oriented original: the uploaded image at full size with its EXIF orientation applied. This covers boxes ([x1, y1, x2, y2]), points, polygons and quadrilaterals. The size is returned as image.width and image.height in every response that has an image, and by POST /v1/images. There is no resizing and no short-side limit: the limits are those in Accepted input, up to 40 megapixels and 16384 px per side.

A client that downscales an image before upload gets coordinates in the downscaled pixels. Smaller uploads save upload time and bandwidth; models work on resized copies in any case.

bbox and polygon are always in image pixels. A mask, returned only on request (geometry: ["mask"]), can be smaller than the image. Its mask.size ([height, width]) is the actual size of the mask grid, and a client scales by it: x by image.width / size[1], y by image.height / size[0]. See Post-processing.

options.maskSize on POST /v1/segment and POST /v1/refine sets the long side of the returned mask grid, from 64 to 4096 px, 2048 by default. The grid is never larger than the image. It affects mask output only; polygons are traced at image resolution. A value out of range is refused with 400 invalid_request.