Ir al contenido

Integration API v1

API push inbound: tu sistema envía catálogo y precios a PriceTag vía HTTP. PriceTag no descarga datos del ERP y no escribe hacia él en v1.

Pensada para equipos técnicos de ERP, POS, middleware y system integrator. Contrato machine-readable: OpenAPI 3 · Versión 1.0 · Actualizada 2026-09-23.

PriceTag es la capa de presentación en góndola (ESL + app de tienda). El ERP / POS suele seguir siendo la source of truth comercial; PriceTag mantiene el snapshot operativo para mostrar y publicar los precios.

Hace

  • Upsert de catálogo y actualizaciones de precio por EAN
  • Auth con token de integración ligado a una tienda
  • Resultado parcial por batch (accepted / rejected[])
  • Staging de precios en review in-app (por defecto)

No hace (v1)

  • Sync bidireccional o webhook hacia el ERP
  • Exponer el snapshot completo o las ops internas
  • Conectores vendor-specific (SAP, Danea, Softcare, …)
  • Auto-publish de hardware / Base Station en el mismo request

Un solo contrato HTTP: quién empuja (ERP, middleware, script, Google Apps Script) es irrelevante.

  1. Cuenta PriceTag con acceso a la tienda target.
  2. storeId — UUID de la tienda (en la app o facilitado en el onboarding).
  3. Token de integración — en la app: Ajustes → Conexiones → Create Token.
    • El plaintext aparece solo al crearlo o rotarlo: guárdalo de inmediato en un secret store.
    • La revocación invalida el token al instante (kill switch).
  4. Capacidad de hacer HTTPS POST / GET con header Authorization y body JSON.
Entorno Base URL
Production https://webapp-pricetag-production.up.railway.app/api

Prefijo de recursos:

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

Ejemplo:

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

En los POST usad Content-Type: application/json.

Usad uno de estos headers:

Authorization: Bearer <integration_token>
X-PriceTag-Key: <integration_token>
Situación Respuesta
Scope Solo el storeId ligado al token
JWT de usuario de la app No aceptado
Token de otro store 403
Token ausente, inválido o revocado 401

El flag UI “enabled” del token no bloquea las llamadas: el control operativo es la revocación.

Todos los productos partner se direccionan por EAN (string, trim). Los ID internos de PriceTag no forman parte del contrato.

Campo Significado
ean Código de barras / EAN
name Nombre del producto (catalog)
price Precio
department Departamento (opcional, catalog)
sku Código interno / SKU (opcional)

brand no es obligatorio (legacy aceptado si se envía). No enviéis nome / prezzo (claves italianas): el body se rechaza.

Dirección Formato
Entrada (POST) Decimal con punto, string o number — p. ej. "3.49" o 3.49
Salida (GET producto) Display italiano — p. ej. "3,49 €"

PriceTag aplica las mismas reglas de normalización del import de precios in-app.

Los POST batch suelen responder HTTP 200 aunque fallen algunas filas. Comprobad siempre:

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

Body no válido → 400. Operaciones no aplicables al snapshot → 422.

Header opcional:

Idempotency-Key: <string ≤ 128 caracteres>
Caso Comportamiento
Misma clave + mismo body Misma respuesta memorizada (TTL 24 h); sin doble aplicación
Misma clave + body distinto 409 (idempotency_mismatch)
Clave ausente Cada request es independiente

Usad el ID del job / batch de vuestro middleware.

Por defecto los cambios de precio no actualizan de inmediato la góndola: entran en review en la app. El operador debe Confirmar o Descartar (el modal no se cierra con clic fuera / Escape).

  1. El partner envía un POST de catálogo o precios.
  2. La API responde 200 con status: "pending_review" y reviewId (si hay diff de precio).
  3. En la app: modal Nuevos preciosConfirmar o Descartar.
  4. Tras Confirmar: Enviar a la cola para publicar en las ESL (si están asignadas).

Usada en POST .../prices.

Valor Efecto
require_review Default. Staging pending; sin cambio en shelf hasta Confirmar. Respuesta: status: "pending_review", reviewId.
apply_immediate Opt-out: last-write en el snapshot sin modal.

En POST .../catalog:

Situación Efecto
EAN nuevo Upsert con price aplicado de inmediato
EAN existente, price igual al shelf Actualiza ficha (name, sku, department, …)
EAN existente, price distinto Ficha actualizada; precio shelf sin cambios; staging review + pending_review

Relevante en POST .../prices solo con apply_immediate (queuePolicy).

Valor Efecto
snapshot_only Default. Solo snapshot (sin cola ESL automática)
enqueue_if_esl Encola si el producto tiene una etiqueta ESL asignada
enqueue_always Encola siempre

Con require_review, tras Confirmar el operador usa Enviar a la cola en la app (como una modificación manual). No hay auto-publish de hardware en el request de integración.

Path absoluto = Base URL + path debajo.

Método Path Descripción
GET /v1/integration/stores/{storeId}/health Token válido + tienda alcanzable
GET /v1/integration/stores/{storeId}/products/{ean} Lectura de producto por EAN
POST /v1/integration/stores/{storeId}/catalog Upsert de catálogo (batch)
POST /v1/integration/stores/{storeId}/prices Actualización de precios (batch)

Verifica credenciales y storeId.

Respuesta 200:

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

enabled refleja el estado UI; no bloquea las mutaciones.

Lectura puntual (debug / reconciliación).

Respuesta 200:

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

404 si el EAN no existe en la tienda.

Upsert por EAN. En v1 el único mode soportado es "upsert".

Request:

{
"items": [
{
"ean": "8051277182755",
"name": "POMODORI SECCHI",
"price": "3.49",
"department": "ORTOFRUTTA",
"sku": "ART-9912"
}
],
"mode": "upsert"
}
Campo Obligatorio Notas
ean No vacío
name No vacío tras trim
price Ver formato de precio
department no Label de departamento
sku no Mapeado a código interno

Header recomendado: Idempotency-Key.

Respuesta 200: ver Respuesta batch. Con diff de precio en EAN existentes: status: "pending_review" + reviewId.

Actualiza solo los precios de productos ya presentes (por EAN).

Request:

{
"items": [
{ "ean": "8051277182755", "price": "3.29" }
],
"queuePolicy": "snapshot_only",
"reviewPolicy": "require_review"
}
Campo Default Notas
items[].ean Obligatorio
items[].price Obligatorio
queuePolicy snapshot_only Sobre todo con apply_immediate
reviewPolicy require_review Staging in-app

EAN desconocido → fila en rejected (product_not_found); no falla el batch entero.

Esquema común a los POST catalog y 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 Significado
accepted Filas aceptadas / mergeadas
created Productos nuevos (típicamente catalog)
updated Productos actualizados
status pending_review o applied
reviewId UUID de la review (si pending_review)
rejected[] Filas no aplicadas, con código máquina

PriceTag no devuelve el snapshot completo en la respuesta.

HTTP Cuándo
200 Batch procesado (también con rejected no vacío)
400 Body no válido / schema
401 Token ausente o inválido
403 Token de otro store
404 Store o producto (GET) no encontrado
409 Idempotency-Key reutilizada con body distinto
422 Ops no aplicables al estado actual
429 Rate limit superado

Códigos típicos en rejected[].code:

Code Contexto
invalid_ean EAN ausente / vacío
invalid_name name ausente (catalog)
invalid_price price no parseable
product_not_found EAN ausente en el store (/prices)
unchanged_price Precio ya igual al shelf (/prices + review)
skipped Filas saltadas por el merge catalog
Límite Valor
Items por POST (catalog / prices) máx. 1.000
Rate limit 60 solicitudes / minuto por token
Productos totales por tienda máx. 5.000
Idempotency-Key máx. 128 caracteres; TTL de respuesta 24 h

Más allá de los límites: 400 (batch demasiado grande) o 429 (rate).

Sustituid STORE_ID y TOKEN.

Ventana de terminal
curl -sS \
-H "Authorization: Bearer $TOKEN" \
"https://webapp-pricetag-production.up.railway.app/api/v1/integration/stores/$STORE_ID/health"
Ventana de terminal
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"
Ventana de terminal
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"

Respuesta típica: "status": "pending_review", "reviewId": "...". En la app aparece el modal Nuevos precios.

Ventana de terminal
curl -sS \
-H "Authorization: Bearer $TOKEN" \
"https://webapp-pricetag-production.up.railway.app/api/v1/integration/stores/$STORE_ID/products/8051277182755"
  1. Obtener storeId y crear el token en Conexiones.
  2. Guardar el token en un vault; no lo commitéis.
  3. GET .../healthok: true.
  4. Push de pocos EAN de prueba (catalog o prices) con Idempotency-Key.
  5. Verificar accepted / rejected y, si cambiaron precios, pending_review.
  6. En la app: modal Nuevos preciosConfirmar o Descartar.
  7. Tras Confirmar: Enviar a la cola (si hay ESL) para publicar en góndola.
  8. En automatización: batch ≤ 1000, máx. 60 req/min, reintentar solo con la misma Idempotency-Key a igualdad de body.
  • CRUD de etiquetas ESL, plano de tienda, departamentos como recurso dedicado
  • Publish / ACK de Base Station o BLE en el mismo request
  • replace_all del catálogo
  • Webhook outbound PriceTag → ERP
  • Escritura de precios hacia el ERP
  • API de billing / multi-token con permisos granulares (roadmap)
  • Variantes de contrato por vendor ERP
  • Contrato OpenAPI: /integration-api-v1.openapi.yaml
  • Onboarding / token de prueba: contactar a PriceTag (storeId + guía Conexiones)
  • Incidentes de producción: indicar storeId, timestamp UTC, Idempotency-Key y body (sin token en claro)