curl --request POST \
--url https://www.closient.com/dashboard/api/v1/trade-items/{organization_id}/{gtin}/images \
--header 'Content-Type: multipart/form-data' \
--header 'X-API-Key: <api-key>' \
--form image='@example-file' \
--form title= \
--form is_primary=false \
--form source_url= \
--form priority=0import requests
url = "https://www.closient.com/dashboard/api/v1/trade-items/{organization_id}/{gtin}/images"
files = { "image": ("example-file", open("example-file", "rb")) }
payload = {
"title": "",
"is_primary": "false",
"source_url": "",
"priority": "0"
}
headers = {"X-API-Key": "<api-key>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('image', '<string>');
form.append('title', '');
form.append('is_primary', 'false');
form.append('source_url', '');
form.append('priority', '0');
const options = {method: 'POST', headers: {'X-API-Key': '<api-key>'}};
options.body = form;
fetch('https://www.closient.com/dashboard/api/v1/trade-items/{organization_id}/{gtin}/images', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://www.closient.com/dashboard/api/v1/trade-items/{organization_id}/{gtin}/images",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"is_primary\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"source_url\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"priority\"\r\n\r\n0\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Content-Type: multipart/form-data",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://www.closient.com/dashboard/api/v1/trade-items/{organization_id}/{gtin}/images"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"is_primary\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"source_url\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"priority\"\r\n\r\n0\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://www.closient.com/dashboard/api/v1/trade-items/{organization_id}/{gtin}/images")
.header("X-API-Key", "<api-key>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"is_primary\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"source_url\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"priority\"\r\n\r\n0\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://www.closient.com/dashboard/api/v1/trade-items/{organization_id}/{gtin}/images")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"is_primary\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"source_url\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"priority\"\r\n\r\n0\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"created": "2025-07-01T14:00:00Z",
"id": "keATfB8VP2gSjcnTbsMNQL",
"image_url": "https://cdn.closient.com/products/00012345678905/hero.jpg",
"is_primary": true,
"priority": 0,
"title": "Front of package"
}{
"created": "2025-07-01T14:00:00Z",
"id": "keATfB8VP2gSjcnTbsMNQL",
"image_url": "https://cdn.closient.com/products/00012345678905/hero.jpg",
"is_primary": true,
"priority": 0,
"title": "Front of package"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}Upload product image
Upload a single image and attach it to the product. Accepts JPEG, PNG, GIF, WebP, SVG, or BMP. When is_primary is true the previously primary image (if any) is automatically demoted, so at most one image per product carries the flag. Requires the organization.contribute_organization permission on the membership.
Idempotent (C-6340): when the product already has an image with the same bytes (SHA-256), or at the same non-empty source_url, that image is returned with 200 and nothing is created or changed on it, except that an empty source_url on it is filled in. A new image returns 201. A client that lost a response can therefore safely send the upload again.
curl --request POST \
--url https://www.closient.com/dashboard/api/v1/trade-items/{organization_id}/{gtin}/images \
--header 'Content-Type: multipart/form-data' \
--header 'X-API-Key: <api-key>' \
--form image='@example-file' \
--form title= \
--form is_primary=false \
--form source_url= \
--form priority=0import requests
url = "https://www.closient.com/dashboard/api/v1/trade-items/{organization_id}/{gtin}/images"
files = { "image": ("example-file", open("example-file", "rb")) }
payload = {
"title": "",
"is_primary": "false",
"source_url": "",
"priority": "0"
}
headers = {"X-API-Key": "<api-key>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('image', '<string>');
form.append('title', '');
form.append('is_primary', 'false');
form.append('source_url', '');
form.append('priority', '0');
const options = {method: 'POST', headers: {'X-API-Key': '<api-key>'}};
options.body = form;
fetch('https://www.closient.com/dashboard/api/v1/trade-items/{organization_id}/{gtin}/images', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://www.closient.com/dashboard/api/v1/trade-items/{organization_id}/{gtin}/images",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"is_primary\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"source_url\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"priority\"\r\n\r\n0\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Content-Type: multipart/form-data",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://www.closient.com/dashboard/api/v1/trade-items/{organization_id}/{gtin}/images"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"is_primary\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"source_url\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"priority\"\r\n\r\n0\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://www.closient.com/dashboard/api/v1/trade-items/{organization_id}/{gtin}/images")
.header("X-API-Key", "<api-key>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"is_primary\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"source_url\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"priority\"\r\n\r\n0\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://www.closient.com/dashboard/api/v1/trade-items/{organization_id}/{gtin}/images")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"is_primary\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"source_url\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"priority\"\r\n\r\n0\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"created": "2025-07-01T14:00:00Z",
"id": "keATfB8VP2gSjcnTbsMNQL",
"image_url": "https://cdn.closient.com/products/00012345678905/hero.jpg",
"is_primary": true,
"priority": 0,
"title": "Front of package"
}{
"created": "2025-07-01T14:00:00Z",
"id": "keATfB8VP2gSjcnTbsMNQL",
"image_url": "https://cdn.closient.com/products/00012345678905/hero.jpg",
"is_primary": true,
"priority": 0,
"title": "Front of package"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}Authorizations
Path Parameters
UUID of the organization that owns the product.
22^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$GTIN-8/12/13/14 of the target product. Normalized to GTIN-14 by zero-padding before lookup.
^\d{8,14}$Body
Image file to upload (JPEG, PNG, GIF, WebP, SVG, or BMP).
Caption / role label for the image (e.g. "Front of package"). Empty string when not specified.
255When true, this image becomes the product's hero image and the previous primary image is automatically unmarked. Defaults to false.
Absolute http(s) URL the image was obtained from, e.g. a retailer's CDN URL. Recorded on the image and used to recognise a repeat upload of the same photo even when its bytes differ. Empty when the image has no external source.
2048^(https?://\S+)?$Display order within the gallery; lower numbers appear first. Defaults to 0.
0 <= x <= 2147483647Response
OK
Single image attached to a product.
URL-safe 22-character shortuuid encoding of the row's UUID primary key. Stable across the row's lifetime; suitable for sharing in URLs, log lines, and external SDK clients. Accepted on input as either the shortuuid form or the canonical UUID form (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).
22^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$Caption / role label for the image (e.g. "Front of package", "Nutrition facts"). Empty string when not set.
255"Front of package"
True for the single image surfaced as the product's hero. At most one image per product has this set; uploading a new primary unsets the previous one.
Display order within a product's image gallery. Lower numbers appear first; ties break on -created. Primary image always sorts first regardless of priority.
x >= 0ISO-8601 UTC timestamp of when the image was uploaded.
Absolute URL of the original (non-resized) image asset on the configured storage backend. null only when the underlying ImageField is empty, which should not occur for persisted rows.
"https://cdn.closient.com/products/00012345678905/hero.jpg"
External URL the image was obtained from. Empty when it has none or predates C-6340.
SHA-256 hex digest of the image bytes. Empty for images uploaded before C-6340.