Version: 1.0
Updated on: September 21st, 2026
This manual is for brand partners publishing images for their own products. It describes what a brand account can do and the limits that apply.
Read Manual for Brand Partners: Icecat Push-API (API-IN) first. This page continues it, and assumes you have it behind you: what a product is, how international and local content differ, how you authenticate, how you find brand, category, and locale IDs, and what a brand account may see. None of that is repeated here. What is here is everything specific to images.
The gallery is the one asset with a service of its own. It runs on a different address and answers in a different shape from the rest of the Brand API — so read the error section even if you have integrated other assets already.
The full field-by-field reference is the Icecat API swagger, tag product-gallery. This page explains what the swagger cannot: the order to do things in, which choice to make, what each choice costs you, and where integrations trip.
Every gallery request goes to https://api.icecat.biz, and every gallery path starts with /product-gallery/v1/. Put together, a real request URL looks like this:
https://api.icecat.biz/product-gallery/v1/imageshttps://api.icecat.biz/product-gallery/v1/products/{productId}/gallerieshttps://api.icecat.biz/product-gallery/v1/jobs/{jobId} Elsewhere on this page the host is left out and paths are written from /product-gallery/v1/…. Always prefix them with https://api.icecat.biz.
The same header as everywhere else in the Brand API, on every request:
Api-Token: your_api_token The token is not tied to an address, and it works on an account that has two-factor authentication switched on – no second factor is ever asked for on this path. That is what makes an unattended import possible under a protected account.
⚠️Older integrations authenticate with a session key instead, sent as the Access-Key header. That still works. If a request carries both, the session key is used and the token is ignored – so when you migrate an integration to the token, stop sending the key rather than sending both.
A refused credential and a missing one are told apart deliberately:
| You get | It means |
|---|---|
401 Missing credentials | the request carried neither header |
401 Invalid API token | the token was not accepted |
503 Auth service unavailable | we could not check the credential — retry |
Invalid API token is the only thing you are told. Whether the token never existed, was revoked, or belongs to an account that can no longer use it is deliberately not disclosed. Repeatedly sending a token that is not accepted gets your address throttled for a while, and the answer stays the same, so treat the first 401 as final rather than retrying in a loop.
Everything on this page uses placeholders in curly braces. This is what each one is and where it comes from:
| Placeholder | What it is | Where you get it |
|---|---|---|
{productId} | your product in Icecat | the answer you got when you created the product |
{taxonomyId} | which gallery of that product: 0 is the Icecat gallery; any other value is one partner’s gallery | 0, or the id Icecat gave you for that partner |
{languageId} | a locale: 0 is INT, above 0 is one language | Get your locales, in Manual for Brand Partners: Icecat Push-API (API-IN) |
{imageId} | one picture, in one locale, of one gallery | the answer when you added it, or any gallery read |
{sourceId} | the physical file behind a picture. It identifies the file, not a product — the same file can stand behind pictures on several of your products | image.source.sourceId in any read |
{jobId} | one batch you sent with POST …/batch, so you can check back on it | the answer to that call |
The product has to be yours. Every call below works on products of the brands your account is assigned to. Another brand’s product answers 403, whatever else is in the request.
You give Icecat a URL, and Icecat fetches the file. The picture has to be reachable from the public internet, not only from your own network.
Nothing here is tied to a particular tool or language. Every example on this page is written as the request itself — the method and path, the headers, the body — because that is what your side has to produce, whatever produces it: a script in any language, the REST connector your PIM or DAM already has, an integration platform, or a client you try things in by hand.
As a brand, you may look at and change the galleries of your own products. You may not delete anything — not an image, not a gallery. That is deliberate: to stop a picture from being distributed to channel partners, you deactivate it, and it stays recoverable. See section 11.
| What you want to do | Call | Explained in |
|---|---|---|
| see every gallery of a product | GET /product-gallery/v1/products/{productId}/galleries?view=FULL | §3 |
| see one gallery | GET /product-gallery/v1/products/{productId}/galleries/{taxonomyId} | §3 |
| see one picture | GET /product-gallery/v1/images/{imageId} | §3 |
| add a picture | POST /product-gallery/v1/images | §3 |
| change a picture | PATCH /product-gallery/v1/images/{imageId} | §9 |
| reuse the same picture in another locale | POST /product-gallery/v1/images/{imageId}/copies | §4 |
| activate or deactivate many pictures at once | PATCH /product-gallery/v1/images | §9 |
| read an order | GET /product-gallery/v1/products/{productId}/galleries/{taxonomyId}/image-orders/{languageId} | §6 |
| write an order | PATCH /product-gallery/v1/products/{productId}/galleries/{taxonomyId}/image-orders/{languageId} | §6 |
| let a locale fall back to the base order | DELETE /product-gallery/v1/products/{productId}/galleries/{taxonomyId}/image-orders/{languageId} | §6 |
| push a whole gallery in one batch — the answer comes back at once and carries a job id, the work runs after it | POST /product-gallery/v1/products/{productId}/batch | §7 |
| push a whole gallery in one batch and wait — the answer comes when every picture has been dealt with, and carries the result of each | POST /product-gallery/v1/products/{productId}/batch-sync | §7 |
| check a batch by its job id: whether it has finished, and what happened to each picture in it | GET /product-gallery/v1/jobs/{jobId} | §7 |
Every field of every one of these is in the swagger, tag product-gallery. Section 16 covers every size and length limit, and section 12 covers the error messages you are most likely to see.
The API has five things in it. Once these are clear, everything else on this page is detail.
Product. Your product in Icecat, addressed by its Icecat product ID.
Gallery. A product does not have one gallery. A gallery is the pair (product, taxonomy):
| taxonomyId | Which gallery |
|---|---|
0 | the Icecat gallery — your standard images, the ones your channel partners see |
| any other | a partner gallery — images for one particular partner. Taxonomies are named after partners, so the taxonomy id is how you address that partner’s gallery |
A partner gallery gives one partner a set of pictures of their own. If you know that a particular retailer sells this product better with different pictures — lifestyle shots instead of packshots, a different first image, images in their house format — you publish those into their taxonomy, and only they receive them. Everyone else keeps the Icecat gallery. Section 8 covers this.
A gallery has no ID of its own. The two numbers are its address, and both are required on every write — there is no default. Pass 0 explicitly when you mean the Icecat gallery.
Image. One picture in one gallery, for one locale. languageId: 0 means INT — the picture is shown in every locale. languageId: 1 (English), 3 (French) and so on means it belongs to that locale alone.
The same picture published in English, French, and Dutch is three images — three entries you can change, deactivate, or reorder independently.
Source. The physical file behind an image. Icecat stores the file once; if you send a file it already holds, it reuses the stored one instead of storing it again. One file can therefore stand behind several images — the locales of this product, and any other product you add it to. The practical effect: a file you send twice costs nothing the second time.
Order. Where each picture stands. A gallery keeps two kinds of order side by side — one base order and, optionally, an order per locale. Section 6 covers it in full.
Add one international picture to the Icecat gallery of one of your products.
The request:
POST https://api.icecat.biz/product-gallery/v1/images its headers:
Api-Token: your_api_tokenContent-Type: application/json and its body:
{
"productId": {productId},
"taxonomyId": 0,
"languageId": 0,
"imageUrl": "https://images.example.com/your-product/front.jpg",
"imageType": "ProductImage",
"fileName": "your_product_front"
} productId, taxonomyId and languageId are required and have no defaults. Passing 0 for the last two is a decision you make explicitly, not something you can leave out.
Every later example on this page shows the method, the path and the body only. The headers are the same every time — Api-Token, and Content-Type: application/json on anything with a body.
The answer carries the picture that was created, the physical file behind it with all five sizes, and where it lives:
{
"productId": {productId},
"taxonomyId": 0,
"image": {
"imageId": {imageId},
"languageId": 0,
"imageType": "ProductImage",
"fileName": "your_product_front",
"isActive": true,
"isPrivate": false,
"expiryDate": "",
"expired": false,
"source": {
"sourceId": {sourceId},
"md5": "9f2c…",
"width": 2973, "height": 2033,
"isCropped": false,
"origin": { "link": "…", "width": 2973, "height": 2033, "sizeBytes": 836052 },
"high": { "link": "…", "width": 2973, "height": 2033, "sizeBytes": 872039 },
"medium": { "link": "…", "width": 500, "height": 342, "sizeBytes": 51002 },
"low": { "link": "…", "width": 200, "height": 137, "sizeBytes": 9905 },
"thumbnail": { "link": "…", "width": 75, "height": 51, "sizeBytes": 2210 }
}
},
"metadata": {
"serverDate": "2026-09-20",
"allowedLanguageIds": [0, 1, 3, 4],
"canDelete": false
}
} Keep the imageId and the sourceId — the first addresses this picture in this locale, the second addresses the file itself, and you need both later: imageId to change or deactivate it, sourceId to place it in an order.
Two things in metadata are worth reading every time:
allowedLanguageIds — the locales this account may edit. A locale outside this list will be refused, so check it once at the start of an integration instead of discovering it per request.canDelete — false for a brand account. It is the API telling you which half of the vocabulary is yours.To verify what a product now holds, ask for the whole picture in one call:
GET https://api.icecat.biz/product-gallery/v1/products/{productId}/galleries?view=FULL view=BASIC (the default) returns each gallery with a count only. view=FULL returns every gallery of the product with its images — already sorted — and its orders.
A picture belongs to exactly one locale, and 0 (INT) is not “no locale” — it is “every locale”.
| You want | What to send |
|---|---|
| one picture for all markets | one image, languageId: 0 |
| a picture only Germany receives | one image, languageId: 4 |
| the same picture in three markets | three images, one per locale |
A locale keeps the INT pictures and adds its own. Adding a German picture does not hide the international ones. If Germany should receive different pictures rather than more pictures, publish the German ones and deactivate the international ones for that gallery — or use a batch with a policy that does both in one call (section 7).
Add it once, then reuse it. This does not upload or fetch the file again — it gives the same physical file an entry in each locale you name:
POST https://api.icecat.biz/product-gallery/v1/images/{imageId}/copies { "languageIds": [1, 3, 4] } Each locale gets its own entry over the same file. Two rules:
[0] alone is valid, [0, 1] is refused.| Field | Meaning | Rules |
|---|---|---|
imageUrl | where Icecat fetches the file | http, https, ftp or ftps, up to 1999 characters. ftps means explicit TLS on the FTP port, not implicit TLS on 990 |
languageId | the locale, 0 = INT | required, no default |
taxonomyId | which gallery, 0 = Icecat | required, no default |
imageType | what the picture shows | see below; absent means ProductImage |
fileName | your own name for it | letters, digits, _ and -, no dot, up to 35 characters |
isActive | distributed or not | absent means active |
isPrivate | restricts the picture to the partners authorized for your brand — see below | absent means public; allowed only in the Icecat gallery (taxonomyId: 0) |
expiryDate | the day the picture stops being published | YYYY-MM-DD, must be a future day; "" means no expiry |
The type says what the picture shows, and partners use it to lay the product page out — which picture is the hero, which are the packaging shots, which is the annotated one. It is one of the few fields that changes how your gallery is presented rather than just stored, so it is worth setting deliberately.
imageType is a string, and it has to match one of the values below exactly — the spelling, the capitals and the hyphens. An unknown value is refused.
| Value | What it is |
|---|---|
ProductImage | the product on a white or transparent background. The default when you send no type, and the type of the main image |
ProductImageFront-Center | straight-on, from the front |
ProductImageFront-Left | angled, front and left side together |
ProductImageFront-Right | angled, front and right side together |
ProductImageLeft | the left side |
ProductImageRight | the right side |
ProductImageRear | the back |
ProductImageTop | from above |
ProductImageBottom | from below |
ProductDetailImage | a close-up of one feature or detail |
| Value | What it is |
|---|---|
ProductPackagingImage | the packaging — the general pack-shot |
PackshotFront-Center | the front of the packaging, straight on |
PackshotFront-Left | the packaging, angled from the front left |
PackshotFront-Right | the packaging, angled from the front right |
PackshotRear | the back of the packaging, where the detailed information usually is |
PackshotBottom | the underside of the packaging |
| Value | What it is |
|---|---|
LifestyleImage | the product in use, in a real setting |
ProductImageAnnotated | a picture with callouts naming features. Usually local, because the callouts are written in a language |
PackageContents | everything the box contains |
Schema/Chart | a diagram, schema or chart. Note the slash — it is part of the value |
Screenshot | an interface, for software and digital products |
ProductAward | the product with an award or badge it has won |
Logo | the product’s logo. For products, there is nothing to photograph — a service, a warranty, a license — the logo is the picture |
Other | anything relevant that none of the above describes |
| Value | What it is |
|---|---|
PackshotFullFlat | the flattened packaging, front and back cover in one image |
ProductImagePEGI | the PEGI age rating (European Union) |
ProductImageUSK | the USK age rating (Germany) |
ProductImageESRB | the ESRB age rating (North America) |
Twenty-eight values in all. The dictionary is an open set — Icecat can add a type without a release — so treat an unknown value as “not added yet” rather than “never possible”, and ask us if you need one.
Four of them are tied to a category. The four video-game types above are accepted only on products in the video-games category; everywhere else they are refused, with a message naming both the type and the category. Every other type is open to every category.
This matters for how you build your mapping: map your image roles to Icecat types per category, not once for the whole catalog. A mapping that works for your games will be refused on your accessories. If a type you need is refused on a category where it makes sense, ask us rather than falling back to Other.
What each type is for, with examples of galleries that use them well, is in How to Make the Product Gallery Great Again — . Read it before you decide how to map your own image roles onto Icecat types; it is written for the people choosing the pictures rather than for the integration.
| Formats | JPEG, PNG, TIFF, BMP, WebP |
| Animated WebP | refused — only still images |
| Size | up to 50 MB |
| Dimensions | 200 to 10 000 pixels on each side |
Format is decided by reading the file, not by its extension or the name you gave it. If a file arrives but isn’t an image, or is damaged, it’s refused with a message naming the reason.
isPrivateA picture sent with isPrivate: true is not distributed to every channel partner. It reaches only the partners who are authorized for your brand in Icecat — the ones you invited or whose request you approved. Every other partner receives the gallery without it, and never learns it exists.
Use it for material you are willing to give your own retail network but not the whole catalog: campaign imagery, packaging for a product that is not on sale yet, pictures under a license that does not cover general distribution.
Three things to know:
taxonomyId: 0). In a partner gallery, the flag is refused, and it would mean nothing there anyway: that gallery already goes to one partner.expiryDate is a day, not a moment, and the calendar is Europe/Amsterdam — the platform’s business timezone. The expiry day itself already counts as expired: a picture dated 2026-12-31 stops being published at the first moment of 31 December in Amsterdam, not at the end of it.
A date that is already today or earlier in that calendar is refused. To stop a picture now, deactivate it.
Every read gives you metadata.serverDate in the same calendar, so you can work out expired yourself without guessing which day the server means.
The order decides which picture a partner sees first, which second, and so on.
Only the relative order matters. When your gallery reaches a partner, the pictures are numbered from 1 with no gaps, in the sequence your numbers put them in. So an order of 1, 2, 5, 9 and an order of 1, 2, 3, 4 arrive as exactly the same gallery. You never have to tidy your numbers for the sake of the output.
Take a gallery of four pictures — three international, one German only — in the Icecat gallery of a product. This is the number each picture holds in each order:
| Picture | Base order (languageId 0) | English (languageId 1) | German (languageId 4) |
|---|---|---|---|
| front.jpg (INT) | 1 | 1 | 2 |
| back.jpg (INT) | 2 | 2 | 3 |
| detail.jpg (INT) | 5 | 5 | 4 |
| de_packshot.jpg (German only) | 9 | — | 1 |
Reading the columns:
So an English partner receives front, back, detail, and a German partner receives de_packshot, front, back, detail.
Note the gaps in the base order — 1, 2, 5, 9. They change nothing: both partners receive a gallery numbered 1, 2, 3, 4.
A gallery keeps:
languageId: 0) — over every picture of the gallery, whatever locales those pictures live in. A picture present only in French is in the base order just the same.languageId > 0) — over the pictures that locale receives: the international ones plus its own.A locale that has no order of its own follows the base order, filtered to what it receives. When you read an order, isInherited: true tells you exactly that.
So one picture can hold several numbers at once: one in the base order, and one in each locale that has been arranged deliberately.
When you write an order, you name pictures by sourceId — the file itself — and not by imageId.
The reason is the base order. It covers the whole gallery, and one picture can be present in several locales at once. If an order named locale entries, a picture published in English, French and Dutch would take three places in one sequence instead of one. Naming the picture keeps it in a single place, no matter which locales it appears in.
Inside one locale the distinction does not arise: there a picture has exactly one entry, so the two mean the same position.
Instead of adding a picture and then correcting its position, name the position in the same call:
{
"productId": {productId},
"taxonomyId": 0,
"languageId": {languageId},
"imageUrl": "https://images.example.com/your-product/de_front.jpg",
"placements": [
{ "languageId": 0, "orderNumber": 3 },
{ "languageId": 4, "orderNumber": 1 }
]
} Filled in for a German picture, that says: third in the gallery’s base order, first for Germany.
In a placement, languageId names which order you are writing into, not the locale of the picture. Which orders a picture may name depends on the locale it was added to:
| The picture is | It may place itself in |
|---|---|
a locale picture (languageId > 0) | the base order and its own locale — so at most two entries |
an INT picture (languageId: 0) | the base order and any locales it likes |
Three more rules, and they are short:
PATCH https://api.icecat.biz/product-gallery/v1/products/{productId}/galleries/{taxonomyId}/image-orders/{languageId} The three placeholders say which order you are writing: the product, which of its galleries, and which order inside that gallery — 0 for the base order, or a locale id for that locale’s own.
{ "placements": [ { "sourceId": {sourceId}, "orderNumber": 1 },
{ "sourceId": {sourceId}, "orderNumber": 2 } ] } Filled in, …/products/152257994/galleries/0/image-orders/4 writes Germany’s order in the Icecat gallery of product 152257994.
You only list the pictures you care about; the rest of the locale’s pictures are arranged around them automatically. There is nothing to wait for: when the call answers, the order is in place.
Every picture you name has to be in the order you are writing. A sourceId identifies a file, not a product, so nothing stops you from sending one that belongs elsewhere — but the call refuses it with 400 and picture N is not in this sequence. That covers both cases: a file that is on another product, and a file that is in this gallery but not visible in the locale you are writing.
Two more refusals, each a 400 before anything is written:
sourceId twice in one request;orderNumber twice in one request — two pictures cannot ask for the same place.DELETE https://api.icecat.biz/product-gallery/v1/products/{productId}/galleries/{taxonomyId}/image-orders/{languageId} That locale stops having an order of its own and follows the base order again. The answer shows the order it falls back to, marked isInherited: true. Only locale orders can be reset — the base order is the ground everything else stands on.
Adding pictures one at a time is fine for a handful. When your system owns the gallery and wants Icecat to match it, use a batch: one submission that adds, updates, deactivates and orders, under a stated policy.
POST https://api.icecat.biz/product-gallery/v1/products/{productId}/batch {
"policy": "BATCH_POLICY_REPLACE_ALL_FOR_ALL_LOCALES",
"items": [
{ "taxonomyId": 0, "languageId": 0,
"imageUrl": "https://images.example.com/your-product/front.jpg",
"imageType": "ProductImage",
"placements": [ { "languageId": 0, "orderNumber": 1 } ] },
{ "taxonomyId": 0, "languageId": 0,
"imageUrl": "https://images.example.com/your-product/back.jpg",
"imageType": "ProductImage",
"placements": [ { "languageId": 0, "orderNumber": 2 } ] }
]
} Up to 2000 items in one submission.
This is the rule that costs integrations the most time. An item is not a partial update: a field you leave out means its default, not “leave what is there”. Re-submitting a picture without imageType resets its type to ProductImage; without fileName clears the name it had.
So build each item as the complete state you want that picture to have, and send every field you care about every time.
The policy is required — there is no default — and it decides what happens to the pictures that are already there. It is applied within each (gallery, locale) group present in your items: galleries your items do not mention are never touched.
| Policy | What it does to what is already there | When you want it |
|---|---|---|
BATCH_POLICY_CREATE_OR_REPLACE | nothing is deactivated; new pictures are added, matching ones updated | you are adding to a gallery somebody else also contributes to |
BATCH_POLICY_REPLACE_ALL | the pictures of the group’s locale are deactivated first, then your items are applied | you own one market’s pictures and are republishing that market |
BATCH_POLICY_REPLACE_ALL_FOR_ALL_LOCALES | all pictures of the group’s gallery are deactivated first, then your items are applied | your system owns the whole gallery and Icecat should mirror it |
Read the middle row carefully: it replaces one locale, not the gallery. If you send only German items under REPLACE_ALL, only German pictures are deactivated — the international ones stay.
A gallery whose items all failed is left alone. A refused item names nothing, so its gallery is never reached, and neither the policy nor anything else touches what is there. A feed that breaks overnight cannot take your gallery down — it simply changes nothing. That holds as long as the submission reaches the gallery only through its items; the next section is about the one way to reach it without them, and about what REPLACE_ALL_FOR_ALL_LOCALES then does.
extraTaxonomyIdsBefore the policy does anything, the batch works out which galleries it is acting on. There are exactly two ways in:
extraTaxonomyIds.Galleries it does not reach are not touched at all, whatever the policy says.
extraTaxonomyIds takes up to 500 ids. Listing a gallery your items already cover changes nothing, and an id Icecat does not know is ignored rather than refused.
This is the case extraTaxonomyIds exists for, and it is worth walking through, because on its own each part of the request looks like it should do nothing.
Say a partner’s contract has ended and their gallery has to go. You have no pictures to send — you want the gallery cleared, not refreshed. A batch with an empty items list reaches nothing, so on its own it does nothing at all. Naming the taxonomy is what points the batch at that gallery:
{
"policy": "BATCH_POLICY_REPLACE_ALL_FOR_ALL_LOCALES",
"items": [],
"extraTaxonomyIds": [ {taxonomyId} ]
} REPLACE_ALL_FOR_ALL_LOCALES is the one policy that knows what to clear without being shown any pictures — “all pictures of this gallery” needs no items to mean something. Pointed at a gallery by extraTaxonomyIds, it deactivates everything in it, in every locale.
The other two policies work the opposite way: they infer what to clear from what was applied, so in a gallery that received nothing they clear nothing. If you would rather be explicit about which markets you are clearing, use one of them and name the locales instead:
{
"policy": "BATCH_POLICY_CREATE_OR_REPLACE",
"items": [],
"extraTaxonomyIds": [ {taxonomyId} ],
"removeLanguageIds": [ 0, 1, 4 ]
} Here nothing is inferred at all: the policy clears nothing, and exactly the three locales you listed are cleared — INT, English and German. Locales you did not name keep their pictures. This is the safer form, and the one to prefer when you are clearing part of a gallery rather than all of it.
Either way the pictures are deactivated, not deleted (section 11), so a partner gallery emptied by mistake can be switched back on.
⚠️The same combination is what makes
REPLACE_ALL_FOR_ALL_LOCALESdangerous. Under that policy, every taxonomy inextraTaxonomyIdsis emptied — including ones you listed for a different reason. Do not list taxonomies “for completeness” under it. List a taxonomy there when you mean to act on that gallery, and nothing else.
The other use is ordinary: one submission carrying items for several partner galleries. There you do not need extraTaxonomyIds at all — each item names its own taxonomyId, and that is what puts its gallery in scope. Reach for the field only when a gallery has to be acted on without items, which in practice means emptying it.
removeLanguageIdsremoveLanguageIds lists locales whose existing pictures should be deactivated. Use it when a market should lose its pictures in the same call that refreshes the others — send the new German items and "removeLanguageIds": [1], and English is cleared while German is replaced.
It differs from the policy in one way that matters: the policy only deactivates in proportion to what it managed to apply, while removeLanguageIds is an instruction that is carried out even when nothing was applied at all. An empty batch with removeLanguageIds clears those locales.
Like the policy, it only reaches the galleries the batch reaches — the ones its items name, plus extraTaxonomyIds.
deduplicateBetweenLocalesAfter the items are applied, deactivate every locale picture whose file is also present among the visible international pictures of its gallery.
Use it when your feed sends the same picture per locale — many do, because their source system has no notion of an international asset. Without this flag a partner in Germany receives the same photograph twice: once as the international picture and once as the German one.
POST …/batch | POST …/batch-sync | |
|---|---|---|
| Answers | at once, with a job id to check back on | when the work is done, with the results |
| Good for | real submissions of any size | small batches, and testing |
batch does not make you wait. It answers straight away with a job:
{ "job": { "jobId": "{jobId}", "status": "STATUS_ACCEPTED" } } Poll it at GET /product-gallery/v1/jobs/{jobId}. The job echoes your whole submission — product, policy, items, removeLanguageIds, extraTaxonomyIds — so a reader does not need the original request to make sense of it. A finished job stays readable for a retention period and is then removed, so read the verdicts when the job finishes rather than months later.
Results appear all at once, with the terminal status. While the job is STATUS_ACCEPTED or STATUS_PROCESSING, no item has a verdict; when it turns STATUS_COMPLETED or STATUS_FAILED, every item has one. There are no partial results and no counters, so poll for the terminal status rather than for progress.
Your pictures are fetched in parallel but applied in the order you submitted them, so the final gallery does not depend on which download finished first. Jobs for the same product run one after another.
Each item ends with a status and a finer numeric code:
| Status | code | Meaning |
|---|---|---|
STATUS_CREATED | 201 | a new picture was created |
STATUS_UPDATED | 200 | an existing picture was updated |
STATUS_UPDATED | 304 | it already held exactly this state; nothing was written |
STATUS_FAILED | 400 | the item cannot be applied — a dead URL, a file that is not an image, an unknown type or locale, a duplicate inside the submission |
STATUS_FAILED | 403 | you may not edit this item’s locale |
A job that completed with failed items is not a failed job: those verdicts belong to the items. A STATUS_FAILED job carries a failureReason instead — PERMISSION_DENIED (yours to fix, and re-running changes nothing), ABANDONED (nothing is wrong with the submission; resend it) or INTERNAL (ours).
And note what cannot happen: if your submission is malformed, it is refused whole with 400 and no job is created. A job id always means the submission was structurally sound.
Everything above works the same way for a partner gallery — you change one number.
{ "productId": {productId}, "taxonomyId": {taxonomyId}, "languageId": 0,
"imageUrl": "https://images.example.com/your-product/lifestyle_1.jpg" } The gallery is created on first write; nothing needs to be set up beforehand.
What it means for that partner: they receive this gallery instead of the Icecat gallery, whole. Galleries are never merged — a partner gets one gallery, from one source, complete. So a partner gallery with three pictures replaces an Icecat gallery of ten for that partner, rather than adding three to it.
What it means for everyone else: nothing. Partners who are not authorized for that taxonomy cannot receive that gallery at all, and keep the Icecat gallery.
Two limits to remember:
isPrivate is not available in a partner gallery. Private pictures exist only in the Icecat gallery (taxonomyId: 0).PATCH https://api.icecat.biz/product-gallery/v1/images/{imageId} { "imageType": "ProductDetailImage", "expiryDate": "2027-01-31" } Absent fields are left alone — the single-picture PATCH really is a patch, unlike a batch item. For expiryDate the two empty-ish values differ: absent means “do not change”, "" means “clear the expiry”.
Order numbers are not changed here — they belong to the order endpoints in section 6.
PATCH https://api.icecat.biz/product-gallery/v1/images { "imageIds": [{imageId}, {imageId}], "isActive": false } This call does one thing: it switches pictures on or off. Up to 2000 of them, all belonging to one product. isActive is required — leaving it out is not the same as sending false, it is a request that names no change, and it is refused. Two more refusals arrive before anything runs: repeating an ID, and mixing pictures of different products.
The answer reports every picture you submitted, changed or not:
| Outcome | Meaning |
|---|---|
STATUS_CHANGED | applied |
STATUS_NO_CHANGE | it was already in that state |
STATUS_SKIPPED_NOT_FOUND | no such picture |
STATUS_SKIPPED_NO_PERMISSION | you may not edit it — most often a locale outside your allowed set |
“No change” is reported rather than left out on purpose: you cannot work it out from the outside. A list of IDs that are all unknown is a normal answer with every entry skipped, not an error.
Your picture is fetched by Icecat’s servers, not by a browser, and not from your office network. The platform deliberately behaves like a browser, because supplier servers expect one:
The most common failure is not a broken link — it is a host that serves the picture to a person and refuses our servers. If a URL opens in your browser but Icecat reports that the supplier refused it, the fix is on the hosting side: allow our fetches, or publish the files on a host that does not challenge automated clients. Sending the same URL again will not help; the refusal is a verdict, not a hiccup.
Addresses that resolve inside Icecat’s own network are refused at every step of a redirect chain. This is a security boundary and cannot be configured away.
As a brand account, you cannot delete. This is the vocabulary you have instead:
| You want | Do this |
|---|---|
| stop distributing a picture to channel partners | PATCH it with isActive: false |
| stop many at once | bulk PATCH with isActive: false |
| put one back | PATCH it with isActive: true |
| stop a picture on a known date | set expiryDate |
| clear a whole locale in one submission | a batch with removeLanguageIds |
A deactivated picture is not distributed to anyone, but it is still there: you and Icecat editors can see it and switch it back on. Nothing you do through this API destroys a picture.
Every error, on every operation, has the same body — and it is not the Brand API’s error shape:
{ "code": 3, "message": "invalid image: image dimensions out of range: 199x200 (allowed 200..10000 per side)", "details": [] } The HTTP status is the class, and it tells you what to do:
| Status | What it means | What to do |
|---|---|---|
400 | the request itself is wrong — a missing field, an impossible value, a file that is not acceptable | fix and resend; retrying unchanged will fail identically |
401 | the credential is missing (Missing credentials) or not accepted (Invalid API token) | check the Api-Token header — see section 1 |
403 | you may not do this — the product, the locale, or the operation is not yours | do not retry; check metadata.allowedLanguageIds, and whether the operation is one brands have at all |
404 | the product, picture or job does not exist | check the id |
409 | the change conflicts with the current state | read the current state and decide |
429 | the service is at capacity right now | back off and retry |
500 | our side failed | retry with a backoff |
503 | the service was saturated and could not take your file, or your credential could not be checked | retry — nothing is wrong with the request, and writing the picture off would discard a good one |
message is written to be shown to a person. Do not parse it — its wording can be improved without notice. Branch on the HTTP status and, where you need finer branching, on the numeric code.
Not exhaustive, and the wording can change — but these are the ones integrations actually hit, and knowing the cause saves the guessing.
Adding or changing a picture
| Message | What happened | What to do |
|---|---|---|
download failed: HTTP 403 from … | the host serving your file refused Icecat’s servers | fix it on the hosting side — section 10. Resending will not help |
download failed: HTTP 404 from … | the address leads to nothing | check the URL |
download failed: empty response body from … | the host answered, with no file | check the URL, then the host |
image download failed | the fetch failed for a reason without a status — DNS, TLS, a refused connection | check the host is reachable from the public internet |
unsupported image format (accepted: jpeg, png, tiff, bmp, webp) | the file is not one of the accepted formats, or it is an animated WebP | convert it. The format is read from the file, not from the extension |
invalid image: image dimensions out of range: 200x143 (allowed 200..10000 per side) | one side is outside the range | resize |
file exceeds the size cap: … bytes (cap …) | over 50 MB | compress or resize |
unknown image type "…" | the type is not in the Icecat dictionary | check the spelling against section 5 — hyphens and capitals are part of the value |
image type "…" is not allowed for the product's category … | the type exists but not for this category | see section 5: four types belong to video games only |
isPrivate is only allowed on the Icecat gallery (taxonomyId=0) | isPrivate was sent on a partner gallery | drop the flag; it would mean nothing there |
unknown taxonomyId … | no such gallery exists to address | check the id Icecat gave you for that partner |
expiryDate "…" is not a valid YYYY-MM-DD date | the format is wrong, or the day is today or earlier | use a future day; to stop a picture now, deactivate it |
Locales
| Message | What happened | What to do |
|---|---|---|
languageIds: INT (0) cannot be mixed with specific languages | a reuse call named 0 alongside real locales | send [0] on its own, or the locales on their own |
languageIds are required | the reuse call named no locale | name at least one |
placements: an image added to locale N cannot place itself in order M | a locale picture tried to write into another locale’s order | a locale picture may name only the base order and its own locale |
placements name order N twice | the same locale appears twice in placements | one entry per locale |
Orders
| Message | What happened | What to do |
|---|---|---|
picture N is not in this sequence | that sourceId is not among the pictures visible in the order you are writing | it is a file ID, not a product ID — check it belongs to this gallery and this locale |
picture N appears more than once | the same sourceId twice in one request | one entry per picture |
orderNumber N is assigned more than once | two pictures asked for the same place | give them different numbers |
orderNumber N is outside [1, 65535] | the number is out of range, or 0 | the first place is 1, never 0 |
Batches and bulk changes
| Message | What happened | What to do |
|---|---|---|
imageIds: N ids exceed the bulk ceiling of 2000 | too many pictures in one call | split the call |
imageIds: id N appears more than once | a repeated ID | de-duplicate your list |
image ids belong to N products; a bulk action addresses one product | the list spans products | one call per product |
Permissions and credentials
| Message | What happened | What to do |
|---|---|---|
Missing credentials | neither Api-Token nor Access-Key was sent | send the token — section 1 |
Invalid API token | the token was not accepted | check it in your profile. The reason is not disclosed, and retrying will not change it |
Auth service unavailable | we could not check the credential | retry with a backoff |
Your user group is not allowed to perform this action. | the operation is not one brand accounts have — cropping, or the source endpoints | see section 14 |
service overloaded, retry later | the service was saturated | retry. Nothing is wrong with your request |
A push you can’t repeat is a push that fails at 3 a.m. and wakes somebody up. Three facts make repeating safe:
REPLACE_ALL… is idempotent in effect. Applying the same submission twice leaves the same gallery, because the policy deactivates first and then applies your items. This is why you should prefer a batch over a sequence of single calls for anything automated.CREATE_OR_REPLACE and single POST /images are not. They add. Repeating them adds again, and you will end up with two entries for one picture in one locale.What to do when a job or a call fails halfway:
STATUS_FAILED with FAILURE_REASON_ABANDONED — resend; nothing about the submission is wrong.STATUS_FAILED with FAILURE_REASON_PERMISSION_DENIED — do not resend until the access question is settled.503 — retry with a backoff. This is the one ingest failure that is ours, not yours.Stated plainly, so you do not build against them:
isPrivate works only at taxonomyId: 0.metadata.allowedLanguageIds is the list; anything else is refused, and in a bulk call is reported as STATUS_SKIPPED_NO_PERMISSION.One product, one submission, three markets. English and French get the international pictures in the standard order; Germany gets an extra German-language packshot first, and the rest after it.
POST https://api.icecat.biz/product-gallery/v1/products/{productId}/batch request headers:
Api-Token: your_api_token
Content-Type: application/json body — 4 is the locale id for German, 0 is INT:
{
"policy": "BATCH_POLICY_REPLACE_ALL_FOR_ALL_LOCALES",
"deduplicateBetweenLocales": true,
"items": [
{ "taxonomyId": 0, "languageId": 0,
"imageUrl": "https://images.example.com/your-product/front.jpg",
"imageType": "ProductImage", "fileName": "your_product_front",
"placements": [ { "languageId": 0, "orderNumber": 1 } ] },
{ "taxonomyId": 0, "languageId": 0,
"imageUrl": "https://images.example.com/your-product/back.jpg",
"imageType": "ProductImageRear", "fileName": "your_product_back",
"placements": [ { "languageId": 0, "orderNumber": 2 } ] },
{ "taxonomyId": 0, "languageId": 0,
"imageUrl": "https://images.example.com/your-product/detail.jpg",
"imageType": "ProductDetailImage", "fileName": "your_product_detail",
"placements": [ { "languageId": 0, "orderNumber": 3 } ] },
{ "taxonomyId": 0, "languageId": 4,
"imageUrl": "https://images.example.com/your-product/de_packshot.jpg",
"imageType": "ProductPackagingImage", "fileName": "your_product_de",
"placements": [ { "languageId": 4, "orderNumber": 1 } ] }
]
} Every item names its type and its file name, because a batch item is the whole state of that picture — see section 7.
What this does:
REPLACE_ALL_FOR_ALL_LOCALES covers all locales of that gallery;Then poll the job, and read the gallery back to confirm:
GET https://api.icecat.biz/product-gallery/v1/jobs/{jobId}
GET https://api.icecat.biz/product-gallery/v1/products/{productId}/galleries?view=FULL
GET https://api.icecat.biz/product-gallery/v1/products/{productId}/galleries/0?languageId=4 The last call answers with Germany’s effective order — the one a German partner will receive, with isInherited telling you whether it is Germany’s own or the base order showing through.
Every bound this API applies, in one place. They are contract limits, the same for every account, and they are checked before anything is written — a request over one of them is refused with 400 and changes nothing.
One picture
imageUrl length | 1999 characters |
fileName | up to 35 characters; letters, digits, _ and -, no dot |
imageType | 1–64 characters, and must be a value from section 5 |
| File size | 50 MB |
| Dimensions | 200 to 10 000 pixels on each side |
| Formats | JPEG, PNG, TIFF, BMP, WebP — animated WebP refused |
Order
| Order number | 1 to 65535. The first place is 1, never 0 |
placements when adding a picture | up to 120 entries, each locale at most once |
| A locale picture may place itself in | the base order and its own locale — at most two orders |
One call
| Batch items | 2000 per submission |
extraTaxonomyIds | 500 per submission, each ID once |
removeLanguageIds | the locales you name; unknown ones are skipped, not refused |
| Activate / deactivate in bulk | 2000 picture IDs, all of one product, no ID repeated |
Your account
| Locales you may write | whatever your account is assigned — metadata.allowedLanguageIds on every read |
| Products you may write | those of the brands assigned to your account |
If one of these blocks an integration that is otherwise reasonable, tell us rather than working around it.
AI is becoming a regular part of online shopping, but consumers are not abandoning traditional…
Sprint 104 is about making what we already have more dependable. Publication status is now…
Icecat will be present at E-SHOW Madrid 2026, taking place on November 4 and 5…
Meta has introduced Muse, a personal AI agent designed to do more than answer questions.…
Bol has strengthened its position as the largest online retailer in the Netherlands, widening its…
German online stores are losing visibility in Google search as AI-generated answers take a larger…