Guida Integrazione Skuno per Sviluppatori E-commerce

Guida pratica per integrare il sistema di arricchimento prodotti Skuno nel tuo e-commerce.

🚀 Setup Rapido

1) Credenziali

Richiedi le credenziali di accesso:

  • API Key: per autenticazione
  • Tenant ID: identificativo del tuo account
  • Base URL: endpoint del servizio

2) Autenticazione

Tutte le richieste richiedono header di autenticazione.

X-API-Key: YOUR_API_KEY
X-Tenant-ID: YOUR_TENANT_ID  # Opzionale con API Key

Nota: X-Tenant-ID è opzionale quando si usa l'autenticazione tramite API Key. Se non fornito, verrà usato automaticamente il tenant associato alla tua API Key.


📦 Inserimento Prodotti

Campi di Controllo Schema

Nello schema_override puoi utilizzare questi campi di controllo:

  • language (obbligatorio): Lingua per l'arricchimento (it, en, fr, de, es)
  • require_images (opzionale): true per richiedere ricerca automatica immagini
  • force_enrichment (opzionale): true per forzare l'arricchimento anche con barcode non validi

Prodotto Singolo

const response = await fetch(
  "https://your-project-id.supabase.co/functions/v1/enrich-products",
  {
    method: "POST",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "X-Tenant-ID": "YOUR_TENANT_ID",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      products: [{
        barcode: "8001234567890",
        callback_url: "https://your-domain.com/webhook/skuno",
        schema_override: {
          type: "object",
          properties: {
            language: "it",
            require_images: false,
            force_enrichment: false,
            nome: { type: "string", description: "Nome completo del prodotto" },
            marca: { type: "string", description: "Marca del prodotto" },
            categoria: { type: "string", description: "Categoria del prodotto" },
            materiale: { type: "string", description: "Materiale principale" },
            colore: { type: "string", description: "Colore del prodotto" }
          },
          required: ["nome", "marca", "categoria"],
        },
        fields_to_create: ["nome", "marca", "categoria", "materiale", "colore"],
        fields_to_enhance: ["descrizione", "prezzo"],
        existing_data: { descrizione: "Scarpe da running", prezzo: "120.00 EUR" },
      }],
    }),
  },
);

const result = await response.json();
console.log("Prodotti in coda:", result.queued_products);

Batch di Prodotti

Per inserire fino a 100 prodotti contemporaneamente, usa lo stesso endpoint enrich-products con un array:

const batchResponse = await fetch(
  "https://your-project-id.supabase.co/functions/v1/enrich-products",
  {
    method: "POST",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "X-Tenant-ID": "YOUR_TENANT_ID",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      products: [
        {
          barcode: "3017620422003",
          callback_url: "https://your-domain.com/webhook/skuno",
          schema_override: {
            type: "object",
            properties: {
              language: "it",
              require_images: true,
              force_enrichment: false,
              nome: { type: "string", description: "Nome completo del prodotto di make-up" },
              marca: { type: "string", description: "Marca del prodotto" },
              categoria: {
                type: "string",
                enum: ["fondotinta", "rossetto", "mascara", "ombretto", "blush", "correttore"],
                description: "Categoria specifica del prodotto make-up",
              },
              tonalita: { type: "string", description: "Tonalità o colore del prodotto" },
              formato: { type: "string", description: "Formato o dimensione del prodotto (es. 30ml, 15g)" },
            },
            required: ["language", "nome", "marca", "categoria"],
          },
          fields_to_create: ["nome", "marca", "categoria", "tonalita", "formato"],
          fields_to_enhance: ["descrizione", "prezzo"],
          existing_data: { descrizione: "Prodotto di make-up", prezzo: "Da verificare" },
        },
        {
          barcode: "8001234567891",
          callback_url: "https://your-domain.com/webhook/skuno",
          schema_override: {
            type: "object",
            properties: {
              language: "it",
              nome: { type: "string", description: "Nome del prodotto alimentare" },
              marca: { type: "string", description: "Marca del prodotto" },
              categoria: { type: "string", description: "Categoria alimentare" },
              ingredienti: { type: "array", items: { type: "string" }, description: "Lista ingredienti" },
            },
            required: ["language", "nome", "marca"],
          },
          fields_to_create: ["nome", "marca", "categoria", "ingredienti"],
          fields_to_enhance: ["descrizione"],
          existing_data: { descrizione: "Prodotto alimentare" },
        },
      ],
    }),
  },
);

const batchResult = await batchResponse.json();
console.log("Prodotti in coda:", batchResult.queued_products);

📡 Webhook e Callback

Configurazione Webhook

Per ricevere notifiche quando i prodotti sono stati arricchiti:

app.post("/webhook/your-ecommerce-endpoint", (req, res) => {
  const { event, product_id, enriched_data, batch_id } = req.body;

  switch (event) {
    case "product.enriched":
      console.log(`Prodotto ${product_id} arricchito:`, enriched_data);
      updateProductInDatabase(product_id, enriched_data);
      break;

    case "batch.completed":
      console.log(`Batch ${batch_id} completato`);
      break;
  }

  res.status(200).send("OK");
});

Verifica Firma Webhook

const crypto = require("crypto");

function verifyWebhookSignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac("sha256", secret)
    .update(payload)
    .digest("hex");
  return signature === expectedSignature;
}

app.post("/webhook/skuno", (req, res) => {
  const signature = req.headers["x-webhook-signature"]; 
  const payload = JSON.stringify(req.body);

  if (!verifyWebhookSignature(payload, signature, YOUR_WEBHOOK_SECRET)) {
    return res.status(401).send("Unauthorized");
  }
});

📊 Monitoraggio

Stato Prodotti

const statusResponse = await fetch(
  `https://your-project-id.supabase.co/functions/v1/product-status?product_id=${productId}`,
  {
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "X-Tenant-ID": "YOUR_TENANT_ID",
    },
  },
);
const status = await statusResponse.json();
console.log("Stato arricchimento:", status.enrichment_status);

Dashboard Statistiche

const dashboardResponse = await fetch(
  "https://your-project-id.supabase.co/functions/v1/dashboard",
  {
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "X-Tenant-ID": "YOUR_TENANT_ID",
    },
  },
);
const stats = await dashboardResponse.json();
console.log("Prodotti totali:", stats.total_products);
console.log("Arricchimenti completati:", stats.enriched_products);

⚠️ Gestione Errori

Codici di Risposta

  • 200: Successo
  • 202: Richiesta accettata (processamento asincrono)
  • 400: Dati non validi
  • 401: Non autorizzato
  • 404: Risorsa non trovata
  • 429: Troppi tentativi
  • 500: Errore del server

🔧 Configurazione Avanzata

Campi Personalizzati

Puoi specificare quali campi creare e arricchire:

{
  "barcode": "PROD123",
  "schema_override": {
    "type": "object",
    "properties": {
      "nome": { "type": "string", "description": "Nome del prodotto" },
      "materiale": { "type": "string", "description": "Materiale principale" },
      "colore": { "type": "string", "description": "Colore del prodotto" },
      "guida_taglie": { "type": "string", "description": "Guida alle taglie" }
    },
    "required": ["nome"]
  },
  "fields_to_create": ["nome", "materiale", "colore", "guida_taglie"],
  "fields_to_enhance": ["descrizione"],
  "existing_data": { "descrizione": "Scarpe da running" }
}

Rate Limiting

  • Prodotti singoli: 100 richieste/minuto
  • Batch: 10 richieste/minuto
  • Sincronizzazione: 5 richieste/minuto

⚙️ Campi di Controllo

Callback URL

Il campo callback_url specifica l'endpoint dove ricevere le notifiche di completamento:

{
  "barcode": "8001234567890",
  "callback_url": "https://your-domain.com/webhook/skuno",
  "schema_override": { /* ... */ }
}

Campi di Controllo Schema

All'interno dello schema_override, puoi utilizzare questi campi di controllo:

  • language: Lingua per l'arricchimento (es. "it", "en", "fr")
  • require_images: true per richiedere anche l'arricchimento delle immagini
  • force_enrichment: true per forzare l'arricchimento anche se già presente

🖼️ Gestione Immagini

Richiesta Immagini

Per richiedere anche l'arricchimento delle immagini, aggiungi require_images: true nello schema:

const response = await fetch(
  "https://your-project-id.supabase.co/functions/v1/enrich-products",
  {
    method: "POST",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "X-Tenant-ID": "YOUR_TENANT_ID",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      products: [{
        barcode: "8001234567890",
        schema_override: {
          type: "object",
          properties: {
            language: "it",
            require_images: true,
            nome: { type: "string", description: "Nome completo del prodotto" },
            immagini: {
              type: "array",
              items: {
                type: "object",
                properties: {
                  url: { type: "string" },
                  alt_text: { type: "string" },
                  tipo: { type: "string", enum: ["principale", "dettaglio", "lifestyle"] }
                }
              },
              description: "Array di immagini del prodotto",
            }
          },
          required: ["nome"],
        },
        fields_to_create: ["nome", "immagini"],
      }],
    }),
  },
);

Webhook con Immagini

Quando le immagini sono pronte, riceverai una notifica separata:

app.post("/webhook/your-ecommerce-endpoint", (req, res) => {
  const { event, product_id, enriched_data } = req.body;

  switch (event) {
    case "product.enriched":
      console.log(`Prodotto ${product_id} arricchito:`, enriched_data);
      break;

    case "images.ready":
      console.log(`Immagini per ${product_id} pronte:`, enriched_data.immagini);
      // Scarica e salva le immagini nel tuo sistema
      downloadAndSaveImages(product_id, enriched_data.immagini);
      break;
  }

  res.status(200).send("OK");
});

Download Immagini

async function downloadAndSaveImages(productId, images) {
  for (const image of images) {
    try {
      const response = await fetch(image.url);
      const buffer = await response.arrayBuffer();
      const filename = `${productId}_${image.tipo}_${Date.now()}.jpg`;
      await saveImageToStorage(filename, Buffer.from(buffer));
      await updateProductImage(productId, image.tipo, filename);
    } catch (error) {
      console.error(`Errore download immagine ${image.url}:`, error);
    }
  }
}