Contents
Catalog API
Load products into a Nucleo catalog, read them back, and get told when they change.
The Catalog API lets the software of another company — an ERP, a PLM, a supplier portal, a marketplace connector — keep products in sync with one Nucleo catalog:
- Load new products and update existing ones, matched by SKU or by your own id (
external_id), with variants, prices, stock, attributes, categories, translations and images. - Load in bulk: up to 100 products per call with a result per product, or up to 5,000 in a queued import.
- Download the whole catalog page by page, then only what changed since your last read (
updated_since). - Get notified by signed webhooks when a product is created, changed or deleted in Nucleo, or when stock changes.
- Read the structures a product refers to: categories, attributes and their allowed values, locales, markets, price lists and locations.
Authentication. A key per catalog, sent as Authorization: Bearer nck_…. An owner or admin of the
catalog creates it in Nucleo; the key is shown once. A key is either read only or read and write, and
it only ever sees its own catalog: there is no header to pick a company or a catalog.
Writes go through the same import as every other source. Products are matched by SKU (upsert: the
same payload sent twice does not create duplicates). Each key has one field rule, chosen in Nucleo
when the key is created: sorgente (the API always updates the fields it sends), inserimento (the API
only fills fields that are empty in Nucleo) or nucleo (the API does not change existing fields). Fields
that another connected system owns are never overwritten: the call succeeds and the result says what
was left out. Every write appears in the product's Activity in Nucleo, with the API and the key's
name as the author. Writing through the API never publishes anything to Shopify or other sales channels.
Conventions. JSON in and out, UTF-8. Dates are ISO 8601 in UTC (2026-10-14T08:00:00Z). Amounts are
strings with two decimals ("19.90"); on input, numbers or numeric strings. Errors always have the same
shape ({"error": {"code", "message", "request_id", "details"}}), in English. Every response carries
X-Request-Id and Nucleo-Api-Version: v1. Fields not sent are left as they are: an absent or empty
value never clears a field.
Authentication
catalogKeyHTTP bearer (nck_…)A Catalog API key (
nck_followed by 48 characters). An owner or admin of the catalog creates it in Nucleo, chooses whether it is read only or read and write and its field rule, and sees it once. Revoking it in Nucleo stops it at once. Thirty failed attempts from the same address within five minutes block that address for five minutes (429).
- Base URL
- https://api-catalog.nucleoplatform.com/api/catalog/v1Production. There is no sandbox.
- Who calls it
- Partners
- Endpoints
- 34 + 1 webhook
- OpenAPI 3.1 specification
- catalog.yaml
Key
Check that a key works, and for which catalog.
/pingCheck the key
Which company, catalog and key the request is authenticated as, and the server time.
Responses
200The key works.application/json
dataPingrequiredChild attributes
api_version"v1"companyobjectChild attributes
idinteger | null
catalogobjectChild attributes
idintegernamestring
keyobjectChild attributes
idstring (uuid)namestringlevelstringOne ofreadwritefield_rulestringsorgente: the API always updates the fields it sends. People in Nucleo can no longer edit the products it wrote.inserimento: the API only fills fields that are empty in Nucleo.nucleo: the API does not change existing fields.One ofsorgenteinserimentonucleorate_limit_per_minuteinteger
webhooksintegerActive webhooks of this key.
server_timestring (date-time)
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/ping \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Accept: application/json'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/ping", {
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/ping', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Accept' => 'application/json',
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"data": {
"api_version": "v1",
"company": {
"id": 12
},
"catalog": {
"id": 4,
"name": "Acme"
},
"key": {
"id": "9b2f1c6e-3a4d-4f5e-8a7b-1c2d3e4f5a6b",
"name": "ERP sync",
"level": "write",
"field_rule": "sorgente",
"rate_limit_per_minute": 600
},
"webhooks": 1,
"server_time": "2026-10-14T08:00:00Z"
}
}{
"error": {
"code": "string",
"message": "string",
"request_id": "string",
"details": {}
}
}{
"error": {
"code": "string",
"message": "string",
"request_id": "string",
"details": {}
}
}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).
/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": {}
}
}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.
/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": {}
}
}Variants
Variants of a product, addressed by their id or sku.
/products/{product}/variantsList the variants of a product
Path parameters
productstringrequiredProduct id or SKU.
Example:TEE-001
Responses
200The variants.application/json
dataarray of VariantrequiredAttributes 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
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/variants \
-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/variants", {
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/variants', [
'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",
"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
}
]
}
]
}{
"error": {
"code": "string",
"message": "string",
"request_id": "string",
"details": {}
}
}/products/{product}/variantsAdd or update a variant
Creates the variant (201) or updates the variant of this product with the same SKU (200).
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
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 variant as it is now, and what happened.application/json
dataVariantrequiredChild attributes
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
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 variant as it is now, and what happened.application/json
dataVariantrequiredChild attributes
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
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
curl -X POST https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001/variants \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Idempotency-Key: 2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44' \
-H 'Content-Type: application/json' \
-d '{
"sku": "string",
"barcode": "string",
"size": "string",
"cost": 0,
"weight": 0,
"weight_unit": "g",
"prices": [
{
"price_list": "string",
"price": 0,
"sale_price": 0
}
],
"stock": [
{
"location": "string",
"available": 1
}
]
}'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001/variants", {
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": "string",
"barcode": "string",
"size": "string",
"cost": 0,
"weight": 0,
"weight_unit": "g",
"prices": [
{
"price_list": "string",
"price": 0,
"sale_price": 0
}
],
"stock": [
{
"location": "string",
"available": 1
}
]
}),
});
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/TEE-001/variants', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Idempotency-Key' => '2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44',
],
'json' => [
'sku' => 'string',
'barcode' => 'string',
'size' => 'string',
'cost' => 0,
'weight' => 0,
'weight_unit' => 'g',
'prices' => [
[
'price_list' => 'string',
'price' => 0,
'sale_price' => 0,
],
],
'stock' => [
[
'location' => 'string',
'available' => 1,
],
],
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"data": {
"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
}
]
},
"result": {
"status": "created",
"errors": [
{
"variant_sku": "string",
"message": "string"
}
],
"notes": [
"string"
]
}
}{
"data": {
"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
}
]
},
"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": {}
}
}/products/{product}/variants/{variant}Update a variant
Only the fields sent change. The SKU cannot change through the API.
Path parameters
productstringrequiredProduct id or SKU.
Example:TEE-001variantstringrequiredVariant id or SKU.
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
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 variant as it is now, and what happened.application/json
dataVariantrequiredChild attributes
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
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
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 PATCH 'https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001/variants/{variant}' \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Idempotency-Key: 2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44' \
-H 'Content-Type: application/json' \
-d '{
"sku": "string",
"barcode": "string",
"size": "string",
"cost": 0,
"weight": 0,
"weight_unit": "g",
"prices": [
{
"price_list": "string",
"price": 0,
"sale_price": 0
}
],
"stock": [
{
"location": "string",
"available": 1
}
]
}'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001/variants/{variant}", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.NUCLEO_ACCESS_TOKEN}`,
"Idempotency-Key": "2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44",
"Content-Type": "application/json",
},
body: JSON.stringify({
"sku": "string",
"barcode": "string",
"size": "string",
"cost": 0,
"weight": 0,
"weight_unit": "g",
"prices": [
{
"price_list": "string",
"price": 0,
"sale_price": 0
}
],
"stock": [
{
"location": "string",
"available": 1
}
]
}),
});
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/variants/{variant}', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Idempotency-Key' => '2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44',
],
'json' => [
'sku' => 'string',
'barcode' => 'string',
'size' => 'string',
'cost' => 0,
'weight' => 0,
'weight_unit' => 'g',
'prices' => [
[
'price_list' => 'string',
'price' => 0,
'sale_price' => 0,
],
],
'stock' => [
[
'location' => 'string',
'available' => 1,
],
],
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"data": {
"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
}
]
},
"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": {}
}
}/products/{product}/variants/{variant}Delete a variant
Deletes the variant with its prices and stock. Not allowed with the field rules inserimento and nucleo.
Path parameters
productstringrequiredProduct id or SKU.
Example:TEE-001variantstringrequiredVariant id or SKU.
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
204Deleted.
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/variants/{variant}' \
-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/variants/{variant}", {
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/variants/{variant}', [
'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 — Deleted.{
"error": {
"code": "string",
"message": "string",
"request_id": "string",
"details": {}
}
}{
"error": {
"code": "string",
"message": "string",
"request_id": "string",
"details": {}
}
}Prices and stock
The two most frequent updates, per product or in bulk by variant SKU (up to 1,000 lines). Only the price lists and locations you send change.
/products/{product}/pricesSet prices of a product
One line per variant and price list (up to 500). Price lists not sent do not change.
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
pricesarray of object & PriceInputrequiredmin items 1max items 500Attributes of each item
variant_skustringrequiredSKU of a variant of this product.
price_liststringrequiredPrice list code (
GET /price-lists).pricenumberrequiredmin 0sale_pricenumber | nullDiscounted price on that list.
min 0
Responses
200All variants of the product as they are now, and what happened.application/json
dataarray of VariantrequiredAttributes 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
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
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 PUT https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001/prices \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Idempotency-Key: 2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44' \
-H 'Content-Type: application/json' \
-d '{
"prices": [
{
"variant_sku": "TEE-001-M",
"price_list": "eu",
"price": 19.9,
"sale_price": 15
},
{
"variant_sku": "TEE-001-L",
"price_list": "eu",
"price": 19.9
}
]
}'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001/prices", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.NUCLEO_ACCESS_TOKEN}`,
"Idempotency-Key": "2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44",
"Content-Type": "application/json",
},
body: JSON.stringify({
"prices": [
{
"variant_sku": "TEE-001-M",
"price_list": "eu",
"price": 19.9,
"sale_price": 15
},
{
"variant_sku": "TEE-001-L",
"price_list": "eu",
"price": 19.9
}
]
}),
});
const data = await res.json();
console.log(res.status, data);<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$response = $client->request('PUT', 'https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001/prices', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Idempotency-Key' => '2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44',
],
'json' => [
'prices' => [
[
'variant_sku' => 'TEE-001-M',
'price_list' => 'eu',
'price' => 19.9,
'sale_price' => 15,
],
[
'variant_sku' => 'TEE-001-L',
'price_list' => 'eu',
'price' => 19.9,
],
],
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"data": [
{
"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
}
]
}
],
"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": {}
}
}/products/{product}/stockSet stock of a product
One line per variant and location (up to 500): available replaces the quantity (and the on-hand
quantity) at that location. Locations not sent do not change.
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
stockarray of object & StockInputrequiredmin items 1max items 500Attributes of each item
variant_skustringrequiredlocationstringrequiredLocation code (
GET /locations).availableintegerrequiredReplaces the quantity at that location.
Responses
200All variants of the product as they are now, and what happened.application/json
dataarray of VariantrequiredAttributes 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
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
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 PUT https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001/stock \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Idempotency-Key: 2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44' \
-H 'Content-Type: application/json' \
-d '{
"stock": [
{
"variant_sku": "TEE-001-M",
"location": "main",
"available": 12
}
]
}'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001/stock", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.NUCLEO_ACCESS_TOKEN}`,
"Idempotency-Key": "2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44",
"Content-Type": "application/json",
},
body: JSON.stringify({
"stock": [
{
"variant_sku": "TEE-001-M",
"location": "main",
"available": 12
}
]
}),
});
const data = await res.json();
console.log(res.status, data);<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$response = $client->request('PUT', 'https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001/stock', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Idempotency-Key' => '2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44',
],
'json' => [
'stock' => [
[
'variant_sku' => 'TEE-001-M',
'location' => 'main',
'available' => 12,
],
],
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"data": [
{
"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
}
]
}
],
"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": {}
}
}/pricesSet prices in bulk
Up to 1,000 lines, each for one variant SKU of any product. The answer has one result per line, in order.
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
pricesarray of object & PriceInputrequiredmin items 1max items 1000Attributes of each item
skustringrequiredVariant SKU.
price_liststringrequiredPrice list code (
GET /price-lists).pricenumberrequiredmin 0sale_pricenumber | nullDiscounted price on that list.
min 0
Responses
200One result per line, in the order sent.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
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 PUT https://api-catalog.nucleoplatform.com/api/catalog/v1/prices \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Idempotency-Key: 2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44' \
-H 'Content-Type: application/json' \
-d '{
"prices": [
{
"sku": "TEE-001-M",
"price_list": "eu",
"price": 21,
"sale_price": null
},
{
"sku": "TEE-002-S",
"price_list": "us",
"price": 25
}
]
}'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/prices", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.NUCLEO_ACCESS_TOKEN}`,
"Idempotency-Key": "2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44",
"Content-Type": "application/json",
},
body: JSON.stringify({
"prices": [
{
"sku": "TEE-001-M",
"price_list": "eu",
"price": 21,
"sale_price": null
},
{
"sku": "TEE-002-S",
"price_list": "us",
"price": 25
}
]
}),
});
const data = await res.json();
console.log(res.status, data);<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$response = $client->request('PUT', 'https://api-catalog.nucleoplatform.com/api/catalog/v1/prices', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Idempotency-Key' => '2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44',
],
'json' => [
'prices' => [
[
'sku' => 'TEE-001-M',
'price_list' => 'eu',
'price' => 21,
'sale_price' => null,
],
[
'sku' => 'TEE-002-S',
'price_list' => 'us',
'price' => 25,
],
],
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"data": [
{
"index": 1,
"sku": "string",
"product_id": 1,
"status": "created",
"errors": [
{
"field": "string",
"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": {}
}
}/stockSet stock in bulk
Up to 1,000 lines, each for one variant SKU and one location. The answer has one result per line, in order.
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
stockarray of object & StockInputrequiredmin items 1max items 1000Attributes of each item
skustringrequiredVariant SKU.
locationstringrequiredLocation code (
GET /locations).availableintegerrequiredReplaces the quantity at that location.
Responses
200One result per line, in the order sent.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
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 PUT https://api-catalog.nucleoplatform.com/api/catalog/v1/stock \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Idempotency-Key: 2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44' \
-H 'Content-Type: application/json' \
-d '{
"stock": [
{
"sku": "TEE-001-M",
"location": "main",
"available": 12
},
{
"sku": "TEE-001-L",
"location": "main",
"available": 0
}
]
}'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/stock", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.NUCLEO_ACCESS_TOKEN}`,
"Idempotency-Key": "2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44",
"Content-Type": "application/json",
},
body: JSON.stringify({
"stock": [
{
"sku": "TEE-001-M",
"location": "main",
"available": 12
},
{
"sku": "TEE-001-L",
"location": "main",
"available": 0
}
]
}),
});
const data = await res.json();
console.log(res.status, data);<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$response = $client->request('PUT', 'https://api-catalog.nucleoplatform.com/api/catalog/v1/stock', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Idempotency-Key' => '2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44',
],
'json' => [
'stock' => [
[
'sku' => 'TEE-001-M',
'location' => 'main',
'available' => 12,
],
[
'sku' => 'TEE-001-L',
'location' => 'main',
'available' => 0,
],
],
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"data": [
{
"index": 1,
"sku": "string",
"product_id": 1,
"status": "created",
"errors": [
{
"field": "string",
"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": {}
}
}Product parts
Attributes, one translation, categories and images of a product.
/products/{product}/attributesSet attribute values
The values sent, by attribute code (GET /attributes); other attributes keep their value. For an
attribute with options, send the option code or its label: a value the attribute does not have yet
becomes a new option.
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
attributesobjectrequiredattribute_setstringName or id of the attribute set to assign.
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
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 PUT https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001/attributes \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Idempotency-Key: 2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44' \
-H 'Content-Type: application/json' \
-d '{
"attributes": {
"color": "red",
"fit": "regular"
}
}'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001/attributes", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.NUCLEO_ACCESS_TOKEN}`,
"Idempotency-Key": "2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44",
"Content-Type": "application/json",
},
body: JSON.stringify({
"attributes": {
"color": "red",
"fit": "regular"
}
}),
});
const data = await res.json();
console.log(res.status, data);<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$response = $client->request('PUT', 'https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001/attributes', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Idempotency-Key' => '2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44',
],
'json' => [
'attributes' => [
'color' => 'red',
'fit' => 'regular',
],
],
'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": {}
}
}/products/{product}/translations/{locale}Set one translation
Path parameters
productstringrequiredProduct id or SKU.
Example:TEE-001localestringrequiredA locale code of the catalog (
GET /locales). The default locale writes the product's own title and description.Example:it
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
titlestringmax length 255descriptionstringHTML, up to 65,000 characters.
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
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 PUT https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001/translations/it \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Idempotency-Key: 2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44' \
-H 'Content-Type: application/json' \
-d '{
"title": "Maglietta basic",
"description": "<p>Cotone biologico.</p>"
}'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001/translations/it", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.NUCLEO_ACCESS_TOKEN}`,
"Idempotency-Key": "2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44",
"Content-Type": "application/json",
},
body: JSON.stringify({
"title": "Maglietta basic",
"description": "<p>Cotone biologico.</p>"
}),
});
const data = await res.json();
console.log(res.status, data);<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$response = $client->request('PUT', 'https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001/translations/it', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Idempotency-Key' => '2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44',
],
'json' => [
'title' => 'Maglietta basic',
'description' => '<p>Cotone biologico.</p>',
],
'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": {}
}
}/products/{product}/categoriesSet the categories
The full set of categories of the product, by id (GET /categories): categories not in the list are
removed, and an empty list removes them all. With the field rule inserimento only a product without
categories can be changed; with nucleo, none.
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
category_idsarray of integerrequiredmax items 200
Responses
200The categories of the product now.application/json
dataarray of ProductCategoryrequiredAttributes of each item
idintegernamestringpathstringExample:Clothing > Tops
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
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 PUT https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001/categories \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Idempotency-Key: 2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44' \
-H 'Content-Type: application/json' \
-d '{
"category_ids": [
12,
31
]
}'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001/categories", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.NUCLEO_ACCESS_TOKEN}`,
"Idempotency-Key": "2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44",
"Content-Type": "application/json",
},
body: JSON.stringify({
"category_ids": [
12,
31
]
}),
});
const data = await res.json();
console.log(res.status, data);<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$response = $client->request('PUT', 'https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001/categories', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Idempotency-Key' => '2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44',
],
'json' => [
'category_ids' => [
12,
31,
],
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"data": [
{
"id": 1,
"name": "string",
"path": "Clothing > Tops"
}
]
}{
"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": {}
}
}/products/{product}/imagesAdd an image
Either JSON with the url of the image, or multipart/form-data with the file in image.
- From a URL, Nucleo downloads the image and stores it: no link to your server remains. Only
public
http(s)addresses; JPEG, PNG, WebP, GIF, AVIF, HEIC, TIFF or BMP up to 40 MB; never SVG. The same URL sent again for the same product returns the image already there (200). - As a file: JPEG, PNG or WebP up to 10 MB.
position starts at 1; without it the image goes last. With the field rule inserimento only a
product without images can get one; with nucleo, none.
Image URLs sent in image_urls of a product are different: they are linked as they are, not downloaded.
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, multipart/form-data
urlstring (uri)requiredmax length 2000altstringmax length 255positionintegermin 1
Responses
200The image.application/json
dataImagerequiredChild attributes
idintegerurlstringaltstring | nullpositionintegerwidthinteger | nullheightinteger | null
201The image.application/json
dataImagerequiredChild attributes
idintegerurlstringaltstring | nullpositionintegerwidthinteger | nullheightinteger | 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
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 POST https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001/images \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Idempotency-Key: 2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://cdn.acme.example/tee-001-back.jpg",
"alt": "Back view",
"position": 2
}'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001/images", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.NUCLEO_ACCESS_TOKEN}`,
"Idempotency-Key": "2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44",
"Content-Type": "application/json",
},
body: JSON.stringify({
"url": "https://cdn.acme.example/tee-001-back.jpg",
"alt": "Back view",
"position": 2
}),
});
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/TEE-001/images', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Idempotency-Key' => '2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44',
],
'json' => [
'url' => 'https://cdn.acme.example/tee-001-back.jpg',
'alt' => 'Back view',
'position' => 2,
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"data": {
"id": 1,
"url": "string",
"alt": "string",
"position": 1,
"width": 1,
"height": 1
}
}{
"data": {
"id": 1,
"url": "string",
"alt": "string",
"position": 1,
"width": 1,
"height": 1
}
}{
"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": {}
}
}/products/{product}/images/{image}Change alt text or position
Path parameters
productstringrequiredProduct id or SKU.
Example:TEE-001imageintegerrequired
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
altstring | nullmax length 255positionintegerNew position; the other images move.
min 1
Responses
200The image.application/json
dataImagerequiredChild attributes
idintegerurlstringaltstring | nullpositionintegerwidthinteger | nullheightinteger | null
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/images/{image}' \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Idempotency-Key: 2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44' \
-H 'Content-Type: application/json' \
-d '{
"alt": "string",
"position": 1
}'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/products/TEE-001/images/{image}", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.NUCLEO_ACCESS_TOKEN}`,
"Idempotency-Key": "2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44",
"Content-Type": "application/json",
},
body: JSON.stringify({
"alt": "string",
"position": 1
}),
});
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/images/{image}', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Idempotency-Key' => '2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44',
],
'json' => [
'alt' => 'string',
'position' => 1,
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"data": {
"id": 1,
"url": "string",
"alt": "string",
"position": 1,
"width": 1,
"height": 1
}
}{
"error": {
"code": "string",
"message": "string",
"request_id": "string",
"details": {}
}
}{
"error": {
"code": "string",
"message": "string",
"request_id": "string",
"details": {}
}
}/products/{product}/images/{image}Delete an image
Path parameters
productstringrequiredProduct id or SKU.
Example:TEE-001imageintegerrequired
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
204Deleted.
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/images/{image}' \
-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/images/{image}", {
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/images/{image}', [
'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 — Deleted.{
"error": {
"code": "string",
"message": "string",
"request_id": "string",
"details": {}
}
}{
"error": {
"code": "string",
"message": "string",
"request_id": "string",
"details": {}
}
}Structures
The codes a product can use. They are managed in Nucleo and read-only here; an unknown code in a write is an error that names it.
/categoriesList categories
Responses
200All categories, flat, with their path.application/json
dataarray of objectrequiredAttributes of each item
idintegerrequiredparent_idinteger | nullrequirednamestringrequiredslugstringrequiredpathstringrequiredExample:Clothing > Topsstatusstringrequiredpositionintegerrequired
curl -X GET https://api-catalog.nucleoplatform.com/api/catalog/v1/categories \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Accept: application/json'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/categories", {
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/categories', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Accept' => 'application/json',
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"data": [
{
"id": 1,
"parent_id": 1,
"name": "string",
"slug": "string",
"path": "Clothing > Tops",
"status": "string",
"position": 1
}
]
}/attributesList attributes and their allowed values
Responses
200The product attributes of the catalog.application/json
dataarray of objectrequiredAttributes of each item
codestringrequiredExample:colornamestringrequiredtypestringrequiredFor example
text,number,boolean,date,dropdown,multiselect.scopestringrequiredproductorvariant.requiredbooleanrequiredvariant_optionbooleanrequiredThe attribute tells variants apart (size, color…).
optionsarray of objectrequiredAllowed values of attributes with options (active ones), with their labels by locale.
Attributes of each item
codestringExample:redlabelsobject
curl -X GET https://api-catalog.nucleoplatform.com/api/catalog/v1/attributes \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Accept: application/json'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/attributes", {
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/attributes', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Accept' => 'application/json',
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"data": [
{
"code": "color",
"name": "string",
"type": "string",
"scope": "string",
"required": true,
"variant_option": true,
"options": [
{
"code": "red",
"labels": {
"en": "Red",
"it": "Rosso"
}
}
]
}
]
}/localesList locales
Responses
200The languages of the catalog.application/json
dataarray of objectrequiredAttributes of each item
codestringExample:itnamestringExample:Italianodefaultboolean
curl -X GET https://api-catalog.nucleoplatform.com/api/catalog/v1/locales \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Accept: application/json'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/locales", {
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/locales', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Accept' => 'application/json',
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"data": [
{
"code": "it",
"name": "Italiano",
"default": true
}
]
}/marketsList markets
Responses
200The markets of the catalog.application/json
dataarray of objectrequiredAttributes of each item
idintegernamestringcurrencystring | nulldefault_localestring | nulladditional_localesarray of stringcountriesarray of stringISO 3166-1 alpha-2.
price_listsarray of stringactivebooleandefaultboolean
curl -X GET https://api-catalog.nucleoplatform.com/api/catalog/v1/markets \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Accept: application/json'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/markets", {
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/markets', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Accept' => 'application/json',
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"data": [
{
"id": 1,
"name": "string",
"currency": "string",
"default_locale": "string",
"additional_locales": [
"string"
],
"countries": [
"string"
],
"price_lists": [
"string"
],
"active": true,
"default": true
}
]
}/price-listsList price lists
Responses
200The price lists of the catalog.application/json
dataarray of objectrequiredAttributes of each item
codestringExample:eunamestringcurrencystring | nullExample:EURactivebooleandefaultboolean
curl -X GET https://api-catalog.nucleoplatform.com/api/catalog/v1/price-lists \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Accept: application/json'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/price-lists", {
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/price-lists', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Accept' => 'application/json',
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"data": [
{
"code": "eu",
"name": "string",
"currency": "EUR",
"active": true,
"default": true
}
]
}/locationsList locations
Responses
200The stock locations of the catalog.application/json
dataarray of objectrequiredAttributes of each item
codestringExample:mainnamestringactivebooleandefaultboolean
curl -X GET https://api-catalog.nucleoplatform.com/api/catalog/v1/locations \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Accept: application/json'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/locations", {
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/locations', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Accept' => 'application/json',
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"data": [
{
"code": "main",
"name": "string",
"active": true,
"default": true
}
]
}Webhooks
Up to 10 HTTPS endpoints per key, each with the events it wants and its own signing secret. Webhooks can be managed with a read-only key too: receiving news does not change the catalog.
/webhooksList webhooks
Responses
200The webhooks of this key (without secrets).application/json
dataarray of WebhookrequiredAttributes of each item
idstring (uuid)urlstringeventsarray of stringOne ofproduct.createdproduct.updatedproduct.deletedstock.updatedactivebooleanlast_success_atstring (date-time) | nulllast_failure_atstring (date-time) | nulllast_errorstring | nullExample:HTTP 503created_atstring (date-time)
curl -X GET https://api-catalog.nucleoplatform.com/api/catalog/v1/webhooks \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Accept: application/json'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/webhooks", {
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/webhooks', [
'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",
"url": "string",
"events": [
"product.created"
],
"active": true,
"last_success_at": "2026-10-01T09:30:00Z",
"last_failure_at": "2026-10-01T09:30:00Z",
"last_error": "HTTP 503",
"created_at": "2026-10-01T09:30:00Z"
}
]
}/webhooksCreate a webhook
The URL must be https and resolve to a public address. The answer contains the signing secret
(whsec_…) — the only time it is shown.
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
urlstring (uri)requiredmax length 500eventsarray of stringrequiredOne ofproduct.createdproduct.updatedproduct.deletedstock.updatedmin items 1
Responses
201Created. Store the secret now.application/json
dataWebhook & objectrequiredChild attributes
idstring (uuid)urlstringeventsarray of stringOne ofproduct.createdproduct.updatedproduct.deletedstock.updatedactivebooleanlast_success_atstring (date-time) | nulllast_failure_atstring (date-time) | nulllast_errorstring | nullExample:HTTP 503created_atstring (date-time)secretstringrequiredShown only now.
Example:whsec_3KQ9…
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
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 POST https://api-catalog.nucleoplatform.com/api/catalog/v1/webhooks \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Idempotency-Key: 2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://erp.acme.example/nucleo/webhooks",
"events": [
"product.created",
"product.updated",
"product.deleted",
"stock.updated"
]
}'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/webhooks", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.NUCLEO_ACCESS_TOKEN}`,
"Idempotency-Key": "2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44",
"Content-Type": "application/json",
},
body: JSON.stringify({
"url": "https://erp.acme.example/nucleo/webhooks",
"events": [
"product.created",
"product.updated",
"product.deleted",
"stock.updated"
]
}),
});
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/webhooks', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Idempotency-Key' => '2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44',
],
'json' => [
'url' => 'https://erp.acme.example/nucleo/webhooks',
'events' => [
'product.created',
'product.updated',
'product.deleted',
'stock.updated',
],
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"data": {
"id": "9b2f4c1e-5d7a-4e8b-9c3d-2a1f0e6b7c8d",
"url": "string",
"events": [
"product.created"
],
"active": true,
"last_success_at": "2026-10-01T09:30:00Z",
"last_failure_at": "2026-10-01T09:30:00Z",
"last_error": "HTTP 503",
"created_at": "2026-10-01T09:30:00Z",
"secret": "whsec_3KQ9…"
}
}{
"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": {}
}
}/webhooks/{webhook}Get a webhook
Path parameters
webhookstring (uuid)required
Responses
200The webhook, with the outcome of its last deliveries.application/json
dataWebhookrequiredChild attributes
idstring (uuid)urlstringeventsarray of stringOne ofproduct.createdproduct.updatedproduct.deletedstock.updatedactivebooleanlast_success_atstring (date-time) | nulllast_failure_atstring (date-time) | nulllast_errorstring | nullExample:HTTP 503created_atstring (date-time)
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/webhooks/{webhook}' \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Accept: application/json'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/webhooks/{webhook}", {
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/webhooks/{webhook}', [
'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",
"url": "string",
"events": [
"product.created"
],
"active": true,
"last_success_at": "2026-10-01T09:30:00Z",
"last_failure_at": "2026-10-01T09:30:00Z",
"last_error": "HTTP 503",
"created_at": "2026-10-01T09:30:00Z"
}
}{
"error": {
"code": "string",
"message": "string",
"request_id": "string",
"details": {}
}
}/webhooks/{webhook}Update a webhook or rotate its secret
With rotate_secret: true the answer contains the new secret; the old one stops working at once.
Path parameters
webhookstring (uuid)required
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
urlstring (uri)max length 500eventsarray of stringOne ofproduct.createdproduct.updatedproduct.deletedstock.updatedmin items 1activebooleanAn inactive webhook receives nothing; events of that time are not sent later.
rotate_secretboolean
Responses
200Updated.application/json
dataWebhook & objectrequiredChild attributes
idstring (uuid)urlstringeventsarray of stringOne ofproduct.createdproduct.updatedproduct.deletedstock.updatedactivebooleanlast_success_atstring (date-time) | nulllast_failure_atstring (date-time) | nulllast_errorstring | nullExample:HTTP 503created_atstring (date-time)secretstringOnly after
rotate_secret.
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
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 PATCH 'https://api-catalog.nucleoplatform.com/api/catalog/v1/webhooks/{webhook}' \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN" \
-H 'Idempotency-Key: 2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://shop.acme.example",
"events": [
"product.created"
],
"active": true,
"rotate_secret": true
}'const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/webhooks/{webhook}", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.NUCLEO_ACCESS_TOKEN}`,
"Idempotency-Key": "2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44",
"Content-Type": "application/json",
},
body: JSON.stringify({
"url": "https://shop.acme.example",
"events": [
"product.created"
],
"active": true,
"rotate_secret": true
}),
});
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/webhooks/{webhook}', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
'Idempotency-Key' => '2b5c0d2e-7d2a-4f6e-9a51-0c8a1e7f3b44',
],
'json' => [
'url' => 'https://shop.acme.example',
'events' => [
'product.created',
],
'active' => true,
'rotate_secret' => true,
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"data": {
"id": "9b2f4c1e-5d7a-4e8b-9c3d-2a1f0e6b7c8d",
"url": "string",
"events": [
"product.created"
],
"active": true,
"last_success_at": "2026-10-01T09:30:00Z",
"last_failure_at": "2026-10-01T09:30:00Z",
"last_error": "HTTP 503",
"created_at": "2026-10-01T09:30:00Z",
"secret": "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": {}
}
}/webhooks/{webhook}Delete a webhook
Path parameters
webhookstring (uuid)required
Responses
204Deleted. Pending deliveries are dropped.
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 DELETE 'https://api-catalog.nucleoplatform.com/api/catalog/v1/webhooks/{webhook}' \
-H "Authorization: Bearer $NUCLEO_ACCESS_TOKEN"const res = await fetch("https://api-catalog.nucleoplatform.com/api/catalog/v1/webhooks/{webhook}", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.NUCLEO_ACCESS_TOKEN}`,
},
});
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/webhooks/{webhook}', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('NUCLEO_ACCESS_TOKEN'),
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();HTTP 204 — Deleted. Pending deliveries are dropped.{
"error": {
"code": "string",
"message": "string",
"request_id": "string",
"details": {}
}
}catalogEventSomething changed in the catalog
Sent to every active webhook that subscribed to the event. The notice is small: it says what changed
and you read the product with GET /products/{id} if you need it.
product.created— a product was created, or restored from the trash.product.updated— a product changed;changed_fieldsnames the fields (for exampletitle,description,price:eu,attr:color,images,stockfor the in/out of stock state).product.deleted— a product was moved to the trash.stock.updated— the available quantity of a variant at a location changed (on the product page, by an import or through this API).
Changes made by any key, including yours, are sent: triggered_by_this_key is true when the
change came from the key that owns the webhook, so you can skip your own echoes.
Signature. X-Nucleo-Signature: t=<unix time>,v1=<signature>, where the signature is the lowercase
hex HMAC-SHA256 of <t>.<raw body> keyed with the webhook's secret. Verify it on the exact bytes
received, compare in constant time and reject a t older than 5 minutes.
Delivery. 10-second timeout, redirects not followed; any 2xx is a success. Deliveries leave
within about a minute of the change. Any other answer is retried about 30 s, 1, 2, 4, 8, 16 and 32
minutes later (8 attempts in all), then the event is dropped. Every attempt carries the same id
(also in X-Nucleo-Webhook-Id) and its number in X-Nucleo-Attempt. Events can arrive out of order:
compare created_at, or re-read the product.
Headers
X-Nucleo-SignaturestringrequiredExample:t=1791115200,v1=5f2b6c0e9a8d4f7b1c3e2a6d9b0f8e7c6a5d4b3c2e1f0a9b8c7d6e5f4a3b2c1dX-Nucleo-EventstringrequiredOne ofproduct.createdproduct.updatedproduct.deletedstock.updatedExample:product.createdX-Nucleo-Webhook-Idstring (uuid)requiredX-Nucleo-Attemptintegerrequiredmin 1max 8
Request Nucleo sendsapplication/json
idstring (uuid)requiredSame at every attempt.
typestringrequiredOne ofproduct.createdproduct.updatedproduct.deletedstock.updatedcreated_atstring (date-time)requiredtriggered_by_this_keybooleanrequireddataobjectrequiredChild attributes
product_idintegerskustring | nullchanged_fieldsarray of stringOnly in
product.updated.variant_idintegerOnly in
stock.updated.variant_skustring | nulllocationstringavailableinteger | null
Expected response
200Received. Any
2xxworks; the body is ignored.
{
"id": "5d1c9a0e-2b7f-4c3a-9e8d-6f1a2b3c4d5e",
"type": "product.updated",
"created_at": "2026-10-14T08:00:00Z",
"triggered_by_this_key": false,
"data": {
"product_id": 1201,
"sku": "TEE-001",
"changed_fields": [
"title",
"price:eu"
]
}
}{
"id": "0a9b8c7d-6e5f-4a3b-2c1d-0e9f8a7b6c5d",
"type": "stock.updated",
"created_at": "2026-10-14T08:00:05Z",
"triggered_by_this_key": true,
"data": {
"product_id": 1201,
"sku": "TEE-001",
"variant_id": 3304,
"variant_sku": "TEE-001-M",
"location": "main",
"available": 11
}
}HTTP 200 — Received. Any `2xx` works; the body is ignored.