APIAPI referenceCatalog Push API
Push Locations
Upsert locations (opening_hours → hours_source = platform).
POST/v1/stores/{store_id}/catalog/locations: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 locations (`opening_hours` → `hours_source = platform`).
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| store_idrequired | path | string | |
| Idempotency-Keyrequired | header | string | Replays return the original |
Request body (LocationsBatch)
| Field | Type | Description |
|---|---|---|
| locationsrequired | CatalogLocationInput[] | CatalogLocationInput items (validated individually; invalid items are reported in rejected[])≥ 1 items · ≤ 500 items |
| locations[].addressrequired | CatalogAddressInput | `Address` (03 §2.4) as pushed by the merchant. |
| locations[].address.cityrequired | string | null | |
| locations[].address.country_coderequired | string | null | pattern `^[A-Z]{2}$` |
| locations[].address.formattedrequired | string | |
| locations[].address.line1required | string | null | |
| locations[].address.line2required | string | null | |
| locations[].address.postal_coderequired | string | null | |
| locations[].address.regionrequired | string | null | |
| locations[].email | string | ≤ 254 characters |
| locations[].external_idrequired | string | 1–128 characters |
| locations[].has_pickup | boolean | |
| locations[].kindrequired | "warehouse" | "retail_store" | "pickup_point" | "virtual" | |
| locations[].latitude | number | ≥ -90 · ≤ 90 |
| locations[].longitude | number | ≥ -180 · ≤ 180 |
| locations[].namerequired | string | 1–255 characters |
| locations[].opening_hours | OpeningHoursInput | `locations.opening_hours` (02 §16.10): `{ weekly; exceptions }`. |
| locations[].opening_hours.exceptions | OpeningHoursException[] | |
| locations[].opening_hours.exceptions[].daterequired | string | pattern `^\d{4}-\d{2}-\d{2}$` |
| locations[].opening_hours.exceptions[].intervals | unknown[][] | |
| locations[].opening_hours.exceptions[].is_closed | boolean | |
| locations[].opening_hours.exceptions[].note | string | ≤ 500 characters |
| locations[].opening_hours.weeklyrequired | OpeningHoursWeekday[] | |
| locations[].opening_hours.weekly[].dayrequired | string (enum) | one of 1, 2, 3, 4, 5, 6, 7 |
| locations[].opening_hours.weekly[].intervalsrequired | unknown[][] | |
| locations[].phone | string | ≤ 64 characters |
| locations[].timezone | string | ≤ 64 characters |
Example request
curl -X POST "https://api.shopassist.coec.dk/v1/stores/sto_01j8000000e008000000000000/catalog/locations:batch" \
-H "Authorization: Bearer $SHOPASSIST_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"locations":[{"address":{"city":"string","country_code":"string","formatted":"string","line1":"string","line2":"string","postal_code":"string","region":"string"},"email":"string","external_id":"string","has_pickup":true,"kind":"warehouse","latitude":-90,"longitude":-180,"name":"string","opening_hours":{"exceptions":[{"date":"string","intervals":[null],"is_closed":true,"note":"string"}],"weekly":[{"day":1,"intervals":[null]}]},"phone":"string","timezone":"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