Skip to main content

Product Sync Integration Guide

This guide covers how to synchronize products, variants, and inventory from your external e-commerce platform (like Shopify, WooCommerce, etc.) to VesuvioPay using the SDK Product API.

Overview​

The VesuvioPay Product Sync API enables you to:

  • Sync products and variants in bulk
  • Create, update, and delete individual products
  • Update inventory levels in real-time
  • Link existing VesuvioPay products with external platform IDs
  • Manage product options and variants

Authentication​

All Product API endpoints require a Private API Key passed in the X-Api-Key header.

X-Api-Key: your-private-api-key

Product Sync Workflow​

Step 1: Bulk Product Synchronization​

The bulk sync endpoint is the recommended way to synchronize your entire product catalog or perform large updates.

Endpoint: POST /api/v1/sdk/products/sync

Request Example:

{
"products": [
{
"externalId": "shopify_12345",
"title": "Premium T-Shirt",
"description": "High-quality cotton t-shirt",
"vendor": "My Brand",
"productType": "Apparel",
"tags": ["clothing", "tshirt", "cotton"],
"status": "Active",
"images": [
{
"url": "https://example.com/images/tshirt-front.jpg",
"altText": "T-shirt front view",
"position": 1
}
],
"variants": [
{
"externalId": "shopify_variant_001",
"title": "Small / Red",
"sku": "TSHIRT-S-RED",
"barcode": "123456789",
"price": 29.99,
"compareAtPrice": 39.99,
"inventoryQuantity": 100,
"inventoryTracking": true,
"weight": 0.2,
"weightUnit": "kg",
"options": [
{ "name": "Size", "value": "Small" },
{ "name": "Color", "value": "Red" }
]
},
{
"externalId": "shopify_variant_002",
"title": "Medium / Red",
"sku": "TSHIRT-M-RED",
"price": 29.99,
"compareAtPrice": 39.99,
"inventoryQuantity": 150,
"inventoryTracking": true,
"weight": 0.2,
"weightUnit": "kg",
"options": [
{ "name": "Size", "value": "Medium" },
{ "name": "Color", "value": "Red" }
]
}
]
}
],
"deleteNotIncluded": false
}

Response:

{
"success": true,
"message": "Products synchronized successfully",
"data": {
"totalProducts": 1,
"created": 1,
"updated": 0,
"deleted": 0,
"failed": 0,
"results": [
{
"externalId": "shopify_12345",
"productId": "a3f2b1c0-1234-5678-90ab-cdef12345678",
"status": "created",
"variantsCreated": 2,
"variantsUpdated": 0
}
]
}
}

Key Fields:

  • externalId: Your platform's unique product identifier
  • deleteNotIncluded: If true, products not in the sync request will be deleted
  • variants: Array of product variants with their own external IDs

Step 2: Individual Product Operations​

Create a Single Product​

Endpoint: POST /api/v1/sdk/products

{
"externalId": "shopify_54321",
"title": "Classic Jeans",
"description": "Comfortable denim jeans",
"status": "Active",
"variants": [
{
"externalId": "shopify_variant_100",
"title": "32x32",
"sku": "JEANS-32-32",
"price": 79.99,
"inventoryQuantity": 50,
"inventoryTracking": true
}
]
}

Update a Product​

Endpoint: PUT /api/v1/sdk/products/{productId}

You can use either the internal VesuvioPay product ID (UUID) or the external ID:

{
"title": "Classic Denim Jeans - Updated",
"description": "Premium quality denim jeans",
"status": "Active"
}

Examples:

# Using internal ID
PUT /api/v1/sdk/products/a3f2b1c0-1234-5678-90ab-cdef12345678

# Using external ID
PUT /api/v1/sdk/products/shopify_54321

Delete a Product​

Endpoint: DELETE /api/v1/sdk/products/{productId}

DELETE /api/v1/sdk/products/shopify_54321

Get Product Details​

Endpoint: GET /api/v1/sdk/products/{productId}

GET /api/v1/sdk/products/shopify_54321

Step 3: Inventory Management​

Update inventory for a specific product variant in real-time.

Endpoint: PUT /api/v1/sdk/products/variants/{variantId}/inventory

{
"inventoryQuantity": 75,
"inventoryTracking": true
}

Response:

{
"success": true,
"data": {
"variantId": "b4e3c2d1-2345-6789-01bc-def123456789",
"externalVariantId": "shopify_variant_100",
"previousQuantity": 50,
"newQuantity": 75,
"updatedAt": "2025-10-08T10:30:00Z"
}
}

You can use either the internal variant ID or external variant ID in the URL.

Step 4: Linking Existing Products​

If you already have products in VesuvioPay and want to link them to your external platform:

Endpoint: PUT /api/v1/sdk/products/{productId}/link-external

Note: This endpoint requires the internal VesuvioPay product ID (UUID), not the external ID.

{
"externalId": "shopify_99999",
"platformName": "Shopify"
}

Response:

{
"success": true,
"data": {
"productId": "a3f2b1c0-1234-5678-90ab-cdef12345678",
"externalId": "shopify_99999",
"platformName": "Shopify",
"linkedAt": "2025-10-08T10:30:00Z"
}
}

Endpoint: PUT /api/v1/sdk/products/{productId}/variants/{variantId}/link-external

{
"externalVariantId": "shopify_variant_999",
"platformName": "Shopify"
}

Handling Variants and Options​

Understanding Product Options​

Product options (like Size, Color, Material) are automatically created based on the variant options you provide:

{
"variants": [
{
"title": "Small / Red / Cotton",
"options": [
{ "name": "Size", "value": "Small" },
{ "name": "Color", "value": "Red" },
{ "name": "Material", "value": "Cotton" }
]
}
]
}

VesuvioPay will:

  1. Create product options (Size, Color, Material)
  2. Create option values (Small, Red, Cotton)
  3. Associate the variant with these option values

Variant Title Best Practices​

  • If you don't specify a title, it will be auto-generated from option values
  • Keep variant titles concise and descriptive
  • Include key differentiating factors (size, color, etc.)

Best Practices for Bulk Sync​

1. Batch Size​

For optimal performance, sync products in batches:

  • Recommended batch size: 100-500 products per request
  • For large catalogs (10,000+ products), break into multiple requests
// Example: Batch processing
async function syncAllProducts(products) {
const batchSize = 250;

for (let i = 0; i < products.length; i += batchSize) {
const batch = products.slice(i, i + batchSize);

await fetch('https://api.vesuviopay.com/api/v1/sdk/products/sync', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': 'your-private-api-key'
},
body: JSON.stringify({ products: batch })
});

// Optional: Add delay between batches to avoid rate limits
await new Promise(resolve => setTimeout(resolve, 1000));
}
}

2. Error Handling​

Always check the sync response for failed products:

const response = await syncProducts(batch);

if (response.data.failed > 0) {
response.data.results
.filter(r => r.status === 'failed')
.forEach(failed => {
console.error(`Failed to sync product ${failed.externalId}: ${failed.error}`);
});
}

3. Incremental Updates​

For real-time sync, only send changed products:

// Track last sync timestamp
const lastSync = getLastSyncTimestamp();

// Get products modified since last sync
const changedProducts = await getProductsModifiedSince(lastSync);

// Sync only changed products
await syncProducts(changedProducts);

// Update sync timestamp
setLastSyncTimestamp(new Date());

4. Inventory Sync Strategy​

For inventory updates, use the dedicated inventory endpoint instead of full product sync:

// Efficient inventory update
async function updateInventory(variantId, newQuantity) {
await fetch(`https://api.vesuviopay.com/api/v1/sdk/products/variants/${variantId}/inventory`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': 'your-private-api-key'
},
body: JSON.stringify({
inventoryQuantity: newQuantity,
inventoryTracking: true
})
});
}

5. Delete Strategy​

Use deleteNotIncluded: false for safety:

  • Set to true only for full catalog replacements
  • For incremental syncs, explicitly delete products using the DELETE endpoint

Complete Integration Example​

Here's a complete example of integrating product sync with Shopify:

const VESUVIO_API_KEY = process.env.VESUVIO_PRIVATE_API_KEY;
const VESUVIO_BASE_URL = 'https://api.vesuviopay.com/api/v1';

class VesuvioProductSync {
constructor(apiKey) {
this.apiKey = apiKey;
}

async syncProductsFromShopify(shopifyProducts) {
// Transform Shopify products to VesuvioPay format
const vesuvioProducts = shopifyProducts.map(product => ({
externalId: `shopify_${product.id}`,
title: product.title,
description: product.body_html,
vendor: product.vendor,
productType: product.product_type,
tags: product.tags.split(',').map(t => t.trim()),
status: product.status === 'active' ? 'Active' : 'Inactive',
images: product.images.map((img, index) => ({
url: img.src,
altText: img.alt,
position: index + 1
})),
variants: product.variants.map(variant => ({
externalId: `shopify_variant_${variant.id}`,
title: variant.title,
sku: variant.sku,
barcode: variant.barcode,
price: parseFloat(variant.price),
compareAtPrice: variant.compare_at_price ? parseFloat(variant.compare_at_price) : null,
inventoryQuantity: variant.inventory_quantity,
inventoryTracking: variant.inventory_management === 'shopify',
weight: variant.weight,
weightUnit: variant.weight_unit,
options: [
{ name: 'Option1', value: variant.option1 },
{ name: 'Option2', value: variant.option2 },
{ name: 'Option3', value: variant.option3 }
].filter(opt => opt.value)
}))
}));

// Sync in batches
const batchSize = 250;
const results = [];

for (let i = 0; i < vesuvioProducts.length; i += batchSize) {
const batch = vesuvioProducts.slice(i, i + batchSize);

try {
const response = await fetch(`${VESUVIO_BASE_URL}/sdk/products/sync`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': this.apiKey
},
body: JSON.stringify({
products: batch,
deleteNotIncluded: false
})
});

const data = await response.json();
results.push(data);

console.log(`Synced batch ${i / batchSize + 1}: ${data.data.created} created, ${data.data.updated} updated`);

// Rate limiting: wait 1 second between batches
if (i + batchSize < vesuvioProducts.length) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
} catch (error) {
console.error(`Error syncing batch ${i / batchSize + 1}:`, error);
}
}

return results;
}

async updateInventory(externalVariantId, newQuantity) {
try {
const response = await fetch(
`${VESUVIO_BASE_URL}/sdk/products/variants/${externalVariantId}/inventory`,
{
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': this.apiKey
},
body: JSON.stringify({
inventoryQuantity: newQuantity,
inventoryTracking: true
})
}
);

return await response.json();
} catch (error) {
console.error(`Error updating inventory for variant ${externalVariantId}:`, error);
throw error;
}
}

async linkExistingProduct(vesuvioProductId, externalId) {
try {
const response = await fetch(
`${VESUVIO_BASE_URL}/sdk/products/${vesuvioProductId}/link-external`,
{
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': this.apiKey
},
body: JSON.stringify({
externalId: externalId,
platformName: 'Shopify'
})
}
);

return await response.json();
} catch (error) {
console.error(`Error linking product ${vesuvioProductId}:`, error);
throw error;
}
}
}

// Usage
const syncService = new VesuvioProductSync(VESUVIO_API_KEY);

// Full catalog sync
const shopifyProducts = await fetchAllShopifyProducts();
await syncService.syncProductsFromShopify(shopifyProducts);

// Real-time inventory update
await syncService.updateInventory('shopify_variant_12345', 100);

Error Handling​

Common errors and how to handle them:

400 Bad Request​

Invalid product data. Check the response message for specific validation errors.

{
"success": false,
"message": "Validation failed",
"errors": [
"Product title is required",
"Variant price must be greater than 0"
]
}

401 Unauthorized​

Invalid or missing API key.

404 Not Found​

Product or variant not found. The external ID may not exist in VesuvioPay.

409 Conflict​

External ID already exists for a different product.

Next Steps​