File Uploads

File uploads in Brandmachine use REST endpoints with multipart/form-data, separate from the GraphQL endpoint. Each kind of resource has its own upload endpoint (product studio inputs, batch images, wardrobe images, brand assets and so on) rather than one generic upload.

Authentication

All upload endpoints accept the same API token as the GraphQL endpoint:

Authorization: Bearer YOUR_API_TOKEN

See Authentication for how to generate a token. None of the upload endpoints is blocked for API tokens.

Upload Endpoints

All endpoints take POST requests on https://production.api.brandmachine.shop. Replace the :id placeholders in the path with the UUID of the record. The Form fields column lists the multipart fields; fields in italics are optional.

Product Studio

EndpointForm fieldsPurpose
/api/product-studios/:studioId/inputs/uploadfile, viewType, titleAdd a product photo to a Product Studio session. viewType is front, back, other or cropped. A session holds at most 15 inputs.
/api/product-studio/platesperspectiveId, file, kindUpload a plate or guide image for a Product Studio perspective. kind is plate (the default) or guide. Returns key and url.

Campaigns and Batches

EndpointForm fieldsPurpose
/api/campaign-images/:campaignImageId/products/uploadfile, productTitle, productDescriptionAdd a product reference image to a campaign image
/api/campaign-images/:campaignImageId/guidance/uploadfileSet a guidance image on a campaign image
/api/batches/:batchId/images/uploadfile, viewTypeAdd an input image to a batch. When viewType is missing or not a known value, the view is detected from the image.
/api/batches/:batchId/reference-sheet/uploadfileUpload a stylist's looks sheet for a batch. The looks read from it appear on the batch as referenceSheetLooks.

Batch image uploads are refused once the batch is running or completed.

Wardrobe

EndpointForm fieldsPurpose
/api/wardrobe-items/:wardrobeItemId/images/uploadfile, viewTypeAdd a photo to a wardrobe item. An item holds at most 8 images.

Brand Assets (Media Planner)

EndpointForm fieldsPurpose
/api/brand-assets/uploadfile, titleUpload a brand asset such as a logo. Must be a PNG.
/api/brand-assets/:brandAssetId/dark-ground-version/uploadfileUpload the version of a brand asset for dark backgrounds. Must be a PNG with the same aspect ratio as the original (within 1%).

Fashion Models

EndpointForm fieldsPurpose
/api/fashion-models/uploadfile, genderStart a casting from a reference image. gender is male, female or other.
/api/fashion-models/upload-existingfile, modelName, gender, setCardFileAdd an existing model directly, without casting

Designer

EndpointForm fieldsPurpose
/api/designer-instances/resourcesdesignerInstanceId, file, text, labelAdd an inspiration resource to a design. Send a file, a text, or both. A text-only resource needs a label; for an image the label is written automatically when you leave it out.
/api/designer/annotation-overlayssourceRevisionId, fileUpload an annotation overlay drawn on a revision. Returns imageKey and imageUrl.
/api/size-charts/importfileImport size charts from a JSON file in Brandmachine's size chart import format. Add ?dryRun=true to the URL to check the file without importing anything. Contact support for the file format.

Patterns

EndpointForm fieldsPurpose
/api/patterns/uploadfile, patternId, name, repeatHorizontal, repeatVerticalUpload a finished pattern image. Without patternId a new pattern is created, and name is required.
/api/patterns/referencespatternId, label, fileAdd an inspiration reference to a pattern

Product Video

EndpointForm fieldsPurpose
/api/product-videos/:videoId/uploadfile, productTitleUpload the product image a product video is generated from

Edited Images

EndpointForm fieldsPurpose
/api/images/upload-editedfile, imageType, imageIdSave an edited version of a generated image. imageType is campaignImage, productStudio or groupShot (case-sensitive), and imageId is the UUID of the campaign image result, product studio output or group shot result. Must be a PNG of at most 27 MiB. Returns success, imageKey and imageUrl.

Example: Upload a Product Studio Input

cURL

curl -X POST \
  https://production.api.brandmachine.shop/api/product-studios/STUDIO_UUID/inputs/upload \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -F "file=@/path/to/product.jpg;type=image/jpeg" \
  -F "viewType=front"

JavaScript (Node.js 18 or later)

import { readFile } from 'node:fs/promises';

async function uploadProductInput(studioId, path, viewType = 'front') {
  const formData = new FormData();
  formData.append('file', new Blob([await readFile(path)], { type: 'image/jpeg' }), 'product.jpg');
  formData.append('viewType', viewType);

  const response = await fetch(
    `https://production.api.brandmachine.shop/api/product-studios/${studioId}/inputs/upload`,
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.BRANDMACHINE_API_TOKEN}`,
      },
      body: formData,
    }
  );

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Upload failed (${response.status}): ${error.reason}`);
  }

  return response.json();
}

Python

import os
import requests

url = 'https://production.api.brandmachine.shop/api/product-studios/STUDIO_UUID/inputs/upload'
headers = {'Authorization': f"Bearer {os.environ['BRANDMACHINE_API_TOKEN']}"}

with open('product.jpg', 'rb') as f:
    files = {'file': ('product.jpg', f, 'image/jpeg')}
    data = {'viewType': 'front'}
    response = requests.post(url, headers=headers, files=files, data=data)
    response.raise_for_status()
    print(response.json())

Example: Upload a Designer Inspiration Resource

curl -X POST \
  https://production.api.brandmachine.shop/api/designer-instances/resources \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -F "designerInstanceId=DESIGN_UUID" \
  -F "label=bird chest" \
  -F "file=@/path/to/inspiration.jpg;type=image/jpeg"

File Requirements

Supported Image Types

  • JPEG (.jpg, .jpeg)
  • PNG (.png)
  • WebP (.webp)

The file name must end in one of these extensions. If the multipart part has a Content-Type, it must be image/jpeg, image/png or image/webp; a generic type such as application/octet-stream is refused. Brand assets and edited images accept PNG only. The size chart import takes a JSON file.

Size Limits

  • A single image may be at most 15 MB. Edited images may be up to 27 MiB.
  • The whole request body may be at most 32 MB.
  • Images are stored as uploaded. They are not resized or converted.

Several uploads (product studio inputs, batch images and looks sheets, wardrobe images, fashion model uploads, Designer resources) analyse the image before they respond, so these requests can take several seconds.

Response Format

A successful upload returns HTTP status 200 and the created or updated record as JSON, including its id. For example, a product studio input upload returns the new input with fields such as id, viewType, productImage and createdAt. The endpoints that return something other than a record are noted in the tables above.

The size chart import always returns HTTP status 200 with a report: problems in the file are listed in its errors array rather than returned as an HTTP error.

Errors return a non-2xx status and a JSON body with a reason:

{
  "error": true,
  "reason": "Only image files (JPG, PNG, WEBP) are allowed"
}

Error Handling

StatusTypical reasonWhat to do
400The file is too large, has the wrong type, or a required form field is missing or invalidCheck the file and the form fields against the tables above
401Missing or invalid tokenCheck the Authorization header. See Authentication.
403 or 404The record in the path or form does not exist or belongs to another teamCheck the UUID
413The request body is over 32 MB, or an edited image is over 27 MiBSend a smaller file
422No looks could be read from a looks sheetUpload a clearer sheet

Retry Failed Uploads

Retry network failures and 5xx responses. A 4xx response fails the same way again, so fix the request instead of retrying it.

async function uploadWithRetry(url, formData, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    const response = await fetch(url, {
      method: 'POST',
      headers: { Authorization: `Bearer ${process.env.BRANDMACHINE_API_TOKEN}` },
      body: formData,
    }).catch((error) => {
      if (attempt === maxRetries) throw error;
      return null;
    });

    if (response?.ok) return response.json();
    if (response && response.status < 500) {
      const error = await response.json();
      throw new Error(`Upload failed (${response.status}): ${error.reason}`);
    }
    if (attempt === maxRetries) throw new Error(`Upload failed after ${maxRetries} attempts`);
    await new Promise((r) => setTimeout(r, 1000 * attempt));
  }
}

Best Practices

  • Validate before uploading (size, type, file extension) to avoid round trips on bad files.
  • Set the part's content type explicitly, as in the examples above. Some HTTP clients send application/octet-stream by default, which is refused.
  • Use the right endpoint. A campaign reference image goes through the campaign endpoint and a pattern reference through the pattern endpoint; the endpoints are not interchangeable.
  • Follow up with GraphQL. Uploading adds the file to the record; starting a generation is a separate GraphQL mutation. Use activeMediaGenerationTasks to see which generations are still running.

Need Help?

Contact support@brandmachine.shop for upload-related issues, with the endpoint and the reason from the response.