---
title: "Upload a product image"
method: POST
path: "/public/v1/product-images"
tags: ["ProductImage"]
---

# Upload a product image

`POST /public/v1/product-images`

## Request

- Base URL: `https://app.distru.com`
- URL: `https://app.distru.com/public/v1/product-images`
- Auth: HTTP bearer

Uploads one image file and adds it to a product in the slot given by `rank`. Send the request as `multipart/form-data`. The image is stored and resized on upload, which takes a few seconds; the response's `url` is immediately downloadable.

A product has 4 image slots, `rank` 0 to 3, and the image with the lowest rank is the product's primary image. Read the product with [GET /public/v1/products/{id}](#get-a-product) to find a free slot (a product with no images has all four free). Uploading into a taken slot is rejected with a 400, so it is safe to retry an upload that timed out: if the first attempt landed, the retry fails instead of adding a duplicate. A product that already has 4 images takes no more until one is deleted with [DELETE /public/v1/product-images/{id}](#delete-a-product-image).

The file must be a JPEG, PNG or GIF of at most 10 MB; the type is read from the file's contents, not its name. Convert WebP or HEIC images first.

The upload updates the product's `updated_datetime` and sends a product update webhook. It does not touch inventory or push anything to Metrc or BioTrack.

Required permission: `products_permissions_edit` (plus access to the product under team restrictions).

## Request body `multipart/form-data`

- object
  - `file` string, binary, required: The image file: a JPEG, PNG or GIF of at most 10 MB. Required and must be non-empty.
  - `product_id` string, required: ID of the product to add the image to. An ID that doesn't exist for your company returns 404.
  - `rank` integer, required: The image's slot, 0 to 3; 0 is the primary image. Must be free on the product.

## Response `201`

Image uploaded

- ProductImageResponse: A single product image wrapped in a data envelope
  - `data` Image: An image as shown in Distru
    - `id` string, required: ID for this image
    - `name` string: Original file name of this image, or null when unknown
    - `rank` integer: Display slot of this image among the product's images, 0 to 3; the lowest rank is the primary image. Null when unranked
    - `url` string: URL to the full-size (original) image file, or null when no file is available

## Other responses

- `400`: Invalid parameters
- `401`: Missing or invalid API token
- `403`: The API token lacks the required permission, or team restrictions hide the record
- `404`: Not Found
- `503`: The upload timed out; nothing was saved, so retry it

## Changes

- **2026-10-07** `e8639f7dafed`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/distru/apis/distru-api/changes/public/v1/product-images/post.md)

---

[API](https://skmtc.dev/distru/apis/distru-api.md) - [All operations](https://skmtc.dev/distru/apis/distru-api/llms.txt) - [OpenAPI document](https://skmtc.dev/distru/apis/distru-api/revisions/9d851a88e82f?raw)
