Upload images

post/v1/file/upload

Uploads up to 10 images in one multipart/form-data request and returns a storage key for each. Use it to store reference images — for example a background for outfit compositions — and then pass the returned key wherever an image key is accepted, or display it through the image delivery endpoint at https://stylor.ai/api/v1/image/{key}.

Accepted files

Files are read from every file part in the body regardless of the form field name; non-file fields are ignored. Only the first 10 file parts are read — any further files are silently dropped, not rejected. Each file must:

  • declare one of image/jpeg, image/png, image/webp, image/avif in the part's Content-Type (a part without one is treated as application/octet-stream and rejected)
  • actually be that format (the first bytes are checked against the declared type's signature)
  • be between 1 KB (1,024 bytes) and 10 MB (10,485,760 bytes)

Per-file results

Files are validated and stored independently, so one bad file does not fail the request. The response always carries three keys: files for the stored ones, failures for the rejected ones (each with the original filename and an error), and a summary. The status tells you at a glance how it went: 200 all stored, 207 some stored. When none was stored the response is an error that still carries files, failures and summary, with error: No files were stored. and a status chosen from the per-file reasons: 415 when every file had an unaccepted type or content, 413 when every file was too large, 422 for any other client-side mix, and 500 (Internal server error.) when storage failed. Per-file error values are:

  • Unsupported file type: <type>. Allowed: image/jpeg, image/png, image/webp, image/avif
  • File content does not match declared type <type> — the bytes are not the declared format
  • File too small to validate as <type> — too few bytes arrived to check the type's signature (under 12 for WebP, 8 for PNG and AVIF, 3 for JPEG)
  • File exceeds maximum size of 10 MB — the upload was cut off at 10 MB and discarded
  • File too small. Minimum size is 1024 bytes
  • Failed to process file — storage or record creation failed
  • File stream error — the connection dropped mid-file

Each stored file gets a UUID fileId and a key of the form uploads/<fileId>.<ext>. Pixel dimensions are extracted where possible and are null when they cannot be read.

This endpoint counts one request against your quota however many files it carries.

Private keyapi.v1.image.uploadNo demo

Request body

multipart/form-datarequired
filearray<string (binary)>required

One or more image files. The field name is not significant — any file part is accepted — but file is conventional. Only the first 10 file parts are read.

maxItems: 10

Response

200Every file was stored.
array<UploadedFile>required

Files that were stored.

fileIdstring (uuid)required

Unique id of the stored file. It is the UUID portion of key.

e.g. "6d1e5c0a-2b3f-4a8e-9c7d-0f1e2a3b4c5d"
keystringrequired

Object key of the form uploads/<fileId>.<ext>. Pass it to GET /v1/image/{key} or to any field that accepts an image key.

e.g. "uploads/6d1e5c0a-2b3f-4a8e-9c7d-0f1e2a3b4c5d.jpg"
filenamestringrequired

The original filename after sanitising (path stripped, unsafe characters removed, at most 255 characters). unnamed when none was sent.

maxLength: 255e.g. "background.jpg"
contentTypestringrequired

The declared and verified MIME type.

One ofimage/jpegimage/pngimage/webpimage/avif
e.g. "image/jpeg"
sizeintegerrequired

Size in bytes.

min: 1024max: 10485760e.g. 482113
sha256stringrequired

Hex SHA-256 of the stored bytes, computed as the file streamed in. Compare it with a local hash to confirm the upload arrived intact.

pattern: ^[0-9a-f]{64}$e.g. "9f2b6c1d4e8a7b3c0d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c"
widthintegernullablerequired

Pixel width, or null if it could not be read.

e.g. 1600
heightintegernullablerequired

Pixel height, or null if it could not be read.

e.g. 1067
array<UploadFailure>required

Files that were rejected.

filenamestringrequired

The sanitised original filename.

e.g. "logo.svg"
errorstringrequired

Why the file was rejected. One of the per-file messages listed in the endpoint description.

e.g. "Unsupported file type: image/svg+xml. Allowed: image/jpeg, image/png, image/webp, image/avif"
objectrequired

Counts for the batch.

totalintegerrequired

Files processed (succeeded + failed). At most 10.

e.g. 2
succeededintegerrequired

Number of entries in files.

e.g. 1
failedintegerrequired

Number of entries in failures.

e.g. 1
207Some files were stored and some were rejected. Inspect failures.
array<UploadedFile>required

Files that were stored.

fileIdstring (uuid)required

Unique id of the stored file. It is the UUID portion of key.

e.g. "6d1e5c0a-2b3f-4a8e-9c7d-0f1e2a3b4c5d"
keystringrequired

Object key of the form uploads/<fileId>.<ext>. Pass it to GET /v1/image/{key} or to any field that accepts an image key.

e.g. "uploads/6d1e5c0a-2b3f-4a8e-9c7d-0f1e2a3b4c5d.jpg"
filenamestringrequired

The original filename after sanitising (path stripped, unsafe characters removed, at most 255 characters). unnamed when none was sent.

maxLength: 255e.g. "background.jpg"
contentTypestringrequired

The declared and verified MIME type.

One ofimage/jpegimage/pngimage/webpimage/avif
e.g. "image/jpeg"
sizeintegerrequired

Size in bytes.

min: 1024max: 10485760e.g. 482113
sha256stringrequired

Hex SHA-256 of the stored bytes, computed as the file streamed in. Compare it with a local hash to confirm the upload arrived intact.

pattern: ^[0-9a-f]{64}$e.g. "9f2b6c1d4e8a7b3c0d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c"
widthintegernullablerequired

Pixel width, or null if it could not be read.

e.g. 1600
heightintegernullablerequired

Pixel height, or null if it could not be read.

e.g. 1067
array<UploadFailure>required

Files that were rejected.

filenamestringrequired

The sanitised original filename.

e.g. "logo.svg"
errorstringrequired

Why the file was rejected. One of the per-file messages listed in the endpoint description.

e.g. "Unsupported file type: image/svg+xml. Allowed: image/jpeg, image/png, image/webp, image/avif"
objectrequired

Counts for the batch.

totalintegerrequired

Files processed (succeeded + failed). At most 10.

e.g. 2
succeededintegerrequired

Number of entries in files.

e.g. 1
failedintegerrequired

Number of entries in failures.

e.g. 1

Errors

24

Every error is JSON with a single error string unless noted. Match on the status code; the message is for humans.

400
Failed to parse upload

The multipart body could not be parsed — it is malformed or truncated, or the multipart/form-data header has no boundary parameter.

400
No files were stored.

Every file's stream broke off mid-upload (File stream error). The body also carries files, failures and summary.

413
No files were stored.

Every file exceeded 10 MB. The body also carries files (empty), failures and summary; read failures[].error.

{
  "error": "No files were stored.",
  "files": [],
  "failures": [
    {
      "filename": "huge.png",
      "error": "File exceeds maximum size of 10 MB"
    }
  ],
  "summary": {
    "total": 1,
    "succeeded": 0,
    "failed": 1
  }
}
415
Content-Type must be multipart/form-data

The request's Content-Type header is not multipart/form-data.

415
No files were stored.

Every file declared an unaccepted type, or its bytes did not match the declared type. The body also carries files (empty), failures and summary; read failures[].error.

{
  "error": "No files were stored.",
  "files": [],
  "failures": [
    {
      "filename": "notes.txt",
      "error": "Unsupported file type: text/plain. Allowed: image/jpeg, image/png, image/webp, image/avif"
    }
  ],
  "summary": {
    "total": 1,
    "succeeded": 0,
    "failed": 1
  }
}
422
No files provided

The request has no body, or the body parsed but contained no file parts.

422
No files were stored.

Every file was rejected, for differing client-side reasons or because a file was under 1 KB. The body also carries files (empty), failures and summary; read failures[].error.

curl -X POST "https://stylor.ai/api/v1/file/upload" \
  -H "Authorization: Bearer $STYLOR_API_KEY" \
  -F "file=@./catalogue.csv"
{
  "files": [
    {
      "fileId": "6d1e5c0a-2b3f-4a8e-9c7d-0f1e2a3b4c5d",
      "key": "uploads/6d1e5c0a-2b3f-4a8e-9c7d-0f1e2a3b4c5d.jpg",
      "filename": "background.jpg",
      "contentType": "image/jpeg",
      "size": 482113,
      "sha256": "9f2b6c1d4e8a7b3c0d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c",
      "width": 1600,
      "height": 1067
    }
  ],
  "failures": [],
  "summary": {
    "total": 1,
    "succeeded": 1,
    "failed": 0
  }
}