Go Live: Full Product Onboarding
This guide walks through the complete lifecycle for getting a product live on noon — from discovering the right category to confirming the listing is active. By the end, your integration will handle the full create-monitor-fix loop and know exactly what triggers a product going live.
How it works
Every product passes through the same sequence. The fix loop between submission and active can repeat multiple times — once for content gaps, once for image review, once for QC.
Why the loop exists: QC only starts after content completeness hits 100%. Images are reviewed independently and in parallel. Each fix cycle requires another UpsertProduct call followed by a GetContent poll. Plan for at least two cycles on first submission.
Go-live gates — all must be true for a language to become active:
| Gate | What noon checks | Where you see it in GetContent |
|---|---|---|
| Content completeness | 100% of mandatory attributes present and valid | statuses[].content.completeness = "100%" |
| Image | At least one image is REVIEW_STATUS_VALID and VISIBILITY_STATUS_VISIBLE | images[].review_status + images[].visibility |
| QC | QC_STATUS_APPROVED | statuses[].qc.status |
| Quota | An available product slot in your monthly allowance | Returned as an error on UpsertProduct if exceeded |
Prerequisites
- Authentication set up — see Getting Credentials and Authenticating Your Requests
- An active noon partner account with API access configured
Step 1 — Discover your category and its attributes
ListCategories (POST /content/v1/categories/list) gives you the valid category code for your product. ListCategoryAttributes (POST /content/v1/categories/attributes/list) gives you the full attribute contract — every mandatory field and its validation rules.
Run both calls once per category and cache the results. Category codes and attribute definitions change infrequently.
See Discover Category Attributes for the complete implementation including code examples and attribute type rules.
Done when: you have a category code and a mapped list of mandatory attributes with valid values ready for submission.
Step 2 — Submit the product
UpsertProduct (POST /content/v1/product/upsert) creates the product or updates it if your partner_sku values already exist. Returns sku_parent — store it immediately.
- 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}],
"attributes": {
"product_title": {
"values": [
{"value": "Acme Runner Sneaker", "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"]
# A 200 does not mean content is valid — always check status_id
assert result["status"]["status_id"] == 0, result["status"]["message"]
// 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 }],
attributes: {
product_title: {
values: [
{ value: "Acme Runner Sneaker", 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, status } = response.data;
if (status.status_id !== 0) throw new Error(status.message);
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": ""}
}
Done when: status.status_id is 0 and sku_parent is stored.
| Error | Meaning | Fix |
|---|---|---|
| Non-2xx | Auth failure, missing X-Project, invalid category, or quota exceeded | Check rpcStatus.message |
status_id: 3 within 200 | Content stored but has problems | Proceed to Step 3 to diagnose |
For the full attribute structuring rules (localizable, multivalued), see Submit a Product.
Step 3 — Check status and identify blockers
GetContent (POST /content/v1/product/content/get) returns the current completeness, image review state, and QC status. Call it after every UpsertProduct to see what changed.
- Python
- Node.js
response = session.post(
"https://noon-api-gateway.noon.partners/content/v1/product/content/get",
json={"sku_parent": sku_parent},
headers={"User-Agent": "MyCatalogApp/1.0.0"},
timeout=30,
)
response.raise_for_status()
content = response.json()
for status in content["statuses"]:
lang = status["language"]
overall = status["overall_status"]
completeness = status["content"]["completeness"]
qc = status["qc"]["status"]
print(f"{lang}: {overall} | completeness={completeness} | qc={qc}")
const response = await client.post(
"https://noon-api-gateway.noon.partners/content/v1/product/content/get",
{ sku_parent },
{ headers: { "Content-Type": "application/json", "User-Agent": "MyCatalogApp/1.0.0" } }
);
for (const status of response.data.statuses) {
const { language, overall_status, content, qc } = status;
console.log(`${language}: ${overall_status} | completeness=${content.completeness} | qc=${qc.status}`);
}
Response:
{
"sku_parent": "Z1ABC234",
"images": [
{
"url": "https://cdn.example.com/acme-runner-main.jpg",
"sort": 1,
"visibility": "VISIBILITY_STATUS_VISIBLE",
"review_status": "REVIEW_STATUS_PENDING",
"issues": []
}
],
"statuses": [
{
"language": "LANGUAGE_EN",
"content": {
"completeness": "80%",
"missing_attributes": ["long_description"],
"invalid_attributes": []
},
"qc": {"status": "QC_STATUS_NOT_ELIGIBLE", "rejection_reasons": [], "comment": ""},
"overall_status": "OVERALL_STATUS_INACTIVE",
"errors": []
}
]
}
Done when: every entry in statuses has overall_status: "OVERALL_STATUS_ACTIVE".
For the complete diagnostic logic — what each QC status means, image status combinations, and the full error code table — see Track and Fix.
Step 4 — Fix and resubmit
Call UpsertProduct again with the same partner_sku values to update the product with corrected content. Then return to Step 3. Repeat until all statuses are active.
Submit → Check → Fix → Check → Fix → … → Active
Testing
Use the noon sandbox environment to run through the full lifecycle before going to production — see Static Sandbox for setup.
Trigger the flow by calling UpsertProduct in sandbox with a test product. Then poll GetContent to observe the status progression:
- After first submission: expect
completenessbelow 100% if any mandatory attributes are missing, andreview_status: "REVIEW_STATUS_PENDING"on images. - After completing content: expect
completeness: "100%"andqc.statusmoving fromQC_STATUS_NOT_ELIGIBLEtoQC_STATUS_PENDING. - After QC approval and a valid image: expect
overall_status: "OVERALL_STATUS_ACTIVE".
The sandbox (PIPR) returns static responses that match the schema's data types — it does not run real QC review. Expect enum fields like qc.status and overall_status to return valid values immediately rather than progressing through the real approval lifecycle. Use sandbox to validate your request structure and response parsing, not to simulate the full go-live sequence.
Common failure modes:
| Symptom | Likely cause | Check |
|---|---|---|
completeness stuck below 100% after resubmit | Missing or invalid mandatory attribute | content.missing_attributes and content.invalid_attributes |
qc.status stays QC_STATUS_NOT_ELIGIBLE | Completeness never reached 100% | Fix all missing/invalid attributes first |
Image stays REVIEW_STATUS_PENDING indefinitely | Image review is asynchronous and may take time | Wait and poll; if persistent, check image URL accessibility |
| Product active in EN but inactive in AR | Arabic mandatory attributes missing or incomplete | Check statuses entry for LANGUAGE_AR separately |
Next steps
- Submit a Product — detailed attribute structuring rules and SKU patterns
- Track and Fix — complete diagnostic guide for all blocker types
- UpsertProduct Reference — full request and response schema
- GetContent Reference — full request and response schema