Loyalty.lt

XML product import

Feed your catalogue in from an XML file or URL

XML product import

Point the platform at your product feed and it pulls in products, prices, stock and categories. Run it on a schedule and the app always shows what you actually sell.

Five endpoints:

POST /{locale}/shop/xml-import/validateParse and report, change nothing
POST /{locale}/shop/xml-import/from-urlFetch a feed and import it
POST /{locale}/shop/xml-import/from-fileUpload a file and import it
GET /{locale}/shop/xml-import/statsWhat the last imports did
GET /{locale}/shop/xml-import/field-mappingsEvery element name the parser accepts

Try it now

There is a working feed at /demo-products.xml. Validate it against staging:

curl -X POST "https://staging-api.loyalty.lt/lt/shop/xml-import/validate" \
  -H "X-API-Key: $LOYALTY_API_KEY" \
  -H "X-API-Secret: $LOYALTY_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"xml_url": "https://docs.loyalty.lt/demo-products.xml"}'

Then import it for real by swapping the path to from-url.

Tip

Always validate first. validate parses the feed and tells you how many products it found and which ones it would skip, without touching your catalogue. Every import endpoint also takes validate_only: true if you prefer one code path.

What the feed has to look like

The parser looks for products at the first of these that matches, anywhere in the document:

product · item · products/product · items/item · offer · offers/offer

So all of these are fine:

<products>
  <product>…</product>
</products>
<items>
  <item>…</item>
</items>
<offers>
  <offer>…</offer>
</offers>

A product needs a name and a price. Anything missing either is skipped and reported; the rest of the feed still imports.

<product>
  <name>Ceramic mug</name>
  <price>6.00</price>
</product>

A complete product

<product>
  <id>SKU-1001</id>
  <name>Espresso blend, 250 g</name>
  <description>Dark roast, ground for espresso machines.</description>
  <brand>KAVA virė</brand>
  <model>EB-250</model>
  <ean>4771234567890</ean>
  <sku>SKU-1001</sku>
  <image>https://cdn.example.com/espresso-250.jpg</image>
  <url>https://example.com/shop/espresso-250</url>
  <weight>0.25</weight>

  <price>8.90</price>
  <discounted_price>7.50</discounted_price>
  <currency>EUR</currency>
  <availability>in_stock</availability>
  <stock>42</stock>
  <delivery>1-2 working days</delivery>
  <warranty>14-day returns</warranty>
  <featured>true</featured>

  <categories>
    <category>Drinks</category>
    <category>Coffee beans</category>
  </categories>
  <specifications>
    <specification name="Roast">Dark</specification>
  </specifications>
  <features>
    <feature>Ground for espresso</feature>
  </features>
</product>

Field names

You do not have to rename anything in your feed — each field accepts several spellings. The first one present wins.

The tables below are a snapshot. The live list is an endpoint, and it is the one worth reading from code — aliases get added as feeds in the wild need them:

curl -X GET "https://api.loyalty.lt/lt/shop/xml-import/field-mappings" \
  -H "X-API-Key: $LOYALTY_API_KEY" \
  -H "X-API-Secret: $LOYALTY_API_SECRET"

Product

Stored asAccepted element names
name (required)name, title, product_name
descriptiondescription, desc
brandbrand, manufacturer
modelmodel
eanean, ean13, barcode
skusku, code, id
xml_idid, xml_id, external_id
image_urlimage, image_url, picture
external_urlurl, link, product_url
weightweight
colorcolor
sizesize

Price and stock

Stored asAccepted element names
price (required)price, regular_price
discounted_pricediscounted_price, sale_price, loyalty_program_item_price
currencycurrency
availabilityavailability, in_stock
stock_quantitystock, quantity
delivery_infodelivery, shipping
warranty_infowarranty
external_idid, external_id
featuredfeatured

Categories

<categories><category>…</category></categories>, or a plain repeated <category>. Categories are created as needed, so you do not have to set them up first.

Importing

curl -X POST "https://api.loyalty.lt/lt/shop/xml-import/from-url" \
  -H "X-API-Key: $LOYALTY_API_KEY" \
  -H "X-API-Secret: $LOYALTY_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"xml_url": "https://example.com/feed.xml"}'
curl -X POST "https://api.loyalty.lt/lt/shop/xml-import/from-file" \
  -H "X-API-Key: $LOYALTY_API_KEY" \
  -H "X-API-Secret: $LOYALTY_API_SECRET" \
  -F "xml_file=@products.xml"
await sdk.xmlImportFromUrl('https://example.com/feed.xml');
const stats = await sdk.getXmlImportStats();
$sdk->importFromUrl('https://example.com/feed.xml');
$stats = $sdk->getImportStats();
sdk.import_from_url('https://example.com/feed.xml')
stats = sdk.get_import_stats()

Matching is by sku / xml_id, so re-importing the same feed updates the products already there rather than duplicating them. Run it as often as your prices change.

A feed URL must be publicly reachable over HTTP. Addresses on private networks are refused — the importer will not fetch localhost, 10.x, 192.168.x or similar. Upload the file instead if the feed is internal.

What you get back

{
  "success": true,
  "code": 200,
  "request_id": "…",
  "message": "Import completed",
  "data": {
    "products_created": 2,
    "products_updated": 1,
    "offers_created": 3,
    "errors": ["Missing required fields for product"]
  }
}

errors lists the products that were skipped; the import itself still succeeded for everything else. GET /{locale}/shop/xml-import/stats returns the same counts for previous runs.

When it does not work

On this page