---
sidebar_position: 1
---
# Discover Category Attributes
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
This guide walks you through the category discovery workflow — finding a valid category code and retrieving its full attribute contract. By the end, you'll know exactly which attributes noon requires for your product type and what validation rules each one must satisfy before you call `UpsertProduct`.
## Prerequisites
- Authentication set up — see [Getting Credentials][getting-credentials] and [Authenticating Your Requests][authenticating-requests]
## Step 1 — Fetch valid category codes
`ListCategories` (`POST /content/v1/categories/list`) returns every valid category code in the catalog. The request body is empty.
```python
# 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/categories/list",
json={},
headers={"User-Agent": "MyCatalogApp/1.0.0"},
timeout=30,
)
response.raise_for_status()
categories = response.json()["categories"]
```
```javascript
// 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/categories/list",
{},
{
headers: {
"Content-Type": "application/json",
"User-Agent": "MyCatalogApp/1.0.0",
},
}
);
const categories = response.data.categories;
```
**Response:**
```json
{
"categories": [
"apparel-shoes-sneakers",
"electronics-mobiles-smartphones",
"home-furniture-sofas"
]
}
```
Each string is a complete `family-product_type-product_subtype` path. Find the code that matches your product type and carry it into Step 2.
You'll know this worked when `categories` is a non-empty array of strings.
## Step 2 — Fetch attributes for your category
`ListCategoryAttributes` (`POST /content/v1/categories/attributes/list`) returns every attribute noon accepts for the given category — mandatory and optional — with the validation rules each value must satisfy.
```python
response = session.post(
"https://noon-api-gateway.noon.partners/content/v1/categories/attributes/list",
json={"category_code": "apparel-shoes-sneakers"},
headers={"User-Agent": "MyCatalogApp/1.0.0"},
timeout=30,
)
response.raise_for_status()
attributes = response.json()["attributes"]
```
```javascript
const response = await client.post(
"https://noon-api-gateway.noon.partners/content/v1/categories/attributes/list",
{ category_code: "apparel-shoes-sneakers" },
{
headers: {
"Content-Type": "application/json",
"User-Agent": "MyCatalogApp/1.0.0",
},
}
);
const attributes = response.data.attributes;
```
**Response:**
```json
{
"attributes": [
{
"attribute_code": "product_title",
"attribute_type": "ATTRIBUTE_TYPE_TEXT",
"is_mandatory": true,
"is_localizable": true,
"is_multivalued": false,
"is_facet": false,
"min_characters": 10,
"max_characters": 200,
"is_html_allowed": false,
"allowed_html_tags": [],
"attribute_options": [],
"attribute_metric_units": [],
"allowed_mimetypes": []
},
{
"attribute_code": "shoe_size",
"attribute_type": "ATTRIBUTE_TYPE_SELECT",
"is_mandatory": true,
"is_localizable": false,
"is_multivalued": false,
"is_facet": true,
"attribute_options": ["36", "37", "38", "39", "40", "41", "42", "43", "44", "45"],
"allowed_html_tags": [],
"attribute_metric_units": [],
"allowed_mimetypes": []
}
]
}
```
You'll know this worked when `attributes` is a non-empty array and each object contains `attribute_code`, `is_mandatory`, and `attribute_type`.
| Condition | What it means | What to do |
|---|---|---|
| Non-2xx response | `category_code` was rejected or the request was malformed | Confirm the code is a value from Step 1 — codes not in that list are invalid. Check `rpcStatus.message` for details. |
| Empty `attributes` array | The category exists but has no attributes exposed to the API | Contact noon — this is unexpected for any active category. |
## Reading the response
Build your attribute mapping from this output. The fields that determine what you must send to `UpsertProduct`:
- **`is_mandatory`** — `true` means this attribute is required. A product missing any mandatory attribute cannot reach 100% completeness and won't enter QC. Map all mandatory attributes before submitting.
- **`attribute_type`** — the format noon expects for the value. See the type table below.
- **`is_localizable`** — `true` means submit separate values per language: `LANGUAGE_EN` for English and `LANGUAGE_AR` for Arabic. Completeness is tracked independently per language.
- **`is_multivalued`** — `true` means the attribute accepts multiple values. Send them with a `sort` field starting at 1. `max_values` caps the count.
- **`is_facet`** — `true` means noon indexes this attribute for storefront search filters. Accurate values improve product discoverability.
- **`attribute_options`** — for `ATTRIBUTE_TYPE_SELECT` attributes, the value you send must exactly match one of these strings (case-sensitive). A non-matching value fails validation.
- **`attribute_metric_units`** — for `ATTRIBUTE_TYPE_METRIC` attributes, the unit is submitted as a separate attribute named `_unit`. The value of that unit attribute must exactly match one of these strings (case-sensitive). Both the numeric attribute and the `_unit` attribute must always be submitted together; you cannot update one without the other. See [Submit a Product][content-submit-product] for the full example.
**Attribute types:**
| `attribute_type` | What to send to `UpsertProduct` |
|---|---|
| `ATTRIBUTE_TYPE_TEXT` | String within `min_characters` / `max_characters`; match `additional_validation_regex` if present; HTML only if `is_html_allowed` is `true`, restricted to `allowed_html_tags` |
| `ATTRIBUTE_TYPE_NUMERIC` | Number within `number_min` / `number_max`; negative only if `is_negative_allowed` is `true` |
| `ATTRIBUTE_TYPE_SELECT` | One of `attribute_options` exactly (case-sensitive) |
| `ATTRIBUTE_TYPE_METRIC` | A number; the unit is submitted as a separate attribute named `_unit` whose value must exactly match one of `attribute_metric_units` — both must always be submitted together |
| `ATTRIBUTE_TYPE_BOOL` | `true` or `false` |
| `ATTRIBUTE_TYPE_DATETIME` | ISO-8601 timestamp within `date_min` / `date_max` |
| `ATTRIBUTE_TYPE_FILE` | Public URL; file within `min_size_in_kilobytes` / `max_size_in_kilobytes` and of a type in `allowed_mimetypes` |
| `ATTRIBUTE_TYPE_JSONDICT` | A JSON object |
:::info
The list is pre-filtered to attributes you can submit. If an attribute code doesn't appear here, don't send it in `UpsertProduct`.
:::
## Next steps
- [UpsertProduct Reference][content-upsert-product-api] — submit a product using the category and attribute mapping you've built
- [GetContent Reference][content-get-content-api] — check completeness, image review status, and QC state after submitting
- [ListCategoryAttributes Reference][content-list-category-attributes-api] — full request and response schema for this call