The Upload API accepts design files from customers and creators before they are attached to an order. Every file is validated by inspecting its raw magic bytes — not just its MIME type or extension — to prevent spoofing. Files are stored in Supabase Storage when configured, or written to the local filesystem in development. ADocumentation Index
Fetch the complete documentation index at: https://mintlify.com/PloutusLab/krafta-web/llms.txt
Use this file to discover all available pages before exploring further.
DesignUpload record is created in the database either way, and its UUID is what you pass to the order creation step.
POST /api/upload
Upload a single design file. The request must bemultipart/form-data. Authentication is optional — uploads from anonymous users are scoped to the "anonymous" storage path unless a userId is provided.
Request
Content-Type:multipart/form-data
The design file to upload. Accepted formats: PNG, JPEG, PDF, and SVG. Maximum size is 20 MB (20 × 1,048,576 bytes).
User identifier used to namespace the storage path (
{userId}/{uuid}.ext). Defaults to "anonymous" if omitted.Accepted file types
The server reads the first 8 bytes of every upload and compares them against known magic byte signatures. The declaredContent-Type header is ignored for validation purposes.
| Format | Magic bytes (hex) | Extension | Notes |
|---|---|---|---|
| PNG | 89504E470D0A1A0A | .png | Full 8-byte signature matched. |
| JPEG | FFD8FF | .jpg | First 3 bytes matched; remainder of header may vary by encoder. |
25504446 (%PDF) | .pdf | Stored in the receipts bucket in Supabase; local path uses uploads/{userId}/. | |
| SVG | <svg or <?xml | .svg | Validated as UTF-8 text. Any SVG containing a <script> tag is rejected with 400. |
File size limit
Files larger than 20 MB (20 × 1,024 × 1,024 bytes) are rejected before any storage or database write occurs.Storage behavior
The route checks at startup whetherNEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY are present and do not contain the placeholder strings. The storage path follows the pattern {userId}/{uuid}{ext} in both cases.
| Environment | Storage target | URL format |
|---|---|---|
| Supabase configured | designs bucket (images) or receipts bucket (PDFs) | Internal Supabase path — signed URL generated on demand |
| No Supabase (development) | public/uploads/{userId}/{uuid}{ext} on the local filesystem | /uploads/{userId}/{uuid}{ext} |
file.name is preserved only in the DesignUpload.fileName database field.
Response
Always
true on a successful upload.UUID of the
DesignUpload record created in the database. Pass this value as designUploadId when creating an OrderItem to link the uploaded file to the order.Public-accessible URL (local filesystem) or internal Supabase storage path. For Supabase-backed uploads, generate a signed URL server-side before displaying it to users.
Original file name as submitted by the client.
MIME type resolved from magic byte inspection, not from the client header. Possible values:
image/png, image/jpeg, application/pdf, image/svg+xml.Error responses
| Status | Condition |
|---|---|
400 | No file field present in the form data. |
400 | File size exceeds 20 MB. |
400 | SVG file contains a <script> element. |
400 | File type not recognized (magic bytes do not match PNG, JPEG, PDF, or SVG). |
500 | Storage write or database error. |
DesignUpload model reference
The following fields are written to theDesignUpload table on every successful upload.
| Column | Type | Description |
|---|---|---|
id | String (UUID) | Primary key — returned as designId in the response. |
fileUrl | String | Storage path or public URL. |
fileName | String | Original client-supplied file name. |
fileSize | Int | File size in bytes. |
mimeType | String | Resolved MIME type from magic byte validation. |
magicBytes | String? | First 16 hex characters of the file, stored for audit purposes. |
userId | String | The userId form field value, or "anonymous". |
createdAt | DateTime | Server timestamp at time of upload. |
The
magicBytes column stores only the first 16 hex characters (8 bytes) of the binary content. This is sufficient to verify the file type during any future audit without re-reading the file from storage.