Contents
Products
Read, create, update and delete products. A product is addressed by its Nucleo id or by its sku
(/products/123 or /products/TEE-001).
Base URL, authentication and conventions of this API are on the Catalog API page.
/productsList products
Products ordered by id, page by page. With updated_since, only the products that changed at or
after that moment, ordered by when they changed: use the server_time of your previous sync
(from GET /ping) or the latest updated_at you stored.
What counts as a change: anything recorded in the product's Activity in Nucleo (made in the app, by an import, by another connected system, by Atomo or by this API) and every change of a stock quantity made on the product page, by an import or by this API.
Deleted products. With updated_since, a product moved to Nucleo's trash comes back as
{"id", "sku", "deleted": true, "updated_at"}: remove it from your copy. A product stays in the
trash for 30 days (and can be restored, which counts as a change), then it is deleted for good and
no longer listed. Without updated_since, products in the trash are not listed.
Query parameters
limitintegerItems per page, 1–200.
min 1max 200default 50Example:50cursorstringThe
next_cursorof the previous page. A cursor works only with the same filters it was made with.updated_sincestring (date-time)ISO 8601 date-time. Only products changed at or after this moment, deleted ones included.
Example:2026-10-13T08:00:00ZskustringOnly these SKUs, comma separated (1–100).
Example:TEE-001,TEE-002statusstringOnly products with this status code (for example
active,draft).
Responses
200One page of products.application/json
dataarray of Product | DeletedProductrequirednext_cursorstring | nullrequiredPass it as
cursorto get the next page;nullon the last page.
400The request must change:
validation_failed(seedetails, one list of messages per field),invalid_json,invalid_cursor,product_not_written,variant_not_written,not_written,image_not_downloadable. application/jsonerrorobjectrequiredChild attributes
codestringrequiredStable machine-readable code. Branch on it, not on the message.
messagestringrequiredrequest_idstringrequiredSame as the
X-Request-Idheader. Quote it to support.detailsobject
401
unauthorized: the key is missing, wrong or revoked.application/jsonerrorobjectrequiredChild attributes
codestringrequiredStable machine-readable code. Branch on it, not on the message.
messagestringrequiredrequest_idstringrequiredSame as the
X-Request-Idheader. Quote it to support.detailsobject
429
rate_limited: wait the seconds inRetry-After.application/jsonHeadersRetry-AfterSeconds to wait.
errorobjectrequiredChild attributes
codestringrequiredStable machine-readable code. Branch on it, not on the message.
messagestringrequiredrequest_idstringrequiredSame as the
X-Request-Idheader. Quote it to support.detailsobject
curl -X GET 'https://api-catalog.nucleoplatform.com/api/catalog/v1/products?limit=50&updated_since=2026-10-13T08%3A00%3A00Z&sku=TEE-001%2CTEE-002' \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Accept: application/json'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/products?limit=50&updated_since=2026-10-13T08%3A00%3A00Z&sku=TEE-001%2CTEE-002", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.NUCLEO_ACCESS_TOKEN}`,
Accept: "application/json",
},
});
const data = await res.json();
console.log(res.status, data);<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$response = $client->request('GET', 'https://api-catalog.nucleoplatform.com/api/catalog/v1/products?limit=50&updated_since=2026-10-13T08%3A00%3A00Z&sku=TEE-001%2CTEE-002', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Accept' => 'application/json',
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"data": [
{
"id": 1,
"sku": "string",
"external_id": "string",
"title": "string",
"description": "string",
"handle": "string",
"status": "string",
"product_type": "string",
"brand": "string",
"attribute_set": "string",
"model_code": "string",
"gender": "string",
"season": "string",
"color": "string",
"material": "string",
"made_in": "string",
"composition": "string",
"categories": [
{
"id": 1,
"name": "string",
"path": "Clothing > Tops"
}
],
"attributes": {},
"translations": {},
"images": [
{
"id": 1,
"url": "string",
"alt": "string",
"position": 1,
"width": 1,
"height": 1
}
],
"variants": [
{
"id": 1,
"sku": "string",
"barcode": "string",
"options": {},
"cost": "string",
"weight": 1.5,
"weight_unit": "string",
"prices": [],
"stock": []
}
],
"updated_at": "2026-10-01T09:30:00Z"
}
],
"next_cursor": "string"
}{
"error": {
"code": "validation_failed",
"message": "The request is not valid: see details.",
"request_id": "7c0f8a1e-3b2d-4e5f-9a6b-1c2d3e4f5a6b",
"details": {
"variants.0.prices.0.price_list": [
"Unknown price list \"usa\". GET /price-lists lists them."
]
}
}
}{
"error": {
"code": "string",
"message": "string",
"request_id": "string",
"details": {}
}
}{
"error": {
"code": "string",
"message": "string",
"request_id": "string",
"details": {}
}
}/productsCreate or update a product
Creates the product, or updates it if the SKU already exists (also if it is known by an earlier SKU).
Without sku, an external_id this key already sent finds the product.
Only the fields you send are written; variants, prices and stock are added or updated, never removed
(delete a variant with DELETE /products/{product}/variants/{variant}).
Answers 201 when the product was created and 200 when it existed. result.status says what
happened; result.notes explains fields that were not written (the key's field rule, or a field
owned by another system). A product that could not be written at all is a 400 product_not_written.
Headers
Idempotency-KeystringAny string of 1–255 printable ASCII characters, unique per change you mean (a UUID is fine). Sending the same request again with the same key within 48 hours returns the first answer without doing the work again (header
Idempotent-Replayed: true). The same key with a different request is a409 idempotency_key_reused; while the first request is still running,409 idempotency_key_in_progresswithRetry-After: 1. Answers429and5xxare not stored: retry them with the same key.max length 255Example:2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44
Request bodyapplication/json
skustringrequiredmax length 255pattern ^[A-Za-z0-9][A-Za-z0-9._-]*$external_idstringYour id for the product. Remembered per key: later calls can send it instead of
sku.max length 255titlestringmax length 255descriptionstringHTML; unsafe markup is removed.
max length 65000statusstringA status code of the catalog (for example
active,draft). An unknown code is an error.product_typestringCreated if new.
max length 255brandstringCreated if new.
max length 255categorystringA category path (
Clothing > Tops): added to the product's categories, created if new. To replace all categories usePUT /products/{product}/categories.max length 500model_codestringmax length 255genderstringmax length 255seasonstringOne or more season codes, comma separated (
SS25, FW25).max length 255colorstringmax length 255materialstringmax length 255made_instringISO country code or country name.
max length 255compositionstringmax length 2000attribute_setstringName or id of the attribute set.
attributesobjectValues by attribute code (
GET /attributes). An unknown code is an error.translationsobjectBy locale code (
GET /locales). An unknown locale is an error.image_urlsarray of string (uri)Linked as they are (not downloaded); a URL already on the product is not added again. To store a copy in Nucleo, use
POST /products/{product}/images.max items 50variantsarray of VariantInputmax items 500Attributes of each item
skustringrequiredUnique in the whole catalog.
max length 255pattern ^[A-Za-z0-9][A-Za-z0-9._-]*$barcodestringUnique in the whole catalog.
max length 255sizestringSize code; becomes the variant's size option (read back in
options, as a normalised option code).max length 255costnumbermin 0weightnumbermin 0weight_unitstringOne ofgkglbozpricesarray of PriceInputAttributes of each item
price_liststringrequiredPrice list code (
GET /price-lists).pricenumberrequiredmin 0sale_pricenumber | nullDiscounted price on that list.
min 0
stockarray of StockInputAttributes of each item
locationstringrequiredLocation code (
GET /locations).availableintegerrequiredReplaces the quantity at that location.
Responses
200The product as it is now, and what happened.application/json
dataProductrequiredChild attributes
idintegerskustringexternal_idstring | nullThe id this key gave the product.
titlestring | nulldescriptionstring | nullhandlestring | nullstatusstring | nullproduct_typestring | nullbrandstring | nullattribute_setstring | nullmodel_codestring | nullgenderstring | nullseasonstring | nullcolorstring | nullmaterialstring | nullmade_instring | nullcompositionstring | nullcategoriesarray of ProductCategoryAttributes of each item
idintegernamestringpathstringExample:Clothing > Tops
attributesobjectValues by attribute code. Options as their code; several options as their codes, comma separated.
translationsobjectLocales other than the default one.
imagesarray of ImageAttributes of each item
idintegerurlstringaltstring | nullpositionintegerwidthinteger | nullheightinteger | null
variantsarray of VariantAttributes of each item
idintegerskustringbarcodestring | nulloptionsobjectThe options that tell variants apart: attribute code → option code (
{"size": "m"}). Option codes are normalised, so a size sent asMcomes back asm.coststring | nullweightnumber | nullweight_unitstring | nullpricesarray of objectAttributes of each item
price_liststringcurrencystring | nullpricestring | nullExample:19.90sale_pricestring | null
stockarray of objectAttributes of each item
locationstringavailableintegeron_handinteger
updated_atstring (date-time) | nullWhen the product last changed;
nullif it has not changed since this field exists.
resultWriteResultrequiredChild attributes
statusstringrequiredpartial: some variants could not be written (seeerrors), the rest was.skipped: the key's field rule left nothing to write.One ofcreatedupdatedpartialskippederrorsarray of objectrequiredAttributes of each item
variant_skustring | nullmessagestring
notesarray of stringrequiredFields not written and why (field rule, field owned by another system).
201The product as it is now, and what happened.application/json
dataProductrequiredChild attributes
idintegerskustringexternal_idstring | nullThe id this key gave the product.
titlestring | nulldescriptionstring | nullhandlestring | nullstatusstring | nullproduct_typestring | nullbrandstring | nullattribute_setstring | nullmodel_codestring | nullgenderstring | nullseasonstring | nullcolorstring | nullmaterialstring | nullmade_instring | nullcompositionstring | nullcategoriesarray of ProductCategoryAttributes of each item
idintegernamestringpathstringExample:Clothing > Tops
attributesobjectValues by attribute code. Options as their code; several options as their codes, comma separated.
translationsobjectLocales other than the default one.
imagesarray of ImageAttributes of each item
idintegerurlstringaltstring | nullpositionintegerwidthinteger | nullheightinteger | null
variantsarray of VariantAttributes of each item
idintegerskustringbarcodestring | nulloptionsobjectThe options that tell variants apart: attribute code → option code (
{"size": "m"}). Option codes are normalised, so a size sent asMcomes back asm.coststring | nullweightnumber | nullweight_unitstring | nullpricesarray of objectAttributes of each item
price_liststringcurrencystring | nullpricestring | nullExample:19.90sale_pricestring | null
stockarray of objectAttributes of each item
locationstringavailableintegeron_handinteger
updated_atstring (date-time) | nullWhen the product last changed;
nullif it has not changed since this field exists.
resultWriteResultrequiredChild attributes
statusstringrequiredpartial: some variants could not be written (seeerrors), the rest was.skipped: the key's field rule left nothing to write.One ofcreatedupdatedpartialskippederrorsarray of objectrequiredAttributes of each item
variant_skustring | nullmessagestring
notesarray of stringrequiredFields not written and why (field rule, field owned by another system).
400The request must change:
validation_failed(seedetails, one list of messages per field),invalid_json,invalid_cursor,product_not_written,variant_not_written,not_written,image_not_downloadable. application/jsonerrorobjectrequiredChild attributes
codestringrequiredStable machine-readable code. Branch on it, not on the message.
messagestringrequiredrequest_idstringrequiredSame as the
X-Request-Idheader. Quote it to support.detailsobject
401
unauthorized: the key is missing, wrong or revoked.application/jsonerrorobjectrequiredChild attributes
codestringrequiredStable machine-readable code. Branch on it, not on the message.
messagestringrequiredrequest_idstringrequiredSame as the
X-Request-Idheader. Quote it to support.detailsobject
403
read_only_key: this key cannot write.application/jsonerrorobjectrequiredChild attributes
codestringrequiredStable machine-readable code. Branch on it, not on the message.
messagestringrequiredrequest_idstringrequiredSame as the
X-Request-Idheader. Quote it to support.detailsobject
409
field_rule(the key's field rule does not allow this change),field_locked(the change touches fields another connected system owns;details.lockedlists them),idempotency_key_reused,idempotency_key_in_progress,webhook_limit. application/jsonerrorobjectrequiredChild attributes
codestringrequiredStable machine-readable code. Branch on it, not on the message.
messagestringrequiredrequest_idstringrequiredSame as the
X-Request-Idheader. Quote it to support.detailsobject
429
rate_limited: wait the seconds inRetry-After.application/jsonHeadersRetry-AfterSeconds to wait.
errorobjectrequiredChild attributes
codestringrequiredStable machine-readable code. Branch on it, not on the message.
messagestringrequiredrequest_idstringrequiredSame as the
X-Request-Idheader. Quote it to support.detailsobject
curl -X POST https://api-catalog.nucleoplatform.com/api/catalog/v1/products \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Idempotency-Key: 2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44' \
-H 'Content-Type: application/json' \
-d '{
"sku": "TEE-001",
"external_id": "erp-778",
"title": "Basic tee",
"description": "<p>Organic cotton.</p>",
"status": "active",
"brand": "Acme",
"product_type": "T-shirt",
"category": "Clothing > Tops",
"attributes": {
"color": "red",
"fit": "regular"
},
"translations": {
"it": {
"title": "Maglietta basic",
"description": "<p>Cotone biologico.</p>"
}
},
"image_urls": [
"https://cdn.acme.example/tee-001-front.jpg"
],
"variants": [
{
"sku": "TEE-001-M",
"barcode": "8001234567890",
"size": "M",
"weight": 0.2,
"weight_unit": "kg",
"prices": [
{
"price_list": "eu",
"price": 19.9,
"sale_price": 15
}
],
"stock": [
{
"location": "main",
"available": 7
}
]
}
]
}'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/products", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.NUCLEO_ACCESS_TOKEN}`,
"Idempotency-Key": "2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44",
"Content-Type": "application/json",
},
body: JSON.stringify({
"sku": "TEE-001",
"external_id": "erp-778",
"title": "Basic tee",
"description": "<p>Organic cotton.</p>",
"status": "active",
"brand": "Acme",
"product_type": "T-shirt",
"category": "Clothing > Tops",
"attributes": {
"color": "red",
"fit": "regular"
},
"translations": {
"it": {
"title": "Maglietta basic",
"description": "<p>Cotone biologico.</p>"
}
},
"image_urls": [
"https://cdn.acme.example/tee-001-front.jpg"
],
"variants": [
{
"sku": "TEE-001-M",
"barcode": "8001234567890",
"size": "M",
"weight": 0.2,
"weight_unit": "kg",
"prices": [
{
"price_list": "eu",
"price": 19.9,
"sale_price": 15
}
],
"stock": [
{
"location": "main",
"available": 7
}
]
}
]
}),
});
const data = await res.json();
console.log(res.status, data);<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$response = $client->request('POST', 'https://api-catalog.nucleoplatform.com/api/catalog/v1/products', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Idempotency-Key' => '2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44',
],
'json' => [
'sku' => 'TEE-001',
'external_id' => 'erp-778',
'title' => 'Basic tee',
'description' => '<p>Organic cotton.</p>',
'status' => 'active',
'brand' => 'Acme',
'product_type' => 'T-shirt',
'category' => 'Clothing > Tops',
'attributes' => [
'color' => 'red',
'fit' => 'regular',
],
'translations' => [
'it' => [
'title' => 'Maglietta basic',
'description' => '<p>Cotone biologico.</p>',
],
],
'image_urls' => [
'https://cdn.acme.example/tee-001-front.jpg',
],
'variants' => [
[
'sku' => 'TEE-001-M',
'barcode' => '8001234567890',
'size' => 'M',
'weight' => 0.2,
'weight_unit' => 'kg',
'prices' => [
[
'price_list' => 'eu',
'price' => 19.9,
'sale_price' => 15,
],
],
'stock' => [
[
'location' => 'main',
'available' => 7,
],
],
],
],
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"data": {
"id": 1,
"sku": "string",
"external_id": "string",
"title": "string",
"description": "string",
"handle": "string",
"status": "string",
"product_type": "string",
"brand": "string",
"attribute_set": "string",
"model_code": "string",
"gender": "string",
"season": "string",
"color": "string",
"material": "string",
"made_in": "string",
"composition": "string",
"categories": [
{
"id": 1,
"name": "string",
"path": "Clothing > Tops"
}
],
"attributes": {},
"translations": {},
"images": [
{
"id": 1,
"url": "string",
"alt": "string",
"position": 1,
"width": 1,
"height": 1
}
],
"variants": [
{
"id": 1,
"sku": "string",
"barcode": "string",
"options": {},
"cost": "string",
"weight": 1.5,
"weight_unit": "string",
"prices": [
{
"price_list": "string",
"currency": "string",
"price": "19.90",
"sale_price": "string"
}
],
"stock": [
{
"location": "string",
"available": 1,
"on_hand": 1
}
]
}
],
"updated_at": "2026-10-01T09:30:00Z"
},
"result": {
"status": "created",
"errors": [
{
"variant_sku": "string",
"message": "string"
}
],
"notes": [
"string"
]
}
}{
"data": {
"id": 1,
"sku": "string",
"external_id": "string",
"title": "string",
"description": "string",
"handle": "string",
"status": "string",
"product_type": "string",
"brand": "string",
"attribute_set": "string",
"model_code": "string",
"gender": "string",
"season": "string",
"color": "string",
"material": "string",
"made_in": "string",
"composition": "string",
"categories": [
{
"id": 1,
"name": "string",
"path": "Clothing > Tops"
}
],
"attributes": {},
"translations": {},
"images": [
{
"id": 1,
"url": "string",
"alt": "string",
"position": 1,
"width": 1,
"height": 1
}
],
"variants": [
{
"id": 1,
"sku": "string",
"barcode": "string",
"options": {},
"cost": "string",
"weight": 1.5,
"weight_unit": "string",
"prices": [
{
"price_list": "string",
"currency": "string",
"price": "19.90",
"sale_price": "string"
}
],
"stock": [
{
"location": "string",
"available": 1,
"on_hand": 1
}
]
}
],
"updated_at": "2026-10-01T09:30:00Z"
},
"result": {
"status": "created",
"errors": [
{
"variant_sku": "string",
"message": "string"
}
],
"notes": [
"string"
]
}
}{
"error": {
"code": "validation_failed",
"message": "The request is not valid: see details.",
"request_id": "7c0f8a1e-3b2d-4e5f-9a6b-1c2d3e4f5a6b",
"details": {
"variants.0.prices.0.price_list": [
"Unknown price list \"usa\". GET /price-lists lists them."
]
}
}
}{
"error": {
"code": "string",
"message": "string",
"request_id": "string",
"details": {}
}
}{
"error": {
"code": "string",
"message": "string",
"request_id": "string",
"details": {}
}
}{
"error": {
"code": "string",
"message": "string",
"request_id": "string",
"details": {}
}
}{
"error": {
"code": "string",
"message": "string",
"request_id": "string",
"details": {}
}
}/products/{product}Get a product
Path parameters
productstringrequiredProduct id or SKU.
Example:TEE-001
Responses
200The product.application/json
dataProductrequiredChild attributes
idintegerskustringexternal_idstring | nullThe id this key gave the product.
titlestring | nulldescriptionstring | nullhandlestring | nullstatusstring | nullproduct_typestring | nullbrandstring | nullattribute_setstring | nullmodel_codestring | nullgenderstring | nullseasonstring | nullcolorstring | nullmaterialstring | nullmade_instring | nullcompositionstring | nullcategoriesarray of ProductCategoryAttributes of each item
idintegernamestringpathstringExample:Clothing > Tops
attributesobjectValues by attribute code. Options as their code; several options as their codes, comma separated.
translationsobjectLocales other than the default one.
imagesarray of ImageAttributes of each item
idintegerurlstringaltstring | nullpositionintegerwidthinteger | nullheightinteger | null
variantsarray of VariantAttributes of each item
idintegerskustringbarcodestring | nulloptionsobjectThe options that tell variants apart: attribute code → option code (
{"size": "m"}). Option codes are normalised, so a size sent asMcomes back asm.coststring | nullweightnumber | nullweight_unitstring | nullpricesarray of objectAttributes of each item
price_liststringcurrencystring | nullpricestring | nullExample:19.90sale_pricestring | null
stockarray of objectAttributes of each item
locationstringavailableintegeron_handinteger
updated_atstring (date-time) | nullWhen the product last changed;
nullif it has not changed since this field exists.
404
product_not_found,variant_not_found,image_not_found,import_not_found,webhook_not_found, ornot_foundfor a path that does not exist.application/jsonerrorobjectrequiredChild attributes
codestringrequiredStable machine-readable code. Branch on it, not on the message.
messagestringrequiredrequest_idstringrequiredSame as the
X-Request-Idheader. Quote it to support.detailsobject
curl -X GET https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001 \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Accept: application/json'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.NUCLEO_ACCESS_TOKEN}`,
Accept: "application/json",
},
});
const data = await res.json();
console.log(res.status, data);<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$response = $client->request('GET', 'https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Accept' => 'application/json',
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"data": {
"id": 1,
"sku": "string",
"external_id": "string",
"title": "string",
"description": "string",
"handle": "string",
"status": "string",
"product_type": "string",
"brand": "string",
"attribute_set": "string",
"model_code": "string",
"gender": "string",
"season": "string",
"color": "string",
"material": "string",
"made_in": "string",
"composition": "string",
"categories": [
{
"id": 1,
"name": "string",
"path": "Clothing > Tops"
}
],
"attributes": {},
"translations": {},
"images": [
{
"id": 1,
"url": "string",
"alt": "string",
"position": 1,
"width": 1,
"height": 1
}
],
"variants": [
{
"id": 1,
"sku": "string",
"barcode": "string",
"options": {},
"cost": "string",
"weight": 1.5,
"weight_unit": "string",
"prices": [
{
"price_list": "string",
"currency": "string",
"price": "19.90",
"sale_price": "string"
}
],
"stock": [
{
"location": "string",
"available": 1,
"on_hand": 1
}
]
}
],
"updated_at": "2026-10-01T09:30:00Z"
}
}{
"error": {
"code": "string",
"message": "string",
"request_id": "string",
"details": {}
}
}/products/{product}Update a product
Writes only the fields you send; everything else stays as it is. The SKU cannot change through the
API (sending a different sku is a 400).
Path parameters
productstringrequiredProduct id or SKU.
Example:TEE-001
Headers
Idempotency-KeystringAny string of 1–255 printable ASCII characters, unique per change you mean (a UUID is fine). Sending the same request again with the same key within 48 hours returns the first answer without doing the work again (header
Idempotent-Replayed: true). The same key with a different request is a409 idempotency_key_reused; while the first request is still running,409 idempotency_key_in_progresswithRetry-After: 1. Answers429and5xxare not stored: retry them with the same key.max length 255Example:2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44
Request bodyapplication/json
skustringmax length 255pattern ^[A-Za-z0-9][A-Za-z0-9._-]*$external_idstringYour id for the product. Remembered per key: later calls can send it instead of
sku.max length 255titlestringmax length 255descriptionstringHTML; unsafe markup is removed.
max length 65000statusstringA status code of the catalog (for example
active,draft). An unknown code is an error.product_typestringCreated if new.
max length 255brandstringCreated if new.
max length 255categorystringA category path (
Clothing > Tops): added to the product's categories, created if new. To replace all categories usePUT /products/{product}/categories.max length 500model_codestringmax length 255genderstringmax length 255seasonstringOne or more season codes, comma separated (
SS25, FW25).max length 255colorstringmax length 255materialstringmax length 255made_instringISO country code or country name.
max length 255compositionstringmax length 2000attribute_setstringName or id of the attribute set.
attributesobjectValues by attribute code (
GET /attributes). An unknown code is an error.translationsobjectBy locale code (
GET /locales). An unknown locale is an error.image_urlsarray of string (uri)Linked as they are (not downloaded); a URL already on the product is not added again. To store a copy in Nucleo, use
POST /products/{product}/images.max items 50variantsarray of VariantInputmax items 500Attributes of each item
skustringrequiredUnique in the whole catalog.
max length 255pattern ^[A-Za-z0-9][A-Za-z0-9._-]*$barcodestringUnique in the whole catalog.
max length 255sizestringSize code; becomes the variant's size option (read back in
options, as a normalised option code).max length 255costnumbermin 0weightnumbermin 0weight_unitstringOne ofgkglbozpricesarray of PriceInputAttributes of each item
price_liststringrequiredPrice list code (
GET /price-lists).pricenumberrequiredmin 0sale_pricenumber | nullDiscounted price on that list.
min 0
stockarray of StockInputAttributes of each item
locationstringrequiredLocation code (
GET /locations).availableintegerrequiredReplaces the quantity at that location.
Responses
200The product as it is now, and what happened.application/json
dataProductrequiredChild attributes
idintegerskustringexternal_idstring | nullThe id this key gave the product.
titlestring | nulldescriptionstring | nullhandlestring | nullstatusstring | nullproduct_typestring | nullbrandstring | nullattribute_setstring | nullmodel_codestring | nullgenderstring | nullseasonstring | nullcolorstring | nullmaterialstring | nullmade_instring | nullcompositionstring | nullcategoriesarray of ProductCategoryAttributes of each item
idintegernamestringpathstringExample:Clothing > Tops
attributesobjectValues by attribute code. Options as their code; several options as their codes, comma separated.
translationsobjectLocales other than the default one.
imagesarray of ImageAttributes of each item
idintegerurlstringaltstring | nullpositionintegerwidthinteger | nullheightinteger | null
variantsarray of VariantAttributes of each item
idintegerskustringbarcodestring | nulloptionsobjectThe options that tell variants apart: attribute code → option code (
{"size": "m"}). Option codes are normalised, so a size sent asMcomes back asm.coststring | nullweightnumber | nullweight_unitstring | nullpricesarray of objectAttributes of each item
price_liststringcurrencystring | nullpricestring | nullExample:19.90sale_pricestring | null
stockarray of objectAttributes of each item
locationstringavailableintegeron_handinteger
updated_atstring (date-time) | nullWhen the product last changed;
nullif it has not changed since this field exists.
resultWriteResultrequiredChild attributes
statusstringrequiredpartial: some variants could not be written (seeerrors), the rest was.skipped: the key's field rule left nothing to write.One ofcreatedupdatedpartialskippederrorsarray of objectrequiredAttributes of each item
variant_skustring | nullmessagestring
notesarray of stringrequiredFields not written and why (field rule, field owned by another system).
400The request must change:
validation_failed(seedetails, one list of messages per field),invalid_json,invalid_cursor,product_not_written,variant_not_written,not_written,image_not_downloadable. application/jsonerrorobjectrequiredChild attributes
codestringrequiredStable machine-readable code. Branch on it, not on the message.
messagestringrequiredrequest_idstringrequiredSame as the
X-Request-Idheader. Quote it to support.detailsobject
403
read_only_key: this key cannot write.application/jsonerrorobjectrequiredChild attributes
codestringrequiredStable machine-readable code. Branch on it, not on the message.
messagestringrequiredrequest_idstringrequiredSame as the
X-Request-Idheader. Quote it to support.detailsobject
404
product_not_found,variant_not_found,image_not_found,import_not_found,webhook_not_found, ornot_foundfor a path that does not exist.application/jsonerrorobjectrequiredChild attributes
codestringrequiredStable machine-readable code. Branch on it, not on the message.
messagestringrequiredrequest_idstringrequiredSame as the
X-Request-Idheader. Quote it to support.detailsobject
409
field_rule(the key's field rule does not allow this change),field_locked(the change touches fields another connected system owns;details.lockedlists them),idempotency_key_reused,idempotency_key_in_progress,webhook_limit. application/jsonerrorobjectrequiredChild attributes
codestringrequiredStable machine-readable code. Branch on it, not on the message.
messagestringrequiredrequest_idstringrequiredSame as the
X-Request-Idheader. Quote it to support.detailsobject
curl -X PATCH https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001 \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Idempotency-Key: 2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44' \
-H 'Content-Type: application/json' \
-d '{
"title": "Basic tee — new season",
"status": "active"
}'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.NUCLEO_ACCESS_TOKEN}`,
"Idempotency-Key": "2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44",
"Content-Type": "application/json",
},
body: JSON.stringify({
"title": "Basic tee — new season",
"status": "active"
}),
});
const data = await res.json();
console.log(res.status, data);<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$response = $client->request('PATCH', 'https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Idempotency-Key' => '2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44',
],
'json' => [
'title' => 'Basic tee — new season',
'status' => 'active',
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"data": {
"id": 1,
"sku": "string",
"external_id": "string",
"title": "string",
"description": "string",
"handle": "string",
"status": "string",
"product_type": "string",
"brand": "string",
"attribute_set": "string",
"model_code": "string",
"gender": "string",
"season": "string",
"color": "string",
"material": "string",
"made_in": "string",
"composition": "string",
"categories": [
{
"id": 1,
"name": "string",
"path": "Clothing > Tops"
}
],
"attributes": {},
"translations": {},
"images": [
{
"id": 1,
"url": "string",
"alt": "string",
"position": 1,
"width": 1,
"height": 1
}
],
"variants": [
{
"id": 1,
"sku": "string",
"barcode": "string",
"options": {},
"cost": "string",
"weight": 1.5,
"weight_unit": "string",
"prices": [
{
"price_list": "string",
"currency": "string",
"price": "19.90",
"sale_price": "string"
}
],
"stock": [
{
"location": "string",
"available": 1,
"on_hand": 1
}
]
}
],
"updated_at": "2026-10-01T09:30:00Z"
},
"result": {
"status": "created",
"errors": [
{
"variant_sku": "string",
"message": "string"
}
],
"notes": [
"string"
]
}
}{
"error": {
"code": "validation_failed",
"message": "The request is not valid: see details.",
"request_id": "7c0f8a1e-3b2d-4e5f-9a6b-1c2d3e4f5a6b",
"details": {
"variants.0.prices.0.price_list": [
"Unknown price list \"usa\". GET /price-lists lists them."
]
}
}
}{
"error": {
"code": "string",
"message": "string",
"request_id": "string",
"details": {}
}
}{
"error": {
"code": "string",
"message": "string",
"request_id": "string",
"details": {}
}
}{
"error": {
"code": "string",
"message": "string",
"request_id": "string",
"details": {}
}
}/products/{product}Delete a product
Moves the product to Nucleo's trash, from where it can be restored for 30 days. Its SKU becomes free
for a new product. Not allowed with the field rules inserimento and nucleo (409 field_rule).
Path parameters
productstringrequiredProduct id or SKU.
Example:TEE-001
Headers
Idempotency-KeystringAny string of 1–255 printable ASCII characters, unique per change you mean (a UUID is fine). Sending the same request again with the same key within 48 hours returns the first answer without doing the work again (header
Idempotent-Replayed: true). The same key with a different request is a409 idempotency_key_reused; while the first request is still running,409 idempotency_key_in_progresswithRetry-After: 1. Answers429and5xxare not stored: retry them with the same key.max length 255Example:2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44
Responses
204Moved to the trash.
403
read_only_key: this key cannot write.application/jsonerrorobjectrequiredChild attributes
codestringrequiredStable machine-readable code. Branch on it, not on the message.
messagestringrequiredrequest_idstringrequiredSame as the
X-Request-Idheader. Quote it to support.detailsobject
404
product_not_found,variant_not_found,image_not_found,import_not_found,webhook_not_found, ornot_foundfor a path that does not exist.application/jsonerrorobjectrequiredChild attributes
codestringrequiredStable machine-readable code. Branch on it, not on the message.
messagestringrequiredrequest_idstringrequiredSame as the
X-Request-Idheader. Quote it to support.detailsobject
409
field_rule(the key's field rule does not allow this change),field_locked(the change touches fields another connected system owns;details.lockedlists them),idempotency_key_reused,idempotency_key_in_progress,webhook_limit. application/jsonerrorobjectrequiredChild attributes
codestringrequiredStable machine-readable code. Branch on it, not on the message.
messagestringrequiredrequest_idstringrequiredSame as the
X-Request-Idheader. Quote it to support.detailsobject
curl -X DELETE https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001 \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Idempotency-Key: 2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.NUCLEO_ACCESS_TOKEN}`,
"Idempotency-Key": "2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44",
},
});
const data = await res.json();
console.log(res.status, data);<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$response = $client->request('DELETE', 'https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Idempotency-Key' => '2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44',
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();HTTP 204 — Moved to the trash.{
"error": {
"code": "string",
"message": "string",
"request_id": "string",
"details": {}
}
}{
"error": {
"code": "string",
"message": "string",
"request_id": "string",
"details": {}
}
}{
"error": {
"code": "string",
"message": "string",
"request_id": "string",
"details": {}
}
}