Import a catalog
This guide imports products from your own system into a marketplace, then keeps them up to date on every run. It uses the Admin API; the same calls exist in the Partners API for a vendor importing their own catalog, with the differences listed at the end.
Before you start
Section titled “Before you start”Collect the identifiers your listings need, once per import:
| Need | Call | Keep |
|---|---|---|
| The vendor | GET /api/v1/users?group_id=… or GET /api/v1/users/search?email=… |
id, sent as user_id |
| Categories | GET /api/v1/listings/categories |
id, sent as category_id |
| Custom fields | GET /api/v1/listings/attributes |
each attribute’s alias, and its options for select fields |
| How it sells | GET /api/v1/listings/order_types |
the order type, when the marketplace has several |
Map your product categories to Kreezalid category ids in your own configuration, rather than matching titles on every run.
1. Create listings in bulk
Section titled “1. Create listings in bulk”POST /api/v1/listings/bulk takes an array of listings, each with the same fields as
Create a listing. Give every listing your
product code in external_id: it is how you will find it again.
curl -X POST https://acme.mykreezalid.com/api/v1/listings/bulk \ -u "$KREEZALID_API_KEY:$KREEZALID_API_SECRET" \ -H "Content-Type: application/json" \ -d '[ { "external_id": "SKU-1001", "user_id": 4242, "category_id": 812, "title": "Linen apron", "description": "<p>Washed linen, adjustable straps.</p>", "price": 39.9, "quantity": 25, "images": ["https://cdn.example.com/products/sku-1001-front.jpg"], "attributes": { "material": "Linen" } }, { "external_id": "SKU-1002", "user_id": 4242, "category_id": 812, "title": "Cotton tea towel", "description": "Set of two.", "price": 14.5, "quantity": 80 } ]'const auth = Buffer.from(`${process.env.KREEZALID_API_KEY}:${process.env.KREEZALID_API_SECRET}`).toString('base64');
export async function createBatch(products) { const res = await fetch('https://acme.mykreezalid.com/api/v1/listings/bulk', { method: 'POST', headers: { Authorization: `Basic ${auth}`, 'Content-Type': 'application/json' }, body: JSON.stringify(products), }); const { responses } = await res.json(); // responses[i] answers products[i] return products.map((p, i) => ({ sku: p.external_id, ...responses[i] }));}<?phpfunction createBatch(array $products): array{ $ch = curl_init('https://acme.mykreezalid.com/api/v1/listings/bulk'); curl_setopt_array($ch, [ CURLOPT_USERPWD => getenv('KREEZALID_API_KEY') . ':' . getenv('KREEZALID_API_SECRET'), CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode(array_values($products)), CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 300, ]); $body = json_decode(curl_exec($ch), true);
return $body['responses'] ?? [];}import os, requests
AUTH = (os.environ["KREEZALID_API_KEY"], os.environ["KREEZALID_API_SECRET"])
def create_batch(products): res = requests.post( "https://acme.mykreezalid.com/api/v1/listings/bulk", auth=AUTH, json=products, timeout=300, ) return res.json()["responses"] # responses[i] answers products[i]The response has one entry per listing, in the order you sent them:
{ "responses": [ { "id": 645646, "message": "Listing created", "success": true }, { "id": null, "message": "Error. No listing created. price: …", "success": false } ]}A refused listing does not stop the others. Store the id of every created listing next to
your product, and log the message of the refused ones. Besides invalid fields, a listing is
refused when the vendor has reached the number of listings the marketplace allows them.
2. Update on the next runs
Section titled “2. Update on the next runs”Creating twice creates two listings: external_id is not unique. On every run after the first,
update the listings you already created and create only the new products.
-
Find the listing. Use the id you stored. If you did not keep it, look it up by your code:
Terminal window curl "https://acme.mykreezalid.com/api/v1/listings/search?external_id=SKU-1001&user_id=4242" \-u "$KREEZALID_API_KEY:$KREEZALID_API_SECRET"The match is exact and the answer is a
listingsarray: empty means the product is new. -
Send what changed.
PATCH /api/v1/listings/{id}with only the fields to change:Terminal window curl -X PATCH https://acme.mykreezalid.com/api/v1/listings/645646 \-u "$KREEZALID_API_KEY:$KREEZALID_API_SECRET" \-H "Content-Type: application/json" \-d '{"price": 36.9, "quantity": 12}'{ "success": true, "message": "Listing has been updated" }A refused update answers
"success": falsewithError. No updates were made.and no detail. Check the values against the field rules of Create a listing. -
Retire products you no longer sell with
"status": "archived"rather than deleting them, so their past orders keep their listing.DELETE /api/v1/listings/{id}removes one for good.
Images
Section titled “Images”imagesis a list of public URLs. Kreezalid downloads each one during the request and stores its own copy, so the URL only needs to work at import time.- On creation, an image that cannot be downloaded is skipped and the listing is created without it.
- On update, sending
imagesreplaces all of the listing’s images with the ones that could be downloaded. When none could, the current images stay. Do not sendimageson every run if they did not change: each run would download them again.
Variants
Section titled “Variants”Sizes, colours and other variants are set on update, not on creation: create the listing,
then PATCH it with a variants array.
curl -X PATCH https://acme.mykreezalid.com/api/v1/listings/645646 \ -u "$KREEZALID_API_KEY:$KREEZALID_API_SECRET" \ -H "Content-Type: application/json" \ -d '{ "variants": [ { "external_id": "SKU-1001-S", "sku": "SKU-1001-S", "title": "Small", "price": 36.9, "quantity": 5 }, { "external_id": "SKU-1001-L", "sku": "SKU-1001-L", "title": "Large", "price": 39.9, "quantity": 7 } ] }'The array is the complete list of the listing’s variants:
- a variant whose
external_id, or failing thatsku, matches an existing one updates it; - a variant that matches none is created;
- an existing variant missing from the array is deleted.
So always send every variant of the listing, not only those that changed. An empty array
changes nothing. Read them back with GET /api/v1/listings/{id}/variants.
Run it reliably
Section titled “Run it reliably”- One request at a time per vendor. Saving a listing updates its vendor’s counters, so parallel writes for the same vendor wait on each other and can fail. Parallelise across vendors, not within one.
- Make every run resumable. Keep, per product, the Kreezalid id and a hash of what you last sent. Skip unchanged products, and you can stop and restart an import at any point.
- Read
successon every answer, not only the HTTP status. See Errors. - Keep your own data with the listing in metafields, for instance
pim.product_id, whenexternal_idalready holds the vendor’s code. - Listen instead of polling to learn about changes made on the marketplace: the
listing.updateandlisting.deletewebhooks.
With the Partners API
Section titled “With the Partners API”A vendor importing their own catalog uses the same requests under /api/partners/v1, with
these differences:
user_idis ignored: every listing belongs to the vendor of the credential, and is created approved.- There is no search endpoint. Keep the ids returned at creation, or rebuild the mapping by
paging through
GET /api/partners/v1/listingsand readingexternal_id. - The variant synchronisation described above is specific to the Admin API: do not rely on
variantsin Partners API updates. - Listings can be attached to the vendor’s stores with
store_idorstore_external_id. See Stores.