API reference / Products
Bulk create/update products
Service Endpoint Seller token required
| Environment | Base URL + Path |
|---|---|
| Production | https://open.mallplus.ph/open/v1/products/bulk |
| Sandbox | https://sandbox.open.mallplus.ph/open/v1/products/bulk |
Common Signing and Seller Headers
| Header | Type | Required | Rules | Description |
|---|---|---|---|---|
| X-MallPlus-Partner-Id | string | Yes | Issued client ID for the calling app. | Identifies the partner app whose secret signs the request. |
| X-MallPlus-Timestamp | integer | Yes | Unix timestamp in seconds; default acceptance window is 90 seconds. | Prevents replay outside the allowed signing window. |
| X-MallPlus-Signature-Version | string | Yes | Use 3 for HMAC v3. | Selects the request signing algorithm. |
| X-MallPlus-Nonce | string | Yes | 32-64 lowercase hexadecimal characters, unique per request. | Replay-protection nonce included in the v3 signing base string. |
| X-MallPlus-Signature | string | Yes | HMAC-SHA256 over timestamp, client ID, method, path, canonical query, body hash, and nonce. | Cryptographic proof that the request was signed with the app secret. |
| X-MallPlus-Access-Token | string | Yes | Required when the operation says seller token required. | Seller OAuth access token returned by the authorization flow. |
| X-MallPlus-Seller-Id | string | Yes | Required when X-MallPlus-Access-Token is required. | Seller ID bound to the seller OAuth token. |
Parameters
This operation does not define path or query parameters in OpenAPI.
Request Body required
| Field | Type | Required | Rules | Description |
|---|---|---|---|---|
| products | array<ProductBulkItemRequest> | Yes | Min items: 1; Max items: 5000 | Products included in the bulk request. |
| products[] | ProductBulkItemRequest | No | No additional properties | Bulk product input. Every item must carry at least one variant with price and stock, matching the single-create contract. |
| products[].title | string | Yes | Min length: 8; Max length: 100 | Product title, 8-100 characters. |
| products[].description | string | No | Max length: 10000 | Human-readable description. |
| products[].category | string | Yes | Min length: 1; Max length: 120 | Category id. Must be a LEAF category (one with no active children) — a parent category is rejected. The mandatory attributes for the chosen category are discoverable via GET /open/v1/categories/{id}/attributes. |
| products[].images | array<string<uri>> | Yes | Min items: 1; Max items: 20 | Product image URLs. At least one HTTPS URL is required. |
| products[].images[] | string<uri> | No | Format: uri; Max length: 2048; Pattern: ^https:// | - |
| products[].weight | number | No | - | Shipping weight in GRAMS. Required unless use_variant_dimensions is true. |
| products[].length | number | No | - | Parcel length in CENTIMETRES. Required unless use_variant_dimensions is true. |
| products[].width | number | No | - | Parcel width in CENTIMETRES. Required unless use_variant_dimensions is true. |
| products[].height | number | No | - | Parcel height in CENTIMETRES. Required unless use_variant_dimensions is true. |
| products[].use_variant_dimensions | boolean | No | - | Ship by variant. When true, weight and dimensions are required on EVERY variant instead of on the product, and the product-level figures are derived from them. |
| products[].attribute_values | array<object> | No | Max items: 50 | Category-mapped attribute values. Which attributes are mandatory, and the allowed values for SELECT/MULTIVALUE types, come from the category — fetch them from GET /open/v1/categories/{id}/attributes. |
| products[].attribute_values[] | object | No | No additional properties | - |
| products[].attribute_values[].attribute_id | string | Yes | Min length: 1; Max length: 255 | Attribute id value. |
| products[].attribute_values[].value | oneOf | Yes | - | Value details. |
| products[].variants | array<ProductVariantCreateRequest> | Yes | Min items: 1; Max items: 100 | Product variants. |
| products[].variants[] | ProductVariantCreateRequest | No | No additional properties | A product variant to create with its price, stock, and option values. Supply options as the variant axis values (for example {"Colour":"Red","Size":"M"}); the option groups they belong to are what the product detail page renders its selectors from, so a variant set with no option values renders as a single un-selectable listing. Physical attributes are required on each variant only when the product sets use_variant_dimensions: true. |
| products[].variants[].title | string | No | Min length: 1; Max length: 200 | Partner-visible title. |
| products[].variants[].sku | string | No | Min length: 1; Max length: 100 | Seller SKU. |
| products[].variants[].price | integer | Yes | Minimum: 0; Maximum: 99999999 | Integer amount in PHP centavos. |
| products[].variants[].stock | integer | Yes | Minimum: 0; Maximum: 1000000000 | Stock count for the primary variant. |
| products[].variants[].options | object | No | - | Variant axis values keyed by option-group name. Required for correct PDP rendering when the product has more than one variant. |
| products[].variants[].images | array<string<uri>> | No | Max items: 20 | Variant-specific image URLs. Falls back to the product images when omitted. |
| products[].variants[].images[] | string<uri> | No | Format: uri; Max length: 2048; Pattern: ^https:// | - |
| products[].variants[].weight | number | No | - | Variant weight in GRAMS. Required when the product sets use_variant_dimensions: true. |
| products[].variants[].length | number | No | - | Variant length in CENTIMETRES. Required when the product sets use_variant_dimensions: true. |
| products[].variants[].width | number | No | - | Variant width in CENTIMETRES. Required when the product sets use_variant_dimensions: true. |
| products[].variants[].height | number | No | - | Variant height in CENTIMETRES. Required when the product sets use_variant_dimensions: true. |
Response Parameters
| Field | Type | Rules | Description |
|---|---|---|---|
| success | enum | Allowed: true | Whether the bulk request was processed. |
| data | object | No additional properties | Response or event payload for this schema. |
| data.created | array<Product> | - | Products that were created, in submission order. |
| data.created[] | Product | No additional properties | Partner-safe product view. Supplier-internal fields are stripped at the API boundary. |
| data.created[].product_id | string | - | Product ID associated with this value. |
| data.created[].short_id | string | - | Human-friendly alternate product identifier |
| data.created[].name | string | - | Partner-visible name. |
| data.created[].description | string | - | Human-readable description. |
| data.created[].status | enum | Allowed: live, unlisted, delisted, proposed, under_review, archived | Current product status. This is the complete set — settable by the partner via POST /open/v1/products/{id}/status: live, unlisted, delisted; read-only, set by the platform: proposed, under_review, archived (proposed after create, under_review while in review, archived after delete). Every value here is also accepted by GET /open/v1/products?status=. |
| data.created[].sku | string | - | SKU of the primary variant |
| data.created[].price | integer | - | Integer amount in PHP centavos. |
| data.created[].stock | integer | - | Stock of the primary variant |
| data.created[].category | string | - | Product category. |
| data.created[].thumbnail | string | - | Product thumbnail image URL. |
| data.created[].images | array<string> | - | Product image URLs, in display order. |
| data.created[].images[] | string | - | Publicly reachable https image URL. |
| data.created[].weight | number | - | Weight in grams |
| data.created[].dimensions | object | No additional properties | Package dimensions in centimetres. |
| data.created[].dimensions.length | number | - | Package length in centimetres. |
| data.created[].dimensions.width | number | - | Package width in centimetres. |
| data.created[].dimensions.height | number | - | Package height in centimetres. |
| data.created[].variants | array<ProductVariant> | - | Product variants. |
| data.created[].variants[] | ProductVariant | No additional properties | A single purchasable variant of a product. |
| data.created[].variants[].variant_id | string | - | Product variant ID associated with this value. |
| data.created[].variants[].sku | string | Nullable | Seller SKU. |
| data.created[].variants[].price | integer | Nullable | Integer amount in PHP centavos. |
| data.created[].variants[].stock_quantity | integer | Nullable | Available stock on hand at the default location |
| data.created[].variants[].options | array<object> | - | Variant option values (e.g. Size=M, Color=Black). |
| data.created[].variants[].options[] | object | No additional properties | One option of the variant, as a name/value pair. |
| data.created[].variants[].options[].name | string | Nullable | Option name, for example Size. |
| data.created[].variants[].options[].value | string | Nullable | Option value, for example M. |
| data.created[].variants[].dimensions | object | No additional properties | Package dimensions in centimetres. |
| data.created[].variants[].dimensions.length | number | - | Package length in centimetres. |
| data.created[].variants[].dimensions.width | number | - | Package width in centimetres. |
| data.created[].variants[].dimensions.height | number | - | Package height in centimetres. |
| data.created[].variants[].weight | number | - | Weight in grams |
| data.created[].variants[].images | array<string> | - | Variant image URLs, in display order. |
| data.created[].variants[].images[] | string | - | Publicly reachable https image URL. |
| data.created[].options_order | array<object> | - | Variant option groups in display order, each with its values in order — the order a storefront renders the variant picker in. Derived at create time from the order the options were submitted in; absent on products with no option groups. |
| data.created[].options_order[] | object | No additional properties | - |
| data.created[].options_order[].title | string | - | Option group name, e.g. Colour. |
| data.created[].options_order[].values | array<string> | - | The group's values, in display order. |
| data.created[].options_order[].values[] | string | - | - |
| data.created[].created_at | string<date-time> | Format: date-time | Created at as an ISO-8601 timestamp. |
| data.created[].updated_at | string<date-time> | Format: date-time | Updated at as an ISO-8601 timestamp. |
| data.errors | array<ProductBulkCreateError> | - | Entries that were rejected. Empty when every product was created. |
| data.errors[] | ProductBulkCreateError | No additional properties | One rejected entry from a bulk create, addressed by its position in the request. |
| data.errors[].index | integer | - | Zero-based position of the rejected product in the submitted products array. |
| data.errors[].message | string | - | Why this entry was rejected. |
| data.summary | object | No additional properties | Counts for the submitted batch. |
| data.summary.total | integer | - | Total number of items in this summary. |
| data.summary.succeeded | integer | - | Number of items that completed successfully. |
| data.summary.failed | integer | - | Number of items that failed. |
Error Codes
| HTTP Status | Schema | Description |
|---|---|---|
| 400 | ErrorResponse | Validation error, or a missing/malformed required signing header (BAD_REQUEST) |
| 401 | ErrorResponse | Unauthorized — invalid credentials, invalid signature, or expired timestamp (TIMESTAMP_EXPIRED) |
| 403 | ErrorResponse | Forbidden — insufficient scope |
| 409 | ErrorResponse | The request conflicts with the current resource state or reuses an idempotency key |
| 413 | ErrorResponse | The request body exceeds the endpoint payload limit |
| 422 | ErrorResponse | The request is well-formed but cannot be processed in the resource’s current state |
| 429 | ErrorResponse | The partner or endpoint rate limit has been exceeded |
| 502 | ErrorResponse | The upstream commerce service rejected the request or returned an invalid response |
| 503 | ErrorResponse | A required platform or upstream dependency is temporarily unavailable |
| 504 | ErrorResponse | The upstream commerce service did not respond before the platform timeout |
Machine-readable codes are returned in error.code: ACCOUNT_LOCKED, APPROVE_FAILED, APP_LIMIT_REACHED, APP_NOT_FOUND, AUTHORIZATION_CODE_EXPIRED, AUTHORIZATION_REVOKED, AUTH_CODE_EXPIRED, AUTH_CODE_USED, BAD_REQUEST, CANCELLATION_ALREADY_PROCESSED, CANCELLATION_DEADLINE_EXCEEDED, CANCEL_FAILED, CANNOT_DELETE_ACCOUNT_WITH_APPS, CONCURRENT_MODIFICATION, CONFLICT, CREATE_FAILED, DISPUTE_FAILED, DUPLICATE, EMAIL_ALREADY_EXISTS, EMAIL_NOT_VERIFIED, FILE_TOO_LARGE, FORBIDDEN, HMAC_VERSION_DEPRECATED, IDEMPOTENCY_KEY_IN_PROGRESS, IDEMPOTENCY_KEY_REQUIRED, IDEMPOTENCY_KEY_REUSED, INTERNAL_ERROR, INVALID_AUTHORIZATION_CODE, INVALID_CREDENTIALS, INVALID_DEVELOPER_TYPE, INVALID_FILE_CONTENT, INVALID_FILE_TYPE, INVALID_JSON, INVALID_NONCE, INVALID_PATH, INVALID_PICKUP_DATE, INVALID_REFRESH_TOKEN, INVALID_REQUEST, INVALID_SIGNATURE, INVALID_STATE, INVALID_TRANSITION, INVALID_VERIFICATION_TOKEN, MAINTENANCE, MEMBER_PERMISSION_DENIED, MISSING_NONCE, NONCE_REUSED, NOT_FOUND, NOT_IMPLEMENTED, ORDER_NOT_CANCELLABLE, PAYLOAD_TOO_LARGE, PICKUP_DATES_UNAVAILABLE, PRODUCT_HAS_ACTIVE_ORDERS, PRODUCT_UNDER_REVIEW, PROFILE_ALREADY_SUBMITTED, PROFILE_TYPE_MISMATCH, PROXY_ERROR, RATE_LIMITED, REDIRECT_URL_MISMATCH, REFRESH_TOKEN_EXPIRED, REFRESH_TOKEN_REUSED, REJECT_FAILED, RETURN_ALREADY_PROCESSED, RETURN_DEADLINE_EXCEEDED, RE_AUTHORIZATION_REQUIRED, SANDBOX_LIMIT_REACHED, SELLER_TOKEN_REQUIRED, SERVICE_UNAVAILABLE, SESSION_EXPIRED, SHIPMENT_ALREADY_ARRANGED, SHIPMENT_NOT_ARRANGED, SHIPPING_LABEL_UNAVAILABLE, SHIP_FAILED, SIGNATURE_REPLAYED, SSRF_CHECK_FAILED, TEST_SHOP_LIMIT_REACHED, TIMESTAMP_EXPIRED, TOKEN_REVOKED, TOO_MANY_REQUESTS, UNAUTHORIZED, UPLOAD_ERROR, UPLOAD_NOT_CONFIGURED, UPSTREAM_ERROR, UPSTREAM_TIMEOUT, VALIDATION_ERROR, VERIFICATION_LINK_USED, VERIFICATION_TOKEN_EXPIRED, WEBHOOK_SUBSCRIPTION_EXISTS
Request Example
curl -X POST "https://open.mallplus.ph/open/v1/products/bulk" \
-H "X-MallPlus-Partner-Id: mp_partner_123" \
-H "X-MallPlus-Timestamp: 1786924800" \
-H "X-MallPlus-Signature-Version: 3" \
-H "X-MallPlus-Nonce: 4f8b9a0c4d5e6f708192a3b4c5d6e7f8" \
-H "X-MallPlus-Signature: <hex_hmac_sha256>" \
-H "X-MallPlus-Access-Token: seller_access_token" \
-H "X-MallPlus-Seller-Id: seller_123" \
-H "Content-Type: application/json" \
--data '{
"products": [
{
"title": "example_title",
"description": "example_description",
"category": "example_category",
"images": [
"https://example.com/resource"
],
"weight": 1,
"length": 1,
"width": 1,
"height": 1,
"use_variant_dimensions": true,
"attribute_values": [
{
"attribute_id": "attribute_id_123",
"value": "example_value"
}
],
"variants": [
{
"title": "example_title",
"sku": "example_sku",
"price": 0,
"stock": 0,
"options": {},
"images": [
"string<uri>"
],
"weight": 1,
"length": 1,
"width": 1,
"height": 1
}
]
}
]
}'Response Example
{
"success": true,
"data": {
"created": [
{
"product_id": "prod_sQHXbsxX8EQ8",
"name": "Air Force 1 Mid",
"status": "proposed",
"sku": "NIKE-AF1-MID-42",
"price": 599900,
"stock": 10,
"category": "Electronics",
"created_at": "2026-08-26T04:33:49.300Z",
"updated_at": "2026-08-26T04:33:49.300Z"
}
],
"errors": [],
"summary": {
"total": 1,
"succeeded": 1,
"failed": 0
}
}
}