Postman — POST /products description
## Purpose
Create a provider product
## Purpose
Create a product for the provider marketplace catalog (simple or variant).
Used by Mobile and Web product-builder screens after taxonomy, pricing,
attributes (when variant), and media are collected.
## Auth
Requires **SecretKeyAuth** and **ProviderBearerAuth**.
Ownership comes from the JWT only. Client-supplied `provider` / `providerId`
and other workflow fields are rejected.
Headers:
- `secretkey` — required (not `x-secret-key`)
- `Authorization: Bearer `
- `lang` — `ar` (default) or `en`
## Transport
`multipart/form-data` only.
- Images (and optional video) are sent as **files**.
- Complex fields (`name`, `description`, `attributes`, `variants`) are sent
as **JSON-encoded text** parts.
- Do not replace this body with raw `application/json`.
- Required parts: `type`, `departmentId`, `subdepartmentId`, `name`,
`description`, `condition`, `pricingMethod`, `images` (1–10).
- Taxonomy IDs must be active, non-deleted, and parent-child consistent.
- When `pricingMethod=ai`, `aiPricingRequestId` is required
(`POST /pricing-request` → `data.id`).
## Product type rules
`type` enum: `simple` | `variant`.
- **`type=simple`**: omit `attributes` and `variants`.
- **`type=variant`**: send `attributes` and `variants` as valid JSON strings.
- Mixing the two columns returns `products.invalidProductPayload`.
| Field | `type=simple` | `type=variant` |
|-------|---------------|----------------|
| `price` / `quantity` | required | **omit** |
| `discountType` / `discountValue` | optional (`none` \| `percentage` \| `fixed`) | **omit** |
| `attributes` | **omit** | required JSON string (MongoId array) |
| `variants` | **omit** | required JSON string (variant objects) |
## Simple product payload
Use `type=simple` when the product has one price and one stock quantity
without selectable variants.
Rules:
- Do not send `attributes`.
- Do not send `variants`.
- Do not send empty strings for forbidden fields.
- Do not check “Send empty value” in Swagger/Postman.
- Use product-level `price` / `quantity` / optional discount fields.
Example form-data:
- `type` = `simple`
- `name` = `{"ar":"سماعات سوني","en":"Sony Headphones"}`
- `description` = `{"ar":"سماعات بحالة ممتازة","en":"Headphones in excellent condition"}`
- `departmentId` / `subdepartmentId` = active taxonomy MongoIds
- `condition` = `new` \| `used`
- `pricingMethod` = `manual`
- `price` = `120`
- `quantity` = `10`
- `discountType` = `none`
- `discountValue` = `0`
- `images` = 1–10 files
Important: **No `attributes`. No `variants`.**
## Variant product payload
Use `type=variant` when the product has selectable options such as الحجم,
اللون, المقاس, or any attribute that changes stock/price.
Rules:
- `attributes` is required.
- `variants` is required.
- Both are **Text** fields in multipart/form-data.
- Values must be valid JSON strings.
- Do not send nested multipart keys such as
`variants[0][attributes][0][attributeId]`.
- Send each whole array as one JSON string.
- Omit top-level `price` / `quantity` / `discountType` / `discountValue`.
## Attributes and variants mapping
### `attributes` (string)
JSON-encoded array of selected Product Attribute **MongoId** strings.
Required for `type=variant`. Forbidden for `type=simple`.
```json
["6a64b113dc6f51f903524f5a","6a64b113dc6f51f903524f5b"]
```
These IDs are the attributes the user selected (e.g. الحجم, اللون).
Every variant row must use only `attributeId` values from this array.
### `variants` (string)
JSON-encoded array of purchasable combinations.
Each object must include `attributes` (`attributeId` + `valueId`),
`quantity`, `price`, `discountType`, `discountValue`.
```json
[{
"attributes": [
{"attributeId":"6a64b113dc6f51f903524f5a","valueId":"6a64b145dc6f51f903524f73"},
{"attributeId":"6a64b113dc6f51f903524f5b","valueId":"6a64b15fdc6f51f903524f8d"}
],
"quantity": 5,
"price": 120,
"discountType": "none",
"discountValue": 0
}]
```
Meaning: الحجم = XL, اللون = أحمر, stock = 5, price = 120, no discount.
### Mobile flow
1. User chooses product type.
2. If simple → send normal product fields only (omit attributes/variants).
3. If variant → user selects attributes (e.g. الحجم, اللون).
4. App sends selected attribute IDs in `attributes`.
5. App builds purchasable combinations in `variants`
(e.g. XL + أحمر, XXL + أسود).
6. Every combination has values, price, stock, and discount data.
## Examples
**A — Simple product:** pick Swagger example `simpleWithPercentage`
(attributes/variants omitted).
**B — Variant, size only:** example `variantOneAttribute`
(`attributes` = one MongoId; each variant has one `{attributeId,valueId}`).
**C — Variant, size + color:** example `variantSizeColor`
(`attributes` = size + color MongoIds; each variant pairs both values;
may use `discountType=percentage` with `discountValue=10`).
Sample IDs:
- size attribute `6a64b113dc6f51f903524f5a` · XL value `6a64b145dc6f51f903524f73`
- color attribute `6a64b113dc6f51f903524f5b` · red value `6a64b15fdc6f51f903524f8d`
## Common mistakes
- Sending `attributes` or `variants` for `type=simple`.
- Sending empty value for `attributes` or `variants`.
- Sending invalid JSON.
- Sending nested multipart keys like `variants[0][attributes][0][attributeId]`.
- Sending `valueId` without its related `attributeId`.
- Sending an `attributeId` in variants that is not in top-level `attributes`.
- Using unsupported `discountType` (allowed: `none`, `percentage`, `fixed`).
- Sending non-numeric quantity / price / discountValue.
## Response envelope
Create returns a **message-only** success envelope (no `data` field):
```json
{ "key": "success", "message": "تم تنفيذ العملية بنجاح", "status": 200 }
```
Clients must branch on `key`, not only HTTP status.
Newly created products return `moderationStatus: wait` (pending administration review)
and emit the `product_submitted_for_review` administration-review event.
The administration review notification is created only after Product
persistence succeeds. There is no transaction/outbox for that side-effect:
a request that fails validation, media/taxonomy/AI checks, or Product
persistence never creates a Product-review notification.
The Notification record and administration counter are persisted before
success. External push delivery is deferred and is not part of the HTTP
response latency or success guarantee.
## Error notes
Possible causes of product payload errors:
- `products.invalidProductPayload` — simple/variant field mix, or invalid matrix
- invalid JSON in `attributes` or `variants`
- forbidden `attributes`/`variants` for simple, or missing for variant
- invalid MongoId / taxonomy / attribute–value relation
- unsupported `discountType` or invalid discount math
- missing/invalid images (MIME, size, count) when required
- invalid / non-owned `aiPricingRequestId`
- protected ownership/workflow fields in the body
- missing/invalid secret key or non-provider token (`unauthorized`)
## Compatibility notes
- Server-controlled: provider, workflow, moderation, deletion fields.
- API values `variant` and `percentage`; legacy `multi_attribute` / `ratio`
remain readable and are normalized in responses.
- Media uses direct multipart upload; **no media ownership registry**.
New files are stored under `products/{productId}/`, while MongoDB stores only each final filename (basename).
Previously stored full product-scoped references remain readable, and
provider-scoped filenames remain readable without migration.
- `aiSuggestedPrice` is server-copied; client AI prices are rejected.
PricingRequest currently has no expiry or product-fingerprint fields, so
those integrity checks are not enforceable. The upstream AI engine remains a stub.
- Full field tables: `docs/PRODUCTS_API_CONTRACT.md`.
## Actor
Provider
## Authorization
- **SecretKeyAuth** — required on virtually every `/api` route.
- Header name in this API: `secretkey: {{secretKey}}`
- OpenAPI security scheme: `SecretKeyAuth`
- Note: some clients use the name `x-secret-key`; this backend reads `secretkey` only.
- **ProviderBearerAuth** — required.
- `Authorization: Bearer {{providerToken}}`
## Required Headers
- `lang`: `{{locale}}` (`ar` | `en`)
- `secretkey`: `{{secretKey}}` (SecretKeyAuth — set before running requests)
- `Authorization`: `Bearer {{providerToken}}`
## Request Body
Content type / mode: **formdata**.
### `type`
Product shape selector.
- `simple`: one price and one stock quantity; omit `attributes` and `variants`.
- `variant`: selectable options; send `attributes` and `variants` as JSON strings.
Required.
Type: string.
Allowed values: simple, variant.
Example: simple
### `departmentId`
Must identify an active, non-deleted department.
Required.
Type: string.
Example: 6a65c62109d85c1dbe0e86e2
### `subdepartmentId`
Must identify an active, non-deleted subdepartment that belongs to departmentId.
Required.
Type: string.
Example: 6a65c81b09d85c1dbe0e8732
### `name`
JSON-encoded form-data text field with exactly ar and en (2–200 characters each).
Required.
Type: string.
Example: {"ar":"سماعات سوني","en":"Sony headphones"}
### `description`
JSON-encoded form-data text field with exactly ar and en (1–500 characters each).
Required.
Type: string.
Example: {"ar":"سماعات بحالة ممتازة","en":"Headphones in excellent condition"}
### `condition`
Condition field.
Required.
Type: string.
Allowed values: new, used.
Example: used
### `pricingMethod`
Source of the selling price the client sends. Always required.
- `manual`: client-chosen price; `aiPricingRequestId` is optional.
- `ai`: price sourced from AI; `aiPricingRequestId` is required and
must be a completed provider-owned PricingRequest (`POST /pricing-request` → `data.id`).
Required.
Type: string.
Allowed values: manual, ai.
Example: manual
### `aiPricingRequestId`
Required when `pricingMethod=ai`. First call
[`POST /pricing-request`](#/AI%20Pricing/createPricingRequest),
then pass the returned `data.id` here. Optional when
`pricingMethod=manual` (may still be sent to store the AI suggestion
for display). Suggested price is loaded server-side; client-submitted
AI prices are rejected. PricingRequest currently has no expiry or
product-fingerprint fields.
Optional.
Type: string.
### `quantity`
Available stock quantity.
Required for `type=simple`.
For `type=variant`, omit this field — quantity belongs to each variant.
Optional.
Type: integer.
Example: 4
### `price`
Product price.
Required for `type=simple`.
For `type=variant`, omit this field — price belongs to each variant.
Optional.
Type: number.
Example: 1400
### `discountType`
Discount strategy for a simple product.
Allowed values: `none`, `percentage`, `fixed`.
Use `0` for `discountValue` when `discountType=none`.
Omit for `type=variant` (each variant has its own discount).
Optional.
Type: string.
Allowed values: none, percentage, fixed.
Example: percentage
### `discountValue`
Numeric discount value for a simple product.
Percentage or fixed amount depending on `discountType`.
Use `0` when `discountType=none`. Omit for `type=variant`.
Optional.
Type: number.
Example: 10
### `attributes`
JSON-encoded array of MongoId strings submitted as a multipart/form-data text field.
Required when `type=variant`.
Forbidden when `type=simple`.
Do not send this field for simple products.
Do not send an empty value.
Do not use “Send empty value” in Swagger or Postman.
These IDs are the selected Product Attributes for the variant product
(for example الحجم and اللون). Every variant row must use only
`attributeId` values included in this array.
Example:
```json
[
"6a64b113dc6f51f903524f5a",
"6a64b113dc6f51f903524f5b"
]
```
Optional.
Type: string.
### `variants`
JSON-encoded array of variant objects submitted as a multipart/form-data text field.
Required when `type=variant`.
Forbidden when `type=simple`.
Do not send this field for simple products.
Do not send an empty value.
Do not send nested multipart keys such as
`variants[0][attributes][0][attributeId]`.
Send the full JSON array as the value of this single text field.
Each variant is one purchasable combination of selected attribute values
(example: الحجم = XL, اللون = أحمر, quantity = 5, price = 120,
discountType = none, discountValue = 0).
Each variant object must include exactly:
- `attributes`: array of `{ attributeId, valueId }`
- `quantity` (integer ≥ 1)
- `price` (number > 0)
- `discountType` (`none` | `percentage` | `fixed`)
- `discountValue` (number; use `0` when `discountType=none`)
Backend-enforced rules:
- every `attributeId` inside a variant must appear in the top-level `attributes` array
- each variant must include every selected attribute exactly once
- each `valueId` must belong to its `attributeId`
- duplicate variant combinations are rejected
- quantity, price, and discountValue must be numeric
Example:
```json
[
{
"attributes": [
{
"attributeId": "6a64b113dc6f51f903524f5a",
"valueId": "6a64b145dc6f51f903524f73"
},
{
"attributeId": "6a64b113dc6f51f903524f5b",
"valueId": "6a64b15fdc6f51f903524f8d"
}
],
"quantity": 5,
"price": 120,
"discountType": "none",
"discountValue": 0
}
]
```
Optional.
Type: string.
### `images`
Product images submitted as files through multipart/form-data.
JPEG/PNG/WEBP, maximum 5MB each, 1–10 files. Direct upload; no media registry.
Required.
Type: file[].
Repeat this multipart file field for each upload supported by the endpoint.
Example: <binary image>
### `video`
Optional product video file (MP4/MOV, maximum 50MB) via multipart/form-data.
Optional.
Type: file.
Send as a file field. Prefer PNG / JPG / JPEG / WEBP when image validation applies.
Multipart notes:
- Use Postman **file** type for binary fields.
- Image uploads typically accept PNG / JPG / JPEG / WEBP when validation applies.
- `attributes` and `variants` are **Text** form-data fields containing JSON strings — not nested keys.
- For `type=simple`: omit `attributes` and `variants` (do not send empty values; do not enable “Send empty value”).
- For `type=variant`: send both as valid JSON strings with attributeId/valueId pairs.
- Common mistakes: nested keys like `variants[0][attributes][0][attributeId]`, invalid JSON, attributeId missing from top-level `attributes`, unsupported discountType.
## Query Parameters
No query parameters for this endpoint.
## Path Parameters
No path parameters for this endpoint.
## Successful Response
Standard envelope:
```json
{
"key": "success",
"message": "...",
"status": 200,
"data": {}
}
```
Clients should branch on `key`, not only HTTP status.
- Product created. Message-only response with no `data` field; clients branch on `key: success`.
- Envelope: `{ key, message, status, data? }` — branch on `key`, not only HTTP status.
## Common Failure Cases
- **Validation error** (`key: fail`, HTTP 400) — missing/invalid body, query, or path fields.
- **Invalid secret key** — missing/wrong `secretkey` header or `{{secretKey}}` value.
- **Unauthorized** (`key: unauthorized`, HTTP 419) — missing/expired/wrong bearer token.
- **Blocked / needActive** — account blocked or not activated when the flow requires it.
- **Missing file** — required upload field not attached.
- **Invalid file type** — file signature rejected (use supported image formats).
- **Exception** (`key: exception`, HTTP 500) — unexpected server error.
## QA Notes
- Set `{{secretKey}}` in the selected environment before calling this endpoint.
- Obtain `{{providerToken}}` from **POST /signin** with `userType=provider` when a provider bearer is required.
- Confirm success and failure responses keep the `{ key, message, status }` envelope.
- Confirm responses never include `password`, OTP, or `activationCode` values.