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
| Resource | Upsert Key | Behaviour |
|---|---|---|
SupplierProduct | (code, supplier_id) | Creates if not found, updates if found |
CompanyProduct | code | Creates 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.
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:
{"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
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:
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
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
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
| Value | Use for |
|---|---|
METERS | Woven/knit fabric, ribbon, tape sold by the linear metre |
KG | Raw materials, yarns, trims sold by weight — default |
PIECES | Cut-and-sew components, finished garments, accessories |
ROLLS | Full 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.