X Enterprises
fastify-ximagepipeline

POST /image-pipeline/upload

Upload a file for async processing — validates, stages to R2, creates a job queue entry, and returns a jobId immediately.

POST /image-pipeline/upload

Accepts a multipart form upload, validates the file (MIME type, size, sourceType), uploads it to the R2 staging prefix, creates a MediaQueue row with PENDING status, and returns a jobId immediately. Processing happens asynchronously in the background worker.

@fastify/multipart must be registered before this plugin.

Request

Method: POST
Path: /image-pipeline/upload
Content-Type: multipart/form-data

FieldTypeRequiredDescription
filebinaryYesImage file (JPEG, PNG, WebP, or GIF by default)
sourceTypestringYesOne of the configured source types (e.g. avatar, gallery)
sourceIdstringYesIdentifier for the owning resource (e.g. user ID, post ID)

Response

202 Accepted

{
  "jobId": "clx1234abcd",
  "message": "File uploaded. Processing started.",
  "statusUrl": "/image-pipeline/status/clx1234abcd"
}

Throws

ErrorHTTPCause
No file provided400Request has no file field
sourceType and sourceId are required400Missing form fields
Unknown sourceType: {type}400sourceType not in variant presets
File type {mime} not allowed400MIME type not in allowedMimeTypes
File too large. Maximum size: {n}MB400File exceeds maxFileSize
Failed to upload file to storage500R2 upload error
Failed to create processing job500Database error creating the job

Examples

cURL upload

curl -X POST http://localhost:3000/image-pipeline/upload \
  -F "file=@/path/to/photo.jpg" \
  -F "sourceType=avatar" \
  -F "sourceId=user-abc123"

Fastify route that proxies a user avatar upload

fastify.post("/users/:id/avatar", async (request, reply) => {
  const data = await request.file();

  const form = new FormData();
  form.append("file", data.file, { filename: data.filename, contentType: data.mimetype });
  form.append("sourceType", "avatar");
  form.append("sourceId", request.params.id);

  const response = await fetch("http://localhost:3000/image-pipeline/upload", {
    method: "POST",
    body: form,
  });

  const { jobId, statusUrl } = await response.json();

  // Store jobId and poll statusUrl until COMPLETE
  return { jobId, statusUrl };
});

See Also

AI Context

package: "@xenterprises/fastify-ximagepipeline"
route: POST /image-pipeline/upload (multipart)
use-when: Upload an image file for async processing — returns jobId immediately; worker processes in background
fields: file (multipart), sourceType (string), sourceId (string)
returns: { jobId, status: "PENDING" }
Copyright © 2026