Salta ai contenuti

Integration API v1

API push inbound: il vostro sistema invia catalogo e prezzi a PriceTag via HTTP. PriceTag non scarica dati dal gestionale e non scrive verso di esso in v1.

Pensata per team tecnici di ERP, POS, middleware e system integrator. Contratto machine-readable: OpenAPI 3 · Versione 1.0 · Aggiornata 2026-09-23.

PriceTag è il layer di presentazione a scaffale (ESL + app negozio). Il gestionale / POS resta, di norma, la source of truth commerciale; PriceTag tiene lo snapshot operativo per mostrare e pubblicare i prezzi.

Fa

  • Upsert catalogo e aggiornamenti prezzo per EAN
  • Auth con token di integrazione legato a un negozio
  • Esito parziale per batch (accepted / rejected[])
  • Staging prezzi in review in-app (default)

Non fa (v1)

  • Sync bidirezionale o webhook verso l’ERP
  • Esporre lo snapshot completo o le ops interne
  • Connettori vendor-specific (SAP, Danea, Softcare, …)
  • Auto-publish hardware / Base Station nello stesso request

Un solo contratto HTTP: chi spinge (ERP, middleware, script, Google Apps Script) è irrilevante.

  1. Account PriceTag con accesso al negozio target.
  2. storeId — UUID del negozio (in app o fornito in onboarding).
  3. Token di integrazione — in app: Impostazioni → Connessioni → Create Token.
    • Il plaintext compare solo alla creazione o rotazione: salvatelo subito in un secret store.
    • La revoca invalida subito il token (kill switch).
  4. Capacità di fare HTTPS POST / GET con header Authorization e body JSON.
Ambiente Base URL
Production https://webapp-pricetag-production.up.railway.app/api

Prefisso risorse:

{BASE}/v1/integration/stores/{storeId}

Esempio:

https://webapp-pricetag-production.up.railway.app/api/v1/integration/stores/19c75782-90ed-4821-85d7-50018ec0beb9/health

Nei POST usate Content-Type: application/json.

Usate uno di questi header:

Authorization: Bearer <integration_token>
X-PriceTag-Key: <integration_token>
Situazione Risposta
Scope Solo lo storeId legato al token
JWT utente app Non accettato
Token di un altro store 403
Token assente, invalido o revocato 401

Il flag UI “enabled” sul token non blocca le chiamate: il controllo operativo è la revoca.

Tutti i prodotti partner sono indirizzati per EAN (stringa, trim). Gli ID interni PriceTag non fanno parte del contratto.

Campo Significato
ean Codice a barre / EAN
name Nome prodotto (catalog)
price Prezzo
department Reparto (opzionale, catalog)
sku Codice interno / SKU (opzionale)

brand non è richiesto (legacy accettato se inviato). Non inviare nome / prezzo (chiavi italiane): il body viene rifiutato.

Direzione Formato
Ingresso (POST) Decimale con punto, stringa o number — es. "3.49" o 3.49
Uscita (GET prodotto) Display italiano — es. "3,49 €"

PriceTag applica le stesse regole di normalizzazione dell’import prezzi in-app.

I POST batch rispondono di solito HTTP 200 anche se alcune righe falliscono. Controllate sempre:

  • accepted / created / updated
  • rejected[] con { ean, code, message }

Body non valido → 400. Operazioni non applicabili allo snapshot → 422.

Header opzionale:

Idempotency-Key: <stringa ≤ 128 caratteri>
Caso Comportamento
Stessa chiave + stesso body Stessa risposta memorizzata (TTL 24 h); nessuna doppia applicazione
Stessa chiave + body diverso 409 (idempotency_mismatch)
Chiave assente Ogni request è indipendente

Usate l’ID del job / batch del vostro middleware.

Di default i cambi prezzo non aggiornano subito lo scaffale: entrano in review nell’app. L’operatore deve Conferma o Scarta (il modal non si chiude con click fuori / Escape).

  1. Il partner invia un POST di catalogo o prezzi.
  2. L’API risponde 200 con status: "pending_review" e reviewId (se c’è un diff di prezzo).
  3. In app: modal Nuovi prezziConferma o Scarta.
  4. Dopo Conferma: Invia alla coda per pubblicare sulle ESL (se assegnate).

Usata su POST .../prices.

Valore Effetto
require_review Default. Staging pending; nessun cambio shelf fino a Conferma. Risposta: status: "pending_review", reviewId.
apply_immediate Opt-out: last-write sullo snapshot senza modal.

Su POST .../catalog:

Situazione Effetto
EAN nuovo Upsert con price applicato subito
EAN esistente, price uguale allo shelf Aggiorna anagrafica (name, sku, department, …)
EAN esistente, price diverso Anagrafica aggiornata; prezzo shelf invariato; staging review + pending_review

Rilevante su POST .../prices solo con apply_immediate (queuePolicy).

Valore Effetto
snapshot_only Default. Solo snapshot (nessuna coda ESL automatica)
enqueue_if_esl Encola se il prodotto ha un’etichetta ESL assegnata
enqueue_always Encola sempre

Con require_review, dopo Conferma l’operatore usa Invia alla coda in app (come una modifica manuale). Non c’è auto-publish hardware nel request di integrazione.

Path assoluto = Base URL + path sotto.

Metodo Path Descrizione
GET /v1/integration/stores/{storeId}/health Token valido + negozio raggiungibile
GET /v1/integration/stores/{storeId}/products/{ean} Lettura prodotto per EAN
POST /v1/integration/stores/{storeId}/catalog Upsert catalogo (batch)
POST /v1/integration/stores/{storeId}/prices Aggiornamento prezzi (batch)

Verifica credenziali e storeId.

Risposta 200:

{
"ok": true,
"storeId": "19c75782-90ed-4821-85d7-50018ec0beb9",
"enabled": true
}

enabled riflette lo stato UI; non blocca le mutazioni.

Lettura puntuale (debug / riconciliazione).

Risposta 200:

{
"ean": "8051277182755",
"name": "POMODORI SECCHI",
"price": "3,49 €",
"department": "Ortofrutta",
"sku": "ART-9912"
}

404 se l’EAN non esiste nel negozio.

Upsert per EAN. In v1 l’unico mode supportato è "upsert".

Request:

{
"items": [
{
"ean": "8051277182755",
"name": "POMODORI SECCHI",
"price": "3.49",
"department": "ORTOFRUTTA",
"sku": "ART-9912"
}
],
"mode": "upsert"
}
Campo Obbligatorio Note
ean Non vuoto
name Non vuoto dopo trim
price Vedi formato prezzo
department no Label reparto
sku no Mappato a codice interno

Header consigliato: Idempotency-Key.

Risposta 200: vedi Risposta batch. Con diff di prezzo su EAN esistenti: status: "pending_review" + reviewId.

Aggiorna solo i prezzi di prodotti già presenti (per EAN).

Request:

{
"items": [
{ "ean": "8051277182755", "price": "3.29" }
],
"queuePolicy": "snapshot_only",
"reviewPolicy": "require_review"
}
Campo Default Note
items[].ean Obbligatorio
items[].price Obbligatorio
queuePolicy snapshot_only Soprattutto con apply_immediate
reviewPolicy require_review Staging in-app

EAN sconosciuto → riga in rejected (product_not_found); non fallisce l’intero batch.

Schema comune ai POST catalog e prices:

{
"accepted": 1,
"created": 0,
"updated": 1,
"status": "pending_review",
"reviewId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"rejected": [
{
"ean": "9999999999999",
"code": "product_not_found",
"message": "Prodotto non trovato per EAN"
}
]
}
Campo Significato
accepted Righe accettate / mergeate
created Nuovi prodotti (tipicamente catalog)
updated Prodotti aggiornati
status pending_review o applied
reviewId UUID della review (se pending_review)
rejected[] Righe non applicate, con codice macchina

PriceTag non restituisce lo snapshot completo nella risposta.

HTTP Quando
200 Batch elaborato (anche con rejected non vuoto)
400 Body non valido / schema
401 Token assente o invalido
403 Token di un altro store
404 Store o prodotto (GET) non trovato
409 Idempotency-Key riusata con body diverso
422 Ops non applicabili allo stato corrente
429 Rate limit superato

Codici tipici in rejected[].code:

Code Contesto
invalid_ean EAN mancante / vuoto
invalid_name name mancante (catalog)
invalid_price price non parseabile
product_not_found EAN assente nello store (/prices)
unchanged_price Prezzo già uguale allo shelf (/prices + review)
skipped Righe saltate dal merge catalog
Limite Valore
Items per POST (catalog / prices) max 1.000
Rate limit 60 richieste / minuto per token
Prodotti totali per negozio max 5.000
Idempotency-Key max 128 caratteri; TTL risposta 24 h

Oltre i limiti: 400 (batch troppo grande) o 429 (rate).

Sostituite STORE_ID e TOKEN.

Finestra del terminale
curl -sS \
-H "Authorization: Bearer $TOKEN" \
"https://webapp-pricetag-production.up.railway.app/api/v1/integration/stores/$STORE_ID/health"
Finestra del terminale
curl -sS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: catalog-job-20260923-001" \
-d '{
"items": [
{
"ean": "8051277182755",
"name": "POMODORI SECCHI",
"price": "3.49",
"department": "ORTOFRUTTA",
"sku": "ART-9912"
}
],
"mode": "upsert"
}' \
"https://webapp-pricetag-production.up.railway.app/api/v1/integration/stores/$STORE_ID/catalog"
Finestra del terminale
curl -sS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: prices-job-20260923-001" \
-d '{
"items": [
{ "ean": "8051277182755", "price": "3.29" }
],
"queuePolicy": "snapshot_only",
"reviewPolicy": "require_review"
}' \
"https://webapp-pricetag-production.up.railway.app/api/v1/integration/stores/$STORE_ID/prices"

Risposta tipica: "status": "pending_review", "reviewId": "...". In app compare il modal Nuovi prezzi.

Finestra del terminale
curl -sS \
-H "Authorization: Bearer $TOKEN" \
"https://webapp-pricetag-production.up.railway.app/api/v1/integration/stores/$STORE_ID/products/8051277182755"
  1. Ottenere storeId e creare il token in Connessioni.
  2. Salvare il token in un vault; non committarlo.
  3. GET .../healthok: true.
  4. Push di pochi EAN di test (catalog o prices) con Idempotency-Key.
  5. Verificare accepted / rejected e, se prezzi cambiati, pending_review.
  6. In app: modal Nuovi prezziConferma o Scarta.
  7. Dopo Conferma: Invia alla coda (se presenti ESL) per pubblicare a scaffale.
  8. In automazione: batch ≤ 1000, max 60 req/min, ritentare solo con la stessa Idempotency-Key a parità di body.
  • CRUD etichette ESL, piano negozio, reparti come risorsa dedicata
  • Publish / ACK Base Station o BLE nello stesso request
  • replace_all del catalogo
  • Webhook outbound PriceTag → ERP
  • Scrittura prezzi verso il gestionale
  • API billing / multi-token con permission granulari (roadmap)
  • Varianti di contratto per vendor ERP
  • Contratto OpenAPI: /integration-api-v1.openapi.yaml
  • Onboarding / token di prova: contattare PriceTag (storeId + guida Connessioni)
  • Incidenti produzione: indicare storeId, timestamp UTC, Idempotency-Key e body (senza token in chiaro)