Product Catalog & Inventory Sync

Sync an external product catalog (ERP, spreadsheet, PIM) into ConformaESG using idempotent bulk upsert for supplier and company products.

Most companies already manage product data in an ERP or PIM system. This recipe shows how to sync that data into ConformaESG reliably using the bulk upsert endpoints — with full support for inventory and textile product fields.

How Upsert Works

ResourceUpsert KeyBehaviour
SupplierProduct(code, supplier_id)Creates if not found, updates if found
CompanyProductcodeCreates if not found, updates if found
YarnProduct(code, supplier_id)Creates if not found, updates if found
FiberProduct(code, supplier_id)Creates if not found, updates if found
FabricProduct(code, supplier_id)Creates if not found, updates if found

Because the upsert key is deterministic, running the same sync payload twice is safe — no duplicates are created.

1. Bulk Sync Supplier Products

Push all products for a single supplier in one request. Fields like quantity_available and lead_time_days are updated on every sync, so your inventory levels stay current.

Shell
curl -X POST "http://localhost:8080/api/v1/supplier/products/bulk?userId=1" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"name": "Organic Cotton Poplin 120gsm",
"code": "GR-POP-120-ORG",
"supplier_id": 42,
"unit_of_measure": "METERS",
"product_origin_country": "IN",
"raw_material_origin_country": "IN",
"quantity_available": 4200.0,
"lead_time_days": 35,
"price_per_unit": 2.80,
"currency": "EUR",
"gots_certified": true
},
{
"name": "Recycled Polyester Fleece 280gsm",
"code": "GR-FLC-280-RCY",
"supplier_id": 42,
"unit_of_measure": "METERS",
"product_origin_country": "TW",
"quantity_available": 1800.0,
"lead_time_days": 21,
"price_per_unit": 3.15,
"currency": "EUR",
"grs_certified": true
}
]
}'

Response 200 OK:

JSON
{
"created": 2,
"updated": 0,
"failed": 0,
"errors": []
}

On a second sync run with updated quantity_available values, the response would show "updated": 2.

2. Bulk Sync Company Products

Shell
curl -X POST "http://localhost:8080/api/v1/company/products/bulk?userId=1" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"name": "Organic Crew-Neck Tee",
"code": "TEE-ORG-WH-M",
"unit_of_measure": "PIECES",
"product_origin_country": "PT",
"raw_material_origin_country": "IN",
"recycled_content_percent": 0.0,
"quantity_available": 620.0,
"lead_time_days": 45
},
{
"name": "Recycled Fleece Hoodie",
"code": "HDY-RCY-NV-L",
"unit_of_measure": "PIECES",
"product_origin_country": "TR",
"recycled_content_percent": 85.0,
"quantity_available": 310.0,
"lead_time_days": 30
}
]
}'

3. Bulk Sync Textile Products

For textile-specific products (yarns, fibers, fabrics), use the textile endpoints:

Shell
curl -X POST "http://localhost:8080/api/v1/textile/fabrics/bulk?userId=1" \
-H "Content-Type: application/json" \
-d '{
"mode": "UPSERT",
"records": [
{
"name": "Organic Cotton Twill 280gsm",
"code": "FAB-OCT-280",
"supplier_id": 42,
"width_cm": 150.0,
"areal_mass_gsm": 280.0,
"weave_type": "Twill",
"gots_certified": true,
"unit_of_measure": "METERS",
"quantity_available": 3000.0
}
]
}'

4. Node.js Sync Script

JavaScript
import fs from 'fs';
const BASE_URL = 'http://localhost:8080';
const HEADERS = {
'Content-Type': 'application/json',
};
async function bulkUpsert(endpoint, items) {
const resp = await fetch(`${BASE_URL}${endpoint}`, {
method: 'POST',
headers: HEADERS,
body: JSON.stringify({ items }),
});
if (!resp.ok) {
throw new Error(`${endpoint} failed: ${resp.status} ${await resp.text()}`);
}
const { created, updated, failed, errors } = await resp.json();
console.log(`${endpoint} — created: ${created}, updated: ${updated}, failed: ${failed}`);
if (errors.length > 0) {
console.warn('Partial failures:', errors);
}
}
async function sync() {
const supplierProducts = JSON.parse(fs.readFileSync('./supplier-products.json', 'utf8'));
const companyProducts = JSON.parse(fs.readFileSync('./company-products.json', 'utf8'));
await bulkUpsert('/api/v1/supplier/products/bulk?userId=1', supplierProducts);
await bulkUpsert('/api/v1/company/products/bulk?userId=1', companyProducts);
console.log('Sync complete.');
}
sync().catch(err => { console.error(err); process.exit(1); });

5. Idempotent Re-sync Pattern

Because the upsert key is (code, supplier_id) for supplier products and code for company products, you can re-run the sync at any frequency without side effects:

  • Code exists → record is updated with the latest values from your ERP
  • Code is new → record is created
  • Batch fails partially → failed items are reported in errors[]; successful items are committed independently
Warning

Individual item failures do not roll back the entire batch. If you need all-or-nothing atomicity, split records into individual POST requests and wrap them in client-side error handling.

6. UnitOfMeasure Best Practices

ValueUse for
METERSWoven/knit fabric, ribbon, tape sold by the linear metre
KGRaw materials, yarns, trims sold by weight — default
PIECESCut-and-sew components, finished garments, accessories
ROLLSFull fabric reels, paper, non-woven rolls

Once set on a product, changing unit_of_measure does not automatically convert historical quantity_available or purchase quantity_kg values — update those fields explicitly in the same PATCH request.