Resumen

Última actualización: Aug 31, 2026Sección: Integraciones

Catálogo de mercado --- API externa de lectura

Referencia completa de la API: https://api.mailoo.app/docs/v1

Usa una integración Market para gestionar un catálogo mínimo de productos en el panel de control de Mailoo y exponer JSON de solo lectura a tu tienda o BFF. Cada ruta externa requiere ``X-API-Key``; no hay acceso anónimo.

Para la edición masiva CSV del panel de control (exportar → editar → subir → previsualizar/aplicar), consulta market-catalog-csv{.interpreted-text role="doc"}.

Resumen

  • Autenticación: Cabecera ``X-API-Key`` en cada solicitud.
  • Alcance: Las claves RESTRICTED necesitan ``market.external-read`` en allowedOperations (las claves FULL también funcionan).
  • Enrutamiento: GET {baseUrl}/api/v1/market/{projectUid}/integrations/{integrationId}/… --- misma estructura de ruta que los recursos del panel de control bajo /projects/{uid}/integrations/{id}/market/…, sin el prefijo projects.
  • Propiedad: La clave API debe pertenecer al propietario del proyecto; projectUid e integrationId deben referirse a una integración ACTIVE de tipo MARKET.
  • Visibilidad: Los productos tienen estado DRAFT/PUBLISHED/ARCHIVED. La API externa de lista/detalle/variantes devuelve solo productos con estado PUBLISHED. Las APIs del panel de control pueden acceder a todos los estados para los propietarios.
  • IndexNOW: config.indexNow opcional en la integración MARKET --- Mailoo hace POST de identificadores de productos a tu URL de notificación al publicar/cambiar; tú envías URLs a IndexNOW (indexnow-notify{.interpreted-text role="doc"}). Las filas de contenido exponen indexNowStatus.
  • Campos de producto (panel): Los campos canónicos de producto y variante se almacenan en attributes; las superposiciones opcionales por idioma viven en locales.<code> (name y attributes en cada entrada). name es un atributo de tipo Base en registros de producto; sku es un atributo obligatorio de tipo Base marcado como eje de variante (almacenado en variantes, usado por la búsqueda effective-price).
  • JSON de producto (lista/detalle/variantes externas solamente): Las respuestas no incluyen un objeto locales. Los attributes del producto y de cada variante están ya fusionados para un solo idioma: valores base del registro del panel más la superposición para el idioma resuelto (name por idioma se mapea en la clave de atributo name). Idioma resuelto = consulta opcional locale=<code> si está presente y es válida (p. ej. en, de, pt-br); de lo contrario ``defaultCatalogLocale`` en el config de la integración MARKET (establecido en la pantalla Editar integración del panel); si no está establecido, ``en``. Un locale de consulta inválido devuelve 400.
  • Campos de producto (externos): Después de la fusión, las mismas reglas del panel aplican a los attributes aplanados (incluyendo la semántica de name / sku arriba).
  • Formato de ID: Los IDs de recursos MARKET en parámetros de solicitud y payloads de respuesta aceptan y devuelven valores tanto cuid como cuid2 (por ejemplo IDs de etiqueta/producto/lista-de-precios/proveedor).
  • Tipos de precio: Consulta Tipos de precio y precio efectivo abajo. Para saber qué precio de lista usar, los integradores deben configurar y enviar la clave del tipo de precio (consulta ``kind`` en ``GET .../effective-price``). El id del tipo de precio (CUID) es interno (y aparece en algunos JSON); no es el identificador externo principal para esa elección.
  • Indicador raíz del integrador: Los objetos de etiqueta incluyen useAsRoot. Los integradores pueden tratar las etiquetas con useAsRoot = true como raíces de nivel superior del catálogo (se permiten múltiples etiquetas), incluso cuando parentTagId no es null.

Iconos de etiqueta: Los objetos de etiqueta pueden incluir iconMediaId. Para obtener los bytes de la imagen, usa la integración de imágenes con una clave que permita ``image.external-read`` (consulta images{.interpreted-text role="doc"}).

Nota de renderizado del panel de Mailoo: useAsRoot está destinado a la lógica de menú del integrador externo. El renderizado y orden del árbol de etiquetas del propio panel de Mailoo permanecen sin cambios y siguen las reglas de jerarquía existentes.

Variables de entorno del BFF

Alinea con el panel de Conexión en la página de la integración (mismos nombres que abajo). Variables típicas del lado del servidor:

  • MAILOO_MARKET_API --- URL base de la API (p. ej. https://api.mailoo.app o tu host de desarrollo), sin barra final.
  • MAILOO_MARKET_API_KEY --- Clave API con ``market.external-read`` (o FULL).
  • MAILOO_MARKET_PROJECT_UID --- UID del proyecto.
  • MAILOO_MARKET_INTEGRATION_ID --- Id de integración MARKET (CUID).

Endpoints (todos GET, todos requieren X-API-Key)

Reemplaza {baseUrl}, {projectUid}, {integrationId} y parámetros de ruta según sea necesario.

  • GET {baseUrl}/api/v1/market/{projectUid}/integrations/{integrationId}/types --- Tipos de producto y atributos.
  • GET …/products --- Todos los productos publicados (variantes y vínculos de etiquetas incluidos; idioma fusionado, sin locales). Consulta opcional ``locale`` (ver Resumen).
  • GET …/products/{productId} --- Un producto (mismo contrato).
  • GET …/products/{productId}/variants --- Solo variantes; cada fila tiene attributes fusionados y sin locales. ``locale`` opcional.
  • GET …/products/{productId}/price-range --- Para un producto publicado: precio unitario efectivo por variante (minAmount / maxAmount son iguales para un solo tipo de precio) más fila opcional a nivel de producto y un agregado min/max a través de esos números. Consulta: ``kind`` o ``priceKindId`` (mismas reglas que ``effective-price``), ``measurementUnitId`` opcional, ``at`` (datetime ISO), ``currency`` opcional (ISO 4217, tres letras). Resolución de moneda: si se omite ``currency``, la moneda de la respuesta se toma de la primera línea de precio coincidente al recorrer listas activas en orden cronológico (``effectiveAt`` ascendente) y líneas en orden de ``id``; solo las líneas en esa moneda se usan para fusiones (otras monedas se ignoran). Si se establece ``currency``, solo participan las líneas en esa moneda; el campo ``currency`` de la respuesta repite la consulta incluso cuando no hay líneas coincidentes (entonces los montos son null).
  • GET …/tags y GET …/tags/{tagId} --- Etiquetas del catálogo.
  • GET …/price-lists y GET …/price-lists/{priceListId} --- Listas de precios internas.
  • GET …/effective-price --- Resuelve precio unitario para un producto y un tipo de precio. Usa la consulta ``kind=<priceKindKey>`` (ver Tipos de precio y precio efectivo). Opcionales: productId / variantId / measurementUnitId / at (datetime ISO). (La consulta ``priceKindId`` se acepta para llamadas internas o BFF; el código de catálogo externo debe usar ``kind``.)
  • GET …/suppliers --- Proveedores.
  • GET …/suppliers/{supplierId}/products --- Productos del proveedor.
  • GET …/suppliers/{supplierId}/price-lists y GET …/suppliers/{supplierId}/price-lists/{supplierPriceListId} --- Listas de precios del proveedor.
  • GET …/units --- Unidades de medida.
  • GET …/packagings --- Embalajes.
  • GET …/packaging-equivalences --- Equivalencias de embalaje.

Las respuestas usan { success: true, data: … }. Para la lista/detalle/variantes de producto externas, data coincide con la estructura de idioma fusionado arriba (sin locales). Las rutas autenticadas del panel GET /api/v1/projects/…/market/products… siguen devolviendo la estructura completa del editor incluyendo locales.

Tipos de precio y precio efectivo

  • En la base de datos, un tipo de precio tiene una ``key`` string estable (única por integración MARKET, p. ej. base) y un ``id`` interno (CUID). La key es lo que debes configurar en el entorno / CMS ("usa base para precios de lista").
  • Resolver un precio desde código externo o de tienda: llama a ``GET {baseUrl}/api/v1/market/{projectUid}/integrations/{integrationId}/effective-price`` con ``kind=<key>`` y ``productId=`` (y opcionales ``variantId``, ``measurementUnitId``, ``at``). Este es el contrato soportado: ``kind`` es la clave del tipo de precio, no el CUID. Todas las listas de precios activas con effectiveAt <= at se fusionan en orden cronológico (más antigua primero, luego las más nuevas superponen): para un ``variantId`` dado, la línea más reciente específica de variante para ese SKU gana; si ninguna lista posterior a esa línea de SKU redefine ese SKU, las líneas de SKU más antiguas permanecen en efecto. Si no hay línea de SKU en absoluto, la línea más reciente a nivel de producto (``variantId`` null) aplica. Cuando se omite ``variantId``, solo se consideran las filas a nivel de producto (la más reciente gana).
  • ``priceKindId`` en la cadena de consulta es solo para llamadas que ya almacenan CUIDs (herramientas de Mailoo, BFFs que reflejan ids internos). No trates el parámetro de consulta priceKindId como la API externa principal para "qué precio de lista usar" --- usa ``kind`` en su lugar.
  • Las respuestas pueden incluir ``priceKindId`` y ``priceKindKey`` (y similares en líneas de lista de precios). Puedes leer ``priceKindKey``; ``priceKindId`` en las respuestas es para correlación y no es algo que el JS del frontend público necesite codificar.
  • Ciclo de vida de listas de precios (rutas de lectura): GET …/price-lists y los payloads de lista incluyen ``archivedAt`` y ``endsAt`` cuando están establecidos. ``GET .../effective-price``, ``GET .../products/{productId}/price-range`` y el pricing del checkout de pedidos solo consideran listas que no están archivadas y, si ``endsAt`` está establecido, solo cuando el instante ``at`` de la consulta es estrictamente anterior a ``endsAt``. Las llamadas del panel pueden hacer PATCH / DELETE a listas de precios internas bajo ``/api/v1/projects/.../market/price-lists/{priceListId}`` (Bearer); la eliminación se rechaza con 409 cuando las dimensiones de alguna línea aparecen en una línea de pedido existente para esa integración.

Tipos de valor de atributo de producto (/types)

GET …/types devuelve atributos de tipo con:

  • valueType: STRING, NUMBER, BOOLEAN, ENUM, SELECT, MD_TEXT
  • options: string[] | null

SELECT es el tipo basado en lista estricta. Para SELECT, options es una lista no vacía de valores permitidos, y los valores de atributo de producto/variante deben coincidir exactamente con uno de ellos.

``MD_TEXT`` e imágenes: Cualquier atributo de producto o variante con valueType: MD_TEXT puede incrustar imágenes alojadas en Mailoo usando sintaxis de imagen markdown (por ejemplo ![alt](url)). Al escribir, el panel normaliza las URLs de imágenes de Mailoo dentro de esos campos; el JSON de producto y variante de las respuestas GET también normaliza strings anidados para que los integradores vean la forma canónica de embed público (…/api/v1/images/public/content?id=<mediaId>). Para obtener bytes de imagen desde esa URL, usa la API de imágenes con una clave que permita ``image.external-read`` (consulta images{.interpreted-text role="doc"}).

``MD_TEXT`` y HTML (solo GET de catálogo externo): En las rutas externas GET …/products, GET …/products/{productId} y GET …/products/{productId}/variants (con X-API-Key y un producto publicado), la API añade un campo string paralelo <attributeKey>Html junto a cada valor Markdown para atributos declarados como MD_TEXT en cualquiera de los tipos vinculados al producto, en los attributes fusionados del producto y en los attributes fusionados de cada variante (no hay árbol locales separado en la respuesta). Usa la clave original para la fuente Markdown (como content del blog) y …Html para HTML renderizado del servidor (como htmlContent del blog). Las respuestas del panel GET /projects/…/market/… omiten estos campos *Html para evitar trabajo extra al editar.

Galería e imagen de listado del producto: Cada producto puede tener un array ``images`` ordenado (objetos con id, mediaId, sortOrder y ``url`` canónica) más ``previewMediaId`` y ``previewImageUrl``. La vista previa es la imagen recomendada para tarjetas de catálogo / listas de productos; si ``previewMediaId`` es null, los integradores pueden recurrir a la primera imagen de la galería. Los media ids de galería se validan al crear/actualizar (media READY, limitado al contexto USER / PROJECT / INTEGRATION del propietario para ese mercado). Redimensionamiento al vuelo opcional: añade ``w``, ``h``, ``fit``, ``format``, ``q`` a la URL de contenido público según se documenta en images{.interpreted-text role="doc"}.

Ejemplo de definición de atributo:

{
  "key": "display_type",
  "name": "Display Type",
  "valueType": "SELECT",
  "options": ["LCD", "OLED", "micro-OLED", "QD-LCD"]
}

Nota de cambio incompatible: las integraciones que anteriormente trataban ENUM como string de forma libre deben cambiar a SELECT cuando se requiere una lista estricta de opciones.

Panel de control: crear productos desde JSON

En el panel de MARKET (sección Productos), puedes preparar payloads de producto como JSON:

  • Copiar plantilla --- copia el borrador actual del producto + pistas de esquema disponibles:
  • schemaVersion
  • product (typeIds, attributes, tagIds)
  • template.attributeSchema (claves, tipos de valor, indicadores de obligatoriedad, opciones SELECT)
  • template.availableTags (todas las etiquetas de catálogo disponibles actualmente)
  • Pegar datos del producto --- cambia el formulario a un modo de entrada JSON donde pegas el payload manualmente (no se requiere solicitud de lectura del portapapeles del navegador).
  • Crear producto (en modo JSON) valida el payload en el servidor y luego crea el producto si la validación tiene éxito.

Para actualizaciones de múltiples filas, prefiere subida/descarga CSV: market-catalog-csv{.interpreted-text role="doc"}.

Payload JSON esperado

{
  "schemaVersion": 1,
  "product": {
    "typeIds": ["<typeId-1>", "<typeId-2>"],
    "attributes": {
      "name": "Vision Pro X Ultra",
      "display-type": "micro-OLED",
      "short_description": "Flagship model.",
      "full_description": "## Vision Pro X Ultra"
    },
    "tagIds": ["<tagId-1>"]
  }
}

Comportamiento de validación

La validación del lado del servidor es la única fuente de verdad (sin reglas de dominio duplicadas en el cliente):

  • El panel llama a POST /api/v1/projects/{uid}/integrations/{id}/market/products/validate antes de crear.
  • El endpoint reutiliza las mismas reglas de validación que la creación de productos:
  • las claves de atributo desconocidas se rechazan
  • los atributos obligatorios deben estar presentes
  • los tipos de valor deben coincidir (NUMBER/BOOLEAN/STRING/MD_TEXT/ENUM/SELECT)
  • los valores SELECT deben ser uno de las options declaradas
  • los ids de etiqueta deben pertenecer a la integración
  • Si la validación falla, el producto no se crea y se muestra el mensaje de error de la API.

Checkout por correo (pedidos)

La API de lectura del catálogo arriba no incluye un carrito de compras: la tienda del integrador mantiene el estado del carrito (por ejemplo localStorage) y envía un snapshot cuando el comprador finaliza la compra.

  • Crear pedido (servidor / BFF): POST {baseUrl}/api/v1/market/{projectUid}/integrations/{integrationId}/orders con cabecera ``X-API-Key`` y alcance ``market.order.submit`` (claves RESTRICTED) o una clave FULL. Cuerpo de la solicitud: ``customerEmail`` del comprador, ``items[]`` (cada línea: ``productId``, ``quantity`` como string decimal, y o bien ``priceKindKey`` (clave amigable para el integrador, p. ej. base) o ``priceKindId`` (CUID) --- no ambos, ``variantId`` / ``measurementUnitId`` opcionales). Mailoo resuelve precios unitarios de las mismas listas de precios que ``GET .../effective-price``; la respuesta incluye ``accessToken`` y ``accessExpiresAt`` (acceso temporal de solo lectura al pedido).
  • Leer pedido (navegador / cualquier cliente): GET {baseUrl}/api/v1/market/public/orders/{orderId}?token= o cabecera ``X-Market-Order-Token`` --- sin API key. Devuelve un snapshot del pedido mientras el token sea válido (TTL predeterminado 72 horas, configurable mediante ``MARKET_ORDER_ACCESS_TOKEN_TTL_HOURS`` en el host de la API). Token faltante/inválido/expirado devuelve 404 (mismo mensaje que id incorrecto).
  • Panel del propietario (JWT): listar/obtener/actualizar pedidos bajo /api/v1/projects/{uid}/integrations/{id}/market/orders/… (Bearer).

Flujo de extremo a extremo, patrones BFF y notas operativas: market-order-email-checkout{.interpreted-text role="doc"}.

Lecturas adicionales

  • OpenAPI: https://api.mailoo.app/docs/v1
  • Flujo CSV del panel: market-catalog-csv{.interpreted-text role="doc"}