Field reference
Full and update feed schemas in XML and JSON: envelope, product fields, format differences and per-product attributes.
All feeds share the same envelope and field semantics. The full feed carries every field below; the update feed carries only the fast-moving subset — the ✓ column — see Update feed. The SKU is the unique identifier for every product — use it as your import key.
Feed envelope
XML
<syntechstock>
<data>
<count>2828</count> <!-- products in this file -->
<currency>ZAR</currency> <!-- currency of all prices in this file -->
</data>
<stock>
<product>...</product>
<product>...</product>
</stock>
</syntechstock>JSON
{
"syntechstock": {
"count": 2828,
"currency": "ZAR",
"products": [ { "sku": "..." }, { "sku": "..." } ]
}
}CSV has no envelope — a header row plus one row per product, with currency as a per-row column instead.
Product fields
| Field | Type | Update feed | Description |
|---|---|---|---|
sku | string | ✓ | Unique product identifier — use as your unique key |
name | string | — | Product name |
price | decimal | ✓ | Your cost price — what you pay Syntech for the item — excluding VAT, in the feed's currency. Always the normal cost; it does not change during a promotion (see Pricing) |
rrp_incl | decimal | ✓ | Recommended retail price, including VAT |
recommended_margin | decimal | ✓ | Suggested margin, based on the normal price (see Pricing) |
promo_price | decimal | ✓ | Promotional cost price, excluding VAT — empty (XML) / false (JSON) / N/A (CSV) when not on promotion |
promo_starts / promo_ends | date | ✓ | Promotion window (inclusive) — empty (XML) / false (JSON) / N/A (CSV) when not on promotion |
cptstock | integer | ✓ | Stock on hand — Cape Town |
jhbstock | integer | ✓ | Stock on hand — Johannesburg |
dbnstock | integer | ✓ | Stock on hand — Durban |
nextshipmenteta | date | ✓ | ETA of the next incoming shipment — empty when none is scheduled |
url | string | — | Product page on syntech.co.za |
description | HTML | — | Full description — features, specifications, what's in the box |
shortdesc | HTML | — | Short description |
weight | decimal | — | Product weight in grams — 0 when not captured |
length / width / height | decimal | — | Product dimensions in centimetres — 0 when not captured |
featured_image | URL | — | Primary product image. CSV header: featured image (space, not underscore) |
additional_images | URL list | — | Gallery images. CSV header: additional images |
all_images | string | — | Every image URL, pipe-separated — convenience field for single-column importers. CSV header: all images |
categories | string | — | Category names, comma-separated |
categoriesalt | string | — | Category names, pipe-separated |
categorytree | string | — | Hierarchy as Parent > Child; multiple trees pipe-separated. Not present in CSV |
categorytreealt | string | — | Hierarchy as Parent/Child; multiple trees pipe-separated. Not present in CSV |
attributes | container | — | Product attributes — the set varies per product, see below. In CSV: one column per attribute, from a fixed legacy set |
last_modified | datetime | ✓ XML/JSON | When this product's data last changed — useful for change detection on your side. Not present in CSV |
currency | string | ✓ CSV only | Per-row currency column — CSV has no envelope; in XML/JSON this lives in the envelope |
Pricing
price (and promo_price when on promotion) is your dealer cost, excluding VAT — the amount you pay Syntech for the item. rrp_incl is the recommended retail price, including VAT. Worked example: cost 169.00, RRP incl 299.00 → 260.00 excluding VAT (15%), and (260 − 169) ÷ 260 = 35% — the recommended_margin. The margin is calculated against the normal price. Remember to add your margin and VAT when calculating your selling price.
Promotions
price always shows the normal cost — it does not switch to the promo value while a promotion runs. To price correctly, apply this rule in your importer:
If promo_price is populated and today falls between promo_starts and promo_ends (inclusive), use promo_price; otherwise use price.
Always gate on the dates rather than assuming a populated promo_price is currently active. Example: price: 8128.00, promo_price: 7999.00, window 2026-07-30 → 2026-08-30 — your cost is 7999.00 during the window and 8128.00 outside it.
Update feed
The update feed shares the envelope above and covers the same full product set (same count), but each product carries only the fast-moving fields marked ✓ — pricing, promos, branch stock, shipment ETA and last_modified. Omitting names, content, images, categories and attributes makes the file dramatically smaller, which is exactly what you want for frequent syncs.
Recommended pattern: import and refresh content from the full feed periodically, then run the update feed on your frequent schedule — match on sku and update only the fields present. Field semantics are identical to the full feed, including the not-on-promotion representations (false in JSON, empty in XML, N/A in CSV).
XML
<product>
<sku>DM-12</sku>
<price>169.00</price>
<rrp_incl>299.00</rrp_incl>
<recommended_margin>35.00</recommended_margin>
<promo_price/>
<promo_starts/>
<promo_ends/>
<cptstock>0</cptstock>
<jhbstock>0</jhbstock>
<dbnstock>0</dbnstock>
<nextshipmenteta>2026-10-02</nextshipmenteta>
<last_modified>2026-08-18 03:25:40</last_modified>
</product>JSON
{
"sku": "DM-12",
"price": 169,
"rrp_incl": 299,
"recommended_margin": 35,
"promo_price": false,
"promo_starts": false,
"promo_ends": false,
"cptstock": 0,
"jhbstock": 0,
"dbnstock": 0,
"nextshipmenteta": "2026-10-02",
"last_modified": "2026-08-18 03:25:40"
}CSV
sku,price,rrp_incl,recommended_margin,promo_price,promo_starts,promo_ends,cptstock,jhbstock,dbnstock,nextshipmenteta,currency
DM-12,169.00,299.00,35.00,N/A,N/A,N/A,0,0,0,2026/10/02,ZAR
CSV specifics to note when parsing: fields that are empty (XML) or false (JSON) are the literal string N/A — don't feed them straight into a number parser; dates use YYYY/MM/DD (slashes, not dashes); currency is a per-row column; and there is no last_modified column.
On promotion — XML example
<product>
<sku>ELA6470GL</sku>
<price>8128.00</price>
<rrp_incl>10999.00</rrp_incl>
<recommended_margin>15.02</recommended_margin>
<promo_price>7999.00</promo_price>
<promo_starts>2026-07-30</promo_starts>
<promo_ends>2026-08-30</promo_ends>
<cptstock>0</cptstock>
<jhbstock>0</jhbstock>
<dbnstock>0</dbnstock>
<nextshipmenteta/>
<last_modified>2026-08-18 13:11:16</last_modified>
</product>Note price still shows the normal 8128.00 even though the promotion is active, and nextshipmenteta is empty when no shipment is scheduled.
Delisted products
End of life or delisted products drop out of the feeds entirely, there is no status flag or tombstone record. To detect removals, diff the feed's SKU set against your catalogue after a sync and deactivate/draft anything no longer present.
Before acting on absences, validate that your download parsed completely by comparing the number of products you parsed against the envelope's count . A truncated or partial file should never trigger mass deactivation on your store.
Format differences
Field names match across formats, with the CSV exceptions noted here and in the table above; the shapes differ:
| XML | JSON | CSV | |
|---|---|---|---|
| Envelope | <data> block, products as <stock><product> elements | count / currency at top level, products array | none — header row + one row per product; currency is a per-row column |
| Numbers (prices, stock, dimensions) | text content | native JSON numbers | text |
| Not on promotion | empty <promo_price/> elements | false (boolean) — test for false, not null | literal string N/A |
| Dates | YYYY-MM-DD | YYYY-MM-DD | YYYY/MM/DD |
| Image fields | featured_image etc., underscores | featured_image etc., underscores | featured image, additional images, all images — spaces in the headers |
attributes | kebab-case elements, CDATA-wrapped — complete | object of kebab-case keys → string values — complete | one column per attribute, fixed legacy set — incomplete (see below) |
description / shortdesc | CDATA-wrapped HTML | HTML string | HTML inside quoted cells — use a proper CSV parser, never split on commas |
categorytree / categorytreealt | present | present | not present |
last_modified | present | present | not present |
Full-feed CSV columns
The full-feed CSV flattens attributes into one column per attribute name — but the column set is fixed and is not extended as new attribute types are added. Newer attributes (for example size-class, surface-type, stitched-edges, model-number, manufacturer-part-number, rgb-lighting) never appear in the CSV, even though they exist on those products in XML and JSON.
If attribute data matters to your integration, use the XML or JSON feed. The CSV will carry legacy attributes.
Within the columns that do exist, any given product only populates the attributes that apply to it. Map columns by header name, never by position. The header row as currently generated:
sku,name,price,rrp_incl,recommended_margin,promo_price,promo_starts,promo_ends,cptstock,jhbstock,dbnstock,nextshipmenteta,url,description,shortdesc,weight,length,width,height,"featured image","additional images","all images",categories,categoriesalt,3g-support,adjustable-pillow,amperage-a,android-version,aspect-ratio,audio-channels,battery-capacity,battery-type,battery-weight,bays,bluetooth,brand,buttons,cable-length-cm,cable-length-m,cable-type,cache,capacity,capacity-mah,channels,charging-port,charging-technology,class,colour,compatibility,connectivity,controller-included,cooler-included,cpu,cpu-cooler-height-mm,cpu-cores,cpu-series,cpu-socket,cpu-speed,curvature,dimm-slots,direction,disk-speed-rpm,dpi,ean,ecc,edging,enclosure,fan-size,fans,form-factor,format,fps,generation,hardness,hardware,has-adapter,hot-swappable,included-controller,input-interface,interface,ir,items,iteration,lighting,lock-type,material,maximum-resolution,memory-bus,memory-capacity,memory-clock-speed,memory-form-factor,memory-generation,memory-speed,microphone,module-type,motherboard-brand,motherboard-support,multiple-displays,noise-cancelling,number-of-fans,number-of-keys,number-of-lithium-ion-cells,output-interface,panel-type,pcie-862,platform,plug,poe,ports,power-delivery,power-delivery-w,power-va,profile,psu-energy-efficient,quickcharge,raincover-resistant,range-m,rank,read-speed,refresh-rate,remote-included,requirement,resolution,screen-size,series,side-panel,size,slots-size,socket,socket-support,software-series,software-version,speed,style,supported-cooler-height,supported-gpu-length-mm,surface,switch-brand,switch-cap,switch-colour,switch-height,switch-quantity,switch-series,thickness,tip,touch,usb-ports,voltage-v,warranty,water-resistant,watt-hours,wattage-w,weight-limit-kg,write-speed,currency
Full-feed JSON example (trimmed)
{
"sku": "DM-12",
"name": "Keychron Desk Mat - Pink",
"price": 169,
"rrp_incl": 299,
"recommended_margin": 35,
"promo_price": false,
"promo_starts": false,
"promo_ends": false,
"cptstock": 0,
"jhbstock": 0,
"dbnstock": 0,
"nextshipmenteta": "2026-10-02",
"url": "https://www.syntech.co.za/product/keychron-desk-mat-pink/",
"additional_images": ["...02.png", "...03.png"],
"categories": "Computer peripherals, Mousepads, Coming Soon",
"categorytree": "Computer peripherals > Mousepads|Coming Soon",
"attributes": {
"colour": "Pink",
"surface-type": "Cloth (Control)",
"brand": "Keychron",
"warranty": "None"
},
"last_modified": "2026-08-18 03:25:40"
}Attributes vary per product
Attribute names are kebab-case, and the set differs by product type — a keyboard exposes different attributes to a power station. Full feed only. Don't hardcode a fixed attribute list; discover them dynamically or map only the ones you need. brand, warranty, model-number and manufacturer-part-number are common across most products.
<attributes>
<colour><![CDATA[ Pink ]]></colour>
<size-class><![CDATA[ XXL / Desk ]]></size-class>
<surface-type><![CDATA[ Cloth (Control) ]]></surface-type>
<brand><![CDATA[ Keychron ]]></brand>
<warranty><![CDATA[ None ]]></warranty>
</attributes>Updated about 8 hours ago
