Contents
Bulk
Many products in one call. POST /products/batch (up to 100) answers with the result of each product;
POST /imports (up to 5,000) is queued and its progress is read with GET /imports/{id}. Both go
through the same checks: an invalid product is reported and skipped, the others are written.
Base URL, authentication and conventions of this API are on the Catalog API page.
/products/batchCreate or update up to 100 products
Each product is checked and written on its own, as with POST /products: an invalid product comes
back as invalid with its errors and the others are written. A SKU repeated in the same request is
invalid (it would not be clear which version wins). The answer lists the products in the order sent.
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
productsarray of ProductInputrequiredmin items 1max items 100Attributes of each item
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 result of each product.application/json
dataarray of ItemResultrequiredAttributes of each item
indexintegerrequiredPosition in the request, from 0.
skustring | nullrequiredproduct_idinteger | nullstatusstringrequiredinvalid: not written because the input is wrong.failed: nothing could be written.One ofcreatedupdatedpartialskippedfailedinvaliderrorsarray of objectrequiredAttributes of each item
fieldstringFor
invalid, the field in error.variant_skustring | nullmessagestring
notesarray of stringrequired
summarySummaryrequiredChild attributes
createdintegerupdatedintegerpartialintegerskippedintegerfailedintegerinvalidinteger
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
curl -X POST https://api-catalog.nucleoplatform.com/api/catalog/v1/products/batch \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Idempotency-Key: 2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44' \
-H 'Content-Type: application/json' \
-d '{
"products": [
{
"sku": "TEE-001",
"title": "Basic tee",
"variants": [
{
"sku": "TEE-001-M",
"prices": [
{
"price_list": "eu",
"price": 19.9
}
]
}
]
},
{
"sku": "TEE-002",
"title": "Pocket tee"
}
]
}'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/products/batch", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.NUCLEO_ACCESS_TOKEN}`,
"Idempotency-Key": "2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44",
"Content-Type": "application/json",
},
body: JSON.stringify({
"products": [
{
"sku": "TEE-001",
"title": "Basic tee",
"variants": [
{
"sku": "TEE-001-M",
"prices": [
{
"price_list": "eu",
"price": 19.9
}
]
}
]
},
{
"sku": "TEE-002",
"title": "Pocket tee"
}
]
}),
});
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/batch', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Idempotency-Key' => '2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44',
],
'json' => [
'products' => [
[
'sku' => 'TEE-001',
'title' => 'Basic tee',
'variants' => [
[
'sku' => 'TEE-001-M',
'prices' => [
[
'price_list' => 'eu',
'price' => 19.9,
],
],
],
],
],
[
'sku' => 'TEE-002',
'title' => 'Pocket tee',
],
],
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"data": [
{
"index": 0,
"sku": "TEE-001",
"product_id": 1201,
"status": "created",
"errors": [],
"notes": []
},
{
"index": 1,
"sku": "TEE-002",
"product_id": 1202,
"status": "updated",
"errors": [],
"notes": []
}
],
"summary": {
"created": 1,
"updated": 1,
"partial": 0,
"skipped": 0,
"failed": 0,
"invalid": 0
}
}{
"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": {}
}
}/importsQueue an import of up to 5,000 products
The products are checked at once (invalid ones are reported in the results and not written), then
written in the background in groups of 100. Follow the progress with GET /imports/{id}. Usually it
starts within a minute.
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
productsarray of ProductInputrequiredmin items 1max items 5000Attributes of each item
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
202Accepted and queued.application/json
HeadersLocationWhere to follow the import.
dataImportrequiredChild attributes
idstring (uuid)requiredstatusstringrequiredOne ofqueuedprocessingdonepartialfailedtotalintegerrequiredprocessedintegerrequiredfailedintegerrequiredProducts
invalidorfailed.created_atstring (date-time)started_atstring (date-time) | nullfinished_atstring (date-time) | null
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
413
payload_too_large: the body is larger than 20 MB.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 POST https://api-catalog.nucleoplatform.com/api/catalog/v1/imports \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Idempotency-Key: 2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44' \
-H 'Content-Type: application/json' \
-d '{
"products": [
{
"sku": "string",
"external_id": "string",
"title": "string",
"description": "string",
"status": "string",
"product_type": "string",
"brand": "string",
"category": "string",
"model_code": "string",
"gender": "string",
"season": "string",
"color": "string",
"material": "string",
"made_in": "string",
"composition": "string",
"attribute_set": "string",
"attributes": {},
"translations": {},
"image_urls": [
"https://shop.acme.example"
],
"variants": [
{
"sku": "string",
"barcode": "string",
"size": "string",
"cost": 0,
"weight": 0,
"weight_unit": "g",
"prices": [
{}
],
"stock": [
{}
]
}
]
}
]
}'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/imports", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.NUCLEO_ACCESS_TOKEN}`,
"Idempotency-Key": "2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44",
"Content-Type": "application/json",
},
body: JSON.stringify({
"products": [
{
"sku": "string",
"external_id": "string",
"title": "string",
"description": "string",
"status": "string",
"product_type": "string",
"brand": "string",
"category": "string",
"model_code": "string",
"gender": "string",
"season": "string",
"color": "string",
"material": "string",
"made_in": "string",
"composition": "string",
"attribute_set": "string",
"attributes": {},
"translations": {},
"image_urls": [
"https://shop.acme.example"
],
"variants": [
{
"sku": "string",
"barcode": "string",
"size": "string",
"cost": 0,
"weight": 0,
"weight_unit": "g",
"prices": [
{}
],
"stock": [
{}
]
}
]
}
]
}),
});
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/imports', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Idempotency-Key' => '2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44',
],
'json' => [
'products' => [
[
'sku' => 'string',
'external_id' => 'string',
'title' => 'string',
'description' => 'string',
'status' => 'string',
'product_type' => 'string',
'brand' => 'string',
'category' => 'string',
'model_code' => 'string',
'gender' => 'string',
'season' => 'string',
'color' => 'string',
'material' => 'string',
'made_in' => 'string',
'composition' => 'string',
'attribute_set' => 'string',
'attributes' => (object) [],
'translations' => (object) [],
'image_urls' => [
'https://shop.acme.example',
],
'variants' => [
[
'sku' => 'string',
'barcode' => 'string',
'size' => 'string',
'cost' => 0,
'weight' => 0,
'weight_unit' => 'g',
'prices' => [
(object) [],
],
'stock' => [
(object) [],
],
],
],
],
],
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"data": {
"id": "9b2f4c1e-5d7a-4e8b-9c3d-2a1f0e6b7c8d",
"status": "queued",
"total": 1,
"processed": 1,
"failed": 1,
"created_at": "2026-10-01T09:30:00Z",
"started_at": "2026-10-01T09:30:00Z",
"finished_at": "2026-10-01T09:30:00Z"
}
}{
"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": {}
}
}/imports/{import}Follow an import
Progress and, product by product, the results so far. Only imports created with the same key.
Path parameters
importstring (uuid)required
Responses
200The import.application/json
dataImport & objectrequiredChild attributes
idstring (uuid)requiredstatusstringrequiredOne ofqueuedprocessingdonepartialfailedtotalintegerrequiredprocessedintegerrequiredfailedintegerrequiredProducts
invalidorfailed.created_atstring (date-time)started_atstring (date-time) | nullfinished_atstring (date-time) | nullresultsarray of ItemResultAttributes of each item
indexintegerrequiredPosition in the request, from 0.
skustring | nullrequiredproduct_idinteger | nullstatusstringrequiredinvalid: not written because the input is wrong.failed: nothing could be written.One ofcreatedupdatedpartialskippedfailedinvaliderrorsarray of objectrequiredAttributes of each item
fieldstringFor
invalid, the field in error.variant_skustring | nullmessagestring
notesarray of stringrequired
summarySummaryChild attributes
createdintegerupdatedintegerpartialintegerskippedintegerfailedintegerinvalidinteger
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/imports/{import}' \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Accept: application/json'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/imports/{import}", {
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/imports/{import}', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Accept' => 'application/json',
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"data": {
"id": "9b2f4c1e-5d7a-4e8b-9c3d-2a1f0e6b7c8d",
"status": "queued",
"total": 1,
"processed": 1,
"failed": 1,
"created_at": "2026-10-01T09:30:00Z",
"started_at": "2026-10-01T09:30:00Z",
"finished_at": "2026-10-01T09:30:00Z",
"results": [
{
"index": 1,
"sku": "string",
"product_id": 1,
"status": "created",
"errors": [
{
"field": "string",
"variant_sku": "string",
"message": "string"
}
],
"notes": [
"string"
]
}
],
"summary": {
"created": 1,
"updated": 1,
"partial": 1,
"skipped": 1,
"failed": 1,
"invalid": 1
}
}
}{
"error": {
"code": "string",
"message": "string",
"request_id": "string",
"details": {}
}
}