APIAPI referenceCatalog Push API
Push Products
Upsert products (mode = replace_all runs tombstone products outside the run after the last batch).
POST/v1/stores/{store_id}/catalog/products:batchScope catalog:write
- Scope:
catalog:write(scopes and plan access) - Rate limit: 600 requests/min per API key (test keys: 60/min); catalog batches 120/min per store (rate limits)
Upsert products (`mode = replace_all` runs tombstone products outside the run after the last batch).
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| store_idrequired | path | string | |
| Idempotency-Keyrequired | header | string | Replays return the original |
Request body (ProductsBatch)
| Field | Type | Description |
|---|---|---|
| batch_index | integer | ≥ 0 · ≤ 9999 |
| is_last_batch | boolean | |
| mode | "upsert" | "replace_all" | default "upsert" |
| productsrequired | CatalogProductInput[] | CatalogProductInput items (validated individually; invalid items are reported in rejected[])≥ 1 items · ≤ 500 items |
| products[].attributes | CatalogAttributeInput[] | |
| products[].attributes[].coderequired | string | 1–128 characters |
| products[].attributes[].labelrequired | string | 1–255 characters |
| products[].attributes[].valuesrequired | string[] | ≤ 100 items |
| products[].brand | string | null | ≤ 255 characters |
| products[].category_external_ids | string[] | |
| products[].description_html | string | null | Sanitized server-side |
| products[].description_text | string | null | |
| products[].external_idrequired | string | 1–128 characters |
| products[].handle | string | null | ≤ 255 characters |
| products[].images | CatalogImageInput[] | |
| products[].images[].alt | string | ≤ 500 characters |
| products[].images[].urlrequired | string | format uri · ≤ 2048 characters |
| products[].images[].variant_external_ids | string[] | |
| products[].is_published | boolean | default true |
| products[].product_type | string | null | ≤ 255 characters |
| products[].status | "active" | "draft" | "archived" | default "active" |
| products[].tags | string[] | |
| products[].titlerequired | string | 1–255 characters |
| products[].translations | CatalogTranslationInput[] | |
| products[].translations[].description_text | string | null | |
| products[].translations[].localerequired | string | 2–35 characters · pattern `^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$` |
| products[].translations[].titlerequired | string | 1–255 characters |
| products[].translations[].url | string | null | format uri · ≤ 2048 characters |
| products[].urlrequired | string | https on a store origin or extra_link_domains (else url_not_allowed)format uri · ≤ 2048 characters |
| products[].variantsrequired | CatalogVariantInput[] | ≥ 1 items |
| products[].variants[].add_to_cart_url | string | null | format uri · ≤ 2048 characters |
| products[].variants[].available_qty | integer | null | |
| products[].variants[].compare_at_price | Money | null | |
| products[].variants[].compare_at_price.amountrequired | string | pattern `^-?\d+(\.\d{1,6})?$` |
| products[].variants[].compare_at_price.currencyrequired | string | pattern `^[A-Z]{3}$` |
| products[].variants[].external_idrequired | string | 1–128 characters |
| products[].variants[].gtin | string | null | ≤ 64 characters |
| products[].variants[].image_url | string | null | format uri · ≤ 2048 characters |
| products[].variants[].is_compare_at_reference | boolean | |
| products[].variants[].is_purchasable | boolean | |
| products[].variants[].options | { [key: string]: string } | |
| products[].variants[].pricerequired | Money | An amount with its currency (`03` §2.4 `Money`). |
| products[].variants[].price.amountrequired | string | pattern `^-?\d+(\.\d{1,6})?$` |
| products[].variants[].price.currencyrequired | string | pattern `^[A-Z]{3}$` |
| products[].variants[].price_includes_tax | boolean | |
| products[].variants[].prices | CatalogPriceInput[] | |
| products[].variants[].prices[].compare_at_price | Money | An amount with its currency (`03` §2.4 `Money`). |
| products[].variants[].prices[].pricerequired | Money | An amount with its currency (`03` §2.4 `Money`). |
| products[].variants[].prices[].price_listrequired | string | pattern `^(currency:[A-Z]{3}|market:[A-Z]{2})$` |
| products[].variants[].sku | string | null | ≤ 255 characters |
| products[].variants[].stock_status | "in_stock" | "low_stock" | "out_of_stock" | "backorder" | "unknown" | |
| products[].variants[].title | string | null | Default: option values joined " / "≤ 255 characters |
| run_id | string | pattern `^[A-Za-z0-9_-]{1,64}$` |
Example request
curl -X POST "https://api.shopassist.coec.dk/v1/stores/sto_01j8000000e008000000000000/catalog/products:batch" \
-H "Authorization: Bearer $SHOPASSIST_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"batch_index":0,"is_last_batch":true,"mode":"upsert","products":[{"attributes":[{"code":"string","label":"string","values":["string"]}],"brand":"string","category_external_ids":["string"],"description_html":"string","description_text":"string","external_id":"string","handle":"string","images":[{"alt":"string","url":"https://shop.example/products/linen-shirt","variant_external_ids":["string"]}],"is_published":true,"product_type":"string","status":"active","tags":["string"],"title":"string","translations":[{"description_text":"string","locale":"da-DK","title":"string","url":"https://shop.example/products/linen-shirt"}],"url":"https://shop.example/products/linen-shirt","variants":[{"add_to_cart_url":"https://shop.example/products/linen-shirt","available_qty":1,"compare_at_price":{"amount":"499.95","currency":"DKK"},"external_id":"string","gtin":"string","image_url":"https://shop.example/products/linen-shirt","is_compare_at_reference":true,"is_purchasable":true,"options":{},"price":{"amount":"499.95","currency":"DKK"},"price_includes_tax":true,"prices":[{"compare_at_price":null,"price":null,"price_list":null}],"sku":"string","stock_status":"in_stock","title":"string"}]}],"run_id":"string"}'Response 202 (BatchAccepted)
| Field | Type | Description |
|---|---|---|
| acceptedrequired | integer | ≥ 0 |
| rejectedrequired | ItemRejection[] | |
| rejected[].errorrequired | ItemError | `{ code; message }` of a rejected item. |
| rejected[].error.coderequired | "validation_error" | "currency_mismatch" | "duplicate_external_id" | "too_many_variants" | "url_not_allowed" | |
| rejected[].error.messagerequired | string | |
| rejected[].indexrequired | integer | ≥ 0 |
| sync_run_idrequired | string | pattern `^sync_[0-9a-hjkmnp-tv-z]{26}$` |
{
"accepted": 0,
"rejected": [
{
"error": {
"code": "validation_error",
"message": "string"
},
"index": 0
}
],
"sync_run_id": "sync_01j8000000e008000000000000"
}Errors
Every error uses the error envelope; the code field is stable.
400Bad Request401Unauthorized403Forbidden404Not Found409Conflict413Content Too Large422Unprocessable Content429Too Many Requests
Last updated on