API per sviluppatori

Il tuo e-commerce, alimentato da Vemlora

Collega il tuo sito personale al gestionale: catalogo, disponibilità in tempo reale, coupon, checkout e ordini arrivano dalle stesse API che usa il nostro negozio.

Panoramica

Le API storefront espongono al tuo sito tutto quello che serve per vendere online, leggendo i dati direttamente da Vemlora. Non devi duplicare prodotti, prezzi o giacenze: aggiorni il gestionale e il sito si adegua.

Catalogo e ricerca

Prodotti, varianti, filtri a faccette, menu e vetrina delle collezioni.

Spedizioni e coupon

Metodi di spedizione e codici sconto gestiti dal gestionale.

Checkout sicuro

Prenotazione della merce e totali calcolati lato server.

Ordini nel gestionale

Ogni pagamento riuscito diventa un ordine pronto da evadere.

Browser del cliente
Server del tuo sito API Vemlora

Il browser parla solo con il tuo sito. È il server del tuo sito a chiamare Vemlora, con una chiave segreta che il cliente non vede mai.

Come ottenere l'accesso

Le API sono disponibili su richiesta. Attiviamo l'accesso per la tua azienda, colleghiamo il catalogo e ti consegniamo la chiave segreta da usare nelle chiamate.

  1. Scrivici dalla pagina contatti indicando il nome della tua azienda su Vemlora e l'indirizzo del sito.
  2. Ricevi la chiave e salvala in una variabile d'ambiente del server, ad esempio VEMLORA_STOREFRONT_KEY.
  3. Configura il gestionale: metodi di spedizione, codici sconto e catalogo che vuoi pubblicare.
Richiedi l'accesso alle API

Guida rapida

Tutte le chiamate partono dallo stesso indirizzo base e portano la chiave nell'header x-storefront-key.

Indirizzo base
https://app.vemlora.it/api/storefront

1. Prova con curl

bash
curl "https://app.vemlora.it/api/storefront/catalog/products?search=tazza&perPage=12" \
  -H "x-storefront-key: $VEMLORA_STOREFRONT_KEY"

2. Crea un piccolo client sul server

Un'unica funzione aggiunge chiave e intestazioni a ogni chiamata. L'esempio è in TypeScript, ma la logica è la stessa in qualunque linguaggio.

TypeScript
// server/utils/vemlora.ts — gira SOLO sul server del tuo sito
const BASE_URL = "https://app.vemlora.it/api/storefront";

export async function vemlora<T>(path: string, init: RequestInit = {}): Promise<T> {
  const response = await fetch(BASE_URL + path, {
    ...init,
    headers: {
      "content-type": "application/json",
      "x-storefront-key": process.env.VEMLORA_STOREFRONT_KEY!,
      ...init.headers,
    },
  });

  const body = await response.json().catch(() => null);
  if (!response.ok && response.status !== 409) {
    throw new Error(body?.statusMessage ?? "Errore Vemlora " + response.status);
  }
  return body as T;
}

// Esempio: prima pagina del catalogo, prodotti disponibili
const catalog = await vemlora("/catalog/products?available=true&sort=newest");

3. Esponi i dati al tuo frontend

Il frontend chiama una rotta del tuo sito, che a sua volta chiama Vemlora. Così la chiave non lascia mai il server.

TypeScript
// server/api/shop/products.get.ts (Nuxt / Nitro)
export default defineEventHandler(async (event) => {
  const query = new URLSearchParams(getQuery(event) as Record<string, string>);
  return vemlora("/catalog/products?" + query.toString());
});

Autenticazione

Ogni richiesta deve includere la chiave segreta nell'header x-storefront-key. Senza chiave, o con una chiave sbagliata, la risposta è 401.

La chiave è come una password

Chi la possiede può creare prenotazioni e ordini a nome del tuo negozio. Usala solo lato server, non salvarla nel repository e scrivici subito se pensi che sia stata esposta: la rigeneriamo.

Convenzioni

  • Formato. Richieste e risposte sono JSON in UTF-8. Per le POST invia l'header content-type application/json.
  • Importi. Tutti i prezzi sono interi in centesimi di euro: 1490 significa 14,90 €. Nessun arrotondamento a carico tuo.
  • Date. Le date sono stringhe ISO 8601 in UTC.
  • Paginazione. Le liste paginate restituiscono page, perPage, total e totalPages.
  • Errori. Un errore ha uno statusCode HTTP e un statusMessage in italiano, già adatto da mostrare all'utente.

Spedizioni e coupon

Metodi di spedizione e codici sconto si configurano nel gestionale. Il sito li legge da qui, così una modifica in Vemlora vale subito anche online.

GET/shipping-methods

Metodi di spedizione

I metodi attivi, ordinati dal più economico.

  • I metodi con international = false valgono solo per l'Italia, quelli con international = true per gli altri paesi dell'Unione Europea.
  • Il checkout rifiuta un metodo non valido per il paese di consegna: filtra l'elenco prima di mostrarlo.
Esempio di risposta
{
  "items": [
    {
      "id": "6650f0c2a1b2c3d4e5f60718",
      "name": "Poste Italiane - Raccomandata",
      "priceCents": 590,
      "maxWeight": null,
      "maxValueCents": null,
      "international": false
    }
  ]
}
POST/coupons/validate

Verifica un codice sconto

Controlla un coupon creato in Ordini & Bolle → Codici sconto e calcola lo sconto sul totale merce. Serve per mostrarlo nel carrello: il checkout lo ricontrolla comunque.

Corpo JSON

NomeTipoDescrizione
code obbligatorio stringIl codice inserito dal cliente. Maiuscole e minuscole sono equivalenti.
cartTotalCents obbligatorio integerTotale merce in centesimi, spedizione esclusa.
  • Lo sconto non supera mai il totale merce: un coupon fisso più alto del carrello lo azzera, senza generare credito.
  • Con la spedizione gratuita restano comunque da pagare 50 centesimi, il minimo addebitabile da Stripe.
  • Possibili motivi di rifiuto: Codice mancante, Codice non valido, Codice scaduto, Codice esaurito, Ordine minimo non raggiunto.
Esempio di richiesta
{ "code": "estate10", "cartTotalCents": 5000 }
Esempio di risposta
// Coupon valido
{ "valid": true, "code": "ESTATE10", "discountCents": 500, "freeShipping": false }

// Coupon rifiutato
{ "valid": false, "reason": "Codice scaduto" }

Checkout

Il checkout blocca la merce per 15 minuti mentre il cliente paga. Prezzi, disponibilità, spedizione e sconto li calcola sempre Vemlora: quello che invia il browser non conta.

POST/checkout/reserve

Prenota la merce

Valida i dati del cliente, verifica le disponibilità, applica spedizione e coupon e prenota le quantità. Restituisce i totali da far pagare.

Corpo JSON

NomeTipoDescrizione
lines obbligatorio arrayRighe del carrello: { variantId, quantity }.
shippingMethodId obbligatorio stringId di un metodo di spedizione valido per il paese.
couponCodestringCodice sconto facoltativo.
customer obbligatorio objectname, surname, email e phone. Il telefono accetta da 6 a 15 cifre con + facoltativo.
address obbligatorio objectstreet, city, zip, province e country. Per l'Italia CAP a 5 cifre e provincia di 2 lettere; all'estero niente provincia.
invoiceobjectFacoltativo. kind private richiede taxCode; kind company richiede companyName e vatNumber, con sdiCode o pec facoltativi.
  • Paesi accettati: i 27 stati dell'Unione Europea, indicati con il codice ISO a due lettere.
  • Per un'azienda italiana senza SDI né PEC la fattura va nel cassetto fiscale con codice 0000000.
  • Un 409 non è un errore del tuo codice: la merce è finita o il coupon non vale più. Mostra il motivo al cliente e fagli aggiornare il carrello.
Esempio di richiesta
{
  "lines": [{ "variantId": "274512-na-na-reg", "quantity": 1 }],
  "shippingMethodId": "6650f0c2a1b2c3d4e5f60718",
  "couponCode": "ESTATE10",
  "customer": { "name": "Giulia", "surname": "Rossi", "email": "giulia@example.com", "phone": "+393331234567" },
  "address":  { "street": "Via Roma 1", "city": "Milano", "zip": "20121", "province": "MI", "country": "IT" },
  "invoice":  { "kind": "private", "taxCode": "RSSGLI90A41F205X" }
}
Esempio di risposta
// 200: merce prenotata
{
  "ok": true,
  "reservationId": "0f8e2a6c-4d1b-4c55-9a1e-2b7f3c9d8e10",
  "expiresAt": "2026-10-18T10:15:00.000Z",
  "lines": [{ "variantId": "274512-na-na-reg", "name": "Tazza in gres 350 ml", "quantity": 1, "unitPriceCents": 1490, "…": "…" }],
  "shippingMethod": { "id": "6650f0c2…", "name": "Poste Italiane - Raccomandata", "priceCents": 590 },
  "coupon": { "code": "ESTATE10", "discountCents": 149, "freeShipping": false },
  "couponError": null,
  "totals": { "itemsTotalCents": 1490, "discountCents": 149, "shippingCents": 590, "totalCents": 1931 }
}

// 409: disponibilità cambiata o coupon non più valido
{
  "ok": false,
  "issues": [{ "variantId": "274512-na-na-reg", "name": "Tazza in gres 350 ml", "reason": "insufficient_stock", "requested": 2, "available": 1 }],
  "couponError": "Codice esaurito"
}
POST/checkout/release

Rilascia una prenotazione

Rimette subito in vendita la merce di una prenotazione non pagata. Chiamalo quando il cliente cambia carrello, spedizione o dati dopo aver aperto il pagamento.

Corpo JSON

NomeTipoDescrizione
reservationId obbligatorio stringL'id restituito da /checkout/reserve.
  • Annulla prima il PaymentIntent su Stripe: la merce di un pagamento ancora valido non deve tornare in vendita.
  • Le prenotazioni dimenticate scadono da sole dopo 15 minuti.
Esempio di richiesta
{ "reservationId": "0f8e2a6c-4d1b-4c55-9a1e-2b7f3c9d8e10" }
Esempio di risposta
{ "ok": true, "released": true }

Ordini

Quando il pagamento va a buon fine, il sito chiede a Vemlora di creare l'ordine. L'ordine compare subito nel gestionale, pronto per DDT, fattura e spedizione.

POST/orders

Crea l'ordine

Crea l'ordine dalla prenotazione salvata al checkout. Righe, prezzi e dati del cliente vengono dalla prenotazione, non dalla richiesta: un browser manomesso non può cambiarli.

Corpo JSON

NomeTipoDescrizione
paymentIntentId obbligatorio stringId del PaymentIntent Stripe pagato.
reservationId obbligatorio stringLa prenotazione salvata nei metadati del pagamento.
amountReceivedCentsintegerImporto incassato secondo Stripe. Se diverso dal totale, l'ordine viene segnalato all'operatore.
claimConfirmationEmailbooleantrue se vuoi occuparti tu dell'email di conferma. Vedi sendConfirmationEmail nella risposta.
  • È idempotente sul paymentIntentId: pagina di ritorno e webhook possono chiamarlo entrambi, l'ordine resta uno. La seconda chiamata riceve existing = true.
  • Una prenotazione appartiene a un solo pagamento: un PaymentIntent diverso riceve 409.
  • Se la prenotazione era già scaduta l'ordine viene creato lo stesso, perché il cliente ha pagato, e stockConflict = true lo segnala in gestionale.
  • orderNumber segue la numerazione configurata in Gestione contatori per i documenti degli ordini web.
Esempio di richiesta
{
  "paymentIntentId": "pi_3Q…",
  "reservationId": "0f8e2a6c-4d1b-4c55-9a1e-2b7f3c9d8e10",
  "amountReceivedCents": 1931,
  "claimConfirmationEmail": true
}
Esempio di risposta
{
  "ok": true,
  "existing": false,
  "orderId": "6712ab…",
  "orderNumber": "2026/0123",
  "stockConflict": false,
  "sendConfirmationEmail": true,
  "order": {
    "createdAt": "2026-10-18T10:00:00.000Z",
    "customer": { "…": "…" },
    "address": { "…": "…" },
    "invoice": null,
    "shippingMethod": { "…": "…" },
    "lines": [ "…" ],
    "coupon": { "code": "ESTATE10", "discountCents": 149, "freeShipping": false },
    "totals": { "itemsTotalCents": 1490, "discountCents": 149, "shippingCents": 590, "totalCents": 1931 }
  }
}

Flusso di acquisto completo

Ecco come si incastrano le chiamate, dalla vetrina all'ordine in gestionale. Il pagamento passa dal tuo account Stripe.

  1. 1

    Mostra il catalogo

    Menu, vetrina e ricerca leggono catalogo e collezioni. Puoi metterli in cache per qualche minuto.

  2. 2

    Componi il carrello

    Salva variantId e quantità. Per i suggerimenti usa i consigliati, per lo sconto la verifica coupon.

  3. 3

    Prenota la merce

    Al momento del pagamento chiama /checkout/reserve: blocca la merce per 15 minuti e restituisce il totale.

  4. 4

    Incassa con Stripe

    Crea il PaymentIntent sul tuo account Stripe con totals.totalCents e reservationId nei metadati.

  5. 5

    Crea l'ordine

    A pagamento riuscito chiama /orders dalla pagina di ritorno e dal webhook. L'ordine arriva in Vemlora.

Il checkout in codice

TypeScript
// 1. Blocca la merce e ottieni i totali calcolati da Vemlora
const reservation = await vemlora("/checkout/reserve", {
  method: "POST",
  body: JSON.stringify({ lines, shippingMethodId, couponCode, customer, address, invoice }),
});
if (!reservation.ok) return showIssues(reservation.issues, reservation.couponError);

// 2. Crea il pagamento sul TUO account Stripe con il totale restituito
const paymentIntent = await stripe.paymentIntents.create({
  amount: reservation.totals.totalCents,
  currency: "eur",
  metadata: { reservationId: reservation.reservationId },
});

// 3. A pagamento riuscito (pagina di ritorno E webhook "payment_intent.succeeded")
const result = await vemlora("/orders", {
  method: "POST",
  body: JSON.stringify({
    paymentIntentId: paymentIntent.id,
    reservationId: paymentIntent.metadata.reservationId,
    amountReceivedCents: paymentIntent.amount_received,
    claimConfirmationEmail: true,
  }),
});

// 4. Solo una delle due chiamate riceve sendConfirmationEmail = true
if (result.sendConfirmationEmail) await sendOrderEmail(result.order);

Se il cliente cambia idea

Se il cliente modifica carrello, spedizione o indirizzo dopo aver aperto il pagamento, annulla il PaymentIntent e rilascia la prenotazione. Poi ripeti la prenotazione con i nuovi dati.

TypeScript
// Il cliente torna al carrello dopo aver aperto il pagamento:
await stripe.paymentIntents.cancel(paymentIntentId); // prima Stripe…
await vemlora("/checkout/release", {                 // …poi Vemlora
  method: "POST",
  body: JSON.stringify({ reservationId }),
});

Errori

Gli errori usano i normali codici HTTP. Il corpo contiene sempre un messaggio leggibile.

Esempio
{
  "statusCode": 400,
  "statusMessage": "Campo non valido: CAP"
}
CodiceSignificatoCosa fare
400Richiesta non validaUn campo manca o non rispetta il formato. statusMessage indica quale.
401Chiave non validaControlla l'header x-storefront-key e che la chiave non sia stata rigenerata.
404Risorsa non trovataProdotto inesistente, oppure API non ancora attivate per la tua azienda.
409ConflittoMerce esaurita, coupon non più valido o prenotazione già usata. Mostra il motivo al cliente.
503Catalogo non prontoIl catalogo non è ancora stato pubblicato. Riprova dopo la prima sincronizzazione.

Buone pratiche

La chiave resta sul server

Chiama le API solo dal backend del tuo sito e conserva la chiave in una variabile d'ambiente. Mai nel codice del browser o in un'app mobile.

Metti in cache il catalogo

Menu e collezioni cambiano solo alla sincronizzazione: una cache di qualche minuto rende il sito più veloce. Non mettere in cache checkout e ordini.

Conferma l'ordine due volte

Chiama /orders sia dalla pagina di ritorno sia dal webhook Stripe. Grazie all'idempotenza non avrai doppioni, e nessun pagamento resterà senza ordine.

Fidati dei totali di Vemlora

Fai pagare sempre totals.totalCents restituito dal checkout, mai un importo calcolato nel browser.

Domande frequenti

Serve un sito fatto in un linguaggio particolare?

No. Le API parlano JSON su HTTPS: funzionano con Nuxt, Next.js, Laravel, WordPress con un plugin custom o qualunque backend capace di fare una richiesta HTTP.

Chi incassa i pagamenti?

Tu, con il tuo account Stripe. Vemlora calcola i totali, blocca la merce e registra l'ordine, ma non tocca mai i soldi.

Gli ordini del sito finiscono nel gestionale?

Sì, appena il pagamento è confermato. Li trovi in Ordini & Bolle con tutti i dati per DDT, fattura e spedizione, e il magazzino si aggiorna da solo.

Posso usare i coupon che creo in Vemlora?

Sì. I codici creati in Ordini & Bolle → Codici sconto valgono subito sul sito, con limiti di utilizzo, ordine minimo e scadenza.

Cosa succede se un prodotto si esaurisce durante il checkout?

La prenotazione risponde 409 con l'elenco delle righe non disponibili e le quantità rimaste. Il cliente aggiorna il carrello e riprova, senza pagare nulla.

Pronto a collegare il tuo sito?

Raccontaci il tuo progetto: attiviamo le API per la tua azienda e ti aiutiamo nei primi passi.