Submit a Product
This guide shows you how to submit a product to noon using UpsertProduct. By the end, you'll have a product stored in noon's catalog with a sku_parent you can use to track its status.
Prerequisites
- Authentication set up — see Getting Credentials and Authenticating Your Requests
- A valid category code and attribute mapping for your product — see Discover Category Attributes
Step 1 — Submit the product
UpsertProduct (POST /content/v1/product/upsert) creates a product if the partner_sku values are new, or updates the existing product they belong to. Pass X-Project as a required header to scope the call to your project.
The example below submits a two-size shoe product with localizable title and brand attributes.
- Python
- Node.js
# get_authenticated_session() is provided in docs/snippets/auth.mdx — see Authenticating Your Requests
session = get_authenticated_session()
response = session.post(
"https://noon-api-gateway.noon.partners/content/v1/product/upsert",
json={
"skus": [
{"partner_sku": "MY-SNK-42", "size": "42"},
{"partner_sku": "MY-SNK-43", "size": "43"},
],
"brand": "Acme",
"category": "apparel-shoes-sneakers",
"images": [
{"url": "https://cdn.example.com/acme-runner-main.jpg", "sort": 1},
{"url": "https://cdn.example.com/acme-runner-side.jpg", "sort": 2},
],
"attributes": {
"product_title": {
"values": [
{"value": "Acme Runner Sneaker", "language": "LANGUAGE_EN"},
{"value": "حذاء اكمي رانر", "language": "LANGUAGE_AR"},
]
},
"long_description": {
"values": [
{"value": "Lightweight everyday runner with cushioned sole.", "language": "LANGUAGE_EN"},
{"value": "حذاء خفيف الوزن للاستخدام اليومي مع نعل مبطن.", "language": "LANGUAGE_AR"},
]
},
},
},
headers={
"User-Agent": "MyCatalogApp/1.0.0",
"X-Project": "<project_code>",
},
timeout=30,
)
response.raise_for_status()
result = response.json()
sku_parent = result["sku_parent"]
// getAuthenticatedClient() is provided in docs/snippets/auth.mdx — see Authenticating Your Requests
const client = await getAuthenticatedClient();
const response = await client.post(
"https://noon-api-gateway.noon.partners/content/v1/product/upsert",
{
skus: [
{ partner_sku: "MY-SNK-42", size: "42" },
{ partner_sku: "MY-SNK-43", size: "43" },
],
brand: "Acme",
category: "apparel-shoes-sneakers",
images: [
{ url: "https://cdn.example.com/acme-runner-main.jpg", sort: 1 },
{ url: "https://cdn.example.com/acme-runner-side.jpg", sort: 2 },
],
attributes: {
product_title: {
values: [
{ value: "Acme Runner Sneaker", language: "LANGUAGE_EN" },
{ value: "حذاء اكمي رانر", language: "LANGUAGE_AR" },
],
},
long_description: {
values: [
{ value: "Lightweight everyday runner with cushioned sole.", language: "LANGUAGE_EN" },
{ value: "حذاء خفيف الوزن للاستخدام اليومي مع نعل مبطن.", language: "LANGUAGE_AR" },
],
},
},
},
{
headers: {
"Content-Type": "application/json",
"User-Agent": "MyCatalogApp/1.0.0",
"X-Project": "<project_code>",
},
}
);
const { sku_parent } = response.data;
Response:
{
"sku_parent": "Z1ABC234",
"variants": [
{"sku": "N12345678", "partner_sku": "MY-SNK-42", "psku_code": "P0012345", "size": "42"},
{"sku": "N12345679", "partner_sku": "MY-SNK-43", "psku_code": "P0012346", "size": "43"}
],
"status": {"status_id": 0, "status_code": "OK", "message": ""}
}
You'll know this worked when status.status_id is 0 and sku_parent is present in the response.
Store sku_parent immediately. It is the only handle accepted by GetContent and the stable identity for all future updates to this product.
| Condition | What it means | What to do |
|---|---|---|
| Non-2xx response | Request failed entirely — auth error, missing X-Project, quota exceeded, or invalid category | Check rpcStatus.message. For quota errors, the message states your limit and period. |
status.status_id: 3 (INVALID_ARGUMENT) within a 200 | Product was stored but has content problems | Note the sku_parent, then call GetContent for the detailed breakdown — see Track and Fix. |
A 200 response does not mean your content is valid. Always check status.status_id — 0 means accepted cleanly; 3 means stored with problems.
SKU and size rules
- Single-SKU product:
sizeis optional. Omit it if your product has no size dimension. - Multi-SKU product: every SKU in the request must have
sizeset. - Updates: all
partner_skuvalues in a single request must belong to the same parent product. noon usespartner_skuas the identity key — sending an existingpartner_skutriggers an update, not a create. partner_skumust be globally unique across all your products, not just within a single product.
Structuring attribute values
Attributes are sent as a map of attribute_code → { "values": [...] }. How you populate values depends on the attribute's flags from ListCategoryAttributes:
Non-localizable, single value:
"colour": { "values": [{"value": "Red"}] }
Localizable (is_localizable: true) — one value per language:
"product_title": {
"values": [
{"value": "Acme Runner Sneaker", "language": "LANGUAGE_EN"},
{"value": "حذاء اكمي رانر", "language": "LANGUAGE_AR"}
]
}
Multivalued (is_multivalued: true) — use sort to order values:
"feature_bullet": {
"values": [
{"value": "Cushioned sole", "language": "LANGUAGE_EN", "sort": 1},
{"value": "Breathable mesh upper", "language": "LANGUAGE_EN", "sort": 2}
]
}
Metric (ATTRIBUTE_TYPE_METRIC) — numeric value and unit as separate attributes:
Metric attributes are always submitted as a pair: the numeric attribute and a corresponding _unit attribute. The unit value must exactly match one of the strings in attribute_metric_units from ListCategoryAttributes. Both must be present in the same UpsertProduct call — you cannot update one without the other.
"product_length": {
"values": [{"value": 10, "language": "LANGUAGE_EN"}]
},
"product_length_unit": {
"values": [{"value": "millimeter", "language": "LANGUAGE_EN"}]
}
Next steps
- Track and Fix — check why your product isn't live yet and fix it
- UpsertProduct Reference — full request and response schema