Skip to Content
v1.5Documentación oficial de la API LX CloudPos · estable en producción
V1Referencia De EndpointsCrear o actualizar producto

Crear o actualizar producto

POST /api/v1/productos

Da de alta o actualiza un producto del catálogo de inventario del tenant — el mismo catálogo que usa POST /facturas cuando una línea trae idProducto. Es un upsert por codigo: si ya existe un producto con ese código en el tenant, se actualiza; si no, se crea.

Existe porque hasta v1.2 un integrador solo podía facturar líneas ya cargadas en el catálogo, o líneas ad-hoc del catálogo de compra — no tenía forma de poblar el catálogo de venta antes de emitir. Con este endpoint es posible precargar el SKU propio del integrador y facturar posteriormente por codigoProducto sin conocer los ids internos de LX CloudPos.

Autenticación y permisos

AtributoValor
EsquemaAPI key (X-Api-Key)
Scope requeridocatalogo:write
IdempotenteNo acepta Idempotency-Key. El upsert por codigo ya es reintentable: reenviar el mismo body da el mismo resultado, solo que como actualización (200) en vez de creación (201).
Rate limit120 req/min por API key
Body máximo64 KB — menor que el límite general de 256 KB

Headers

POST /api/v1/productos HTTP/1.1 Host: api.pos.lxsyscr.com X-Api-Key: lxs_live_a1b2c3d4_001122... Content-Type: application/json Accept: application/json

Request body

Ver ProductoApiRequestDto para el schema completo.

Ejemplo — alta:

{ "codigo": "API-TEST-001", "nombre": "Producto de prueba API", "descripcion": "Alta desde la API de integradores", "codigoCabys": "2342002000000", "unidadMedida": "Unid", "precioVenta": 1500.00, "precioCosto": 1000.00, "impuestos": [ { "codigo": "01", "porcentaje": 13.00, "codigoTarifa": "08" } ] }

No se mandó idMarca ni idProveedor — ver la nota sobre ese par de campos en las reglas de negocio.

Ejemplo — actualización (mismo codigo, cambia el precio):

{ "codigo": "API-TEST-001", "nombre": "Producto de prueba API", "descripcion": "Alta desde la API de integradores", "codigoCabys": "2342002000000", "unidadMedida": "Unid", "precioVenta": 1650.00, "precioCosto": 1000.00, "impuestos": [ { "codigo": "01", "porcentaje": 13.00, "codigoTarifa": "08" } ] }

Importante: Esta actualización no es parcial. El cuerpo de arriba repite todos los campos, no solo precioVenta, porque los que se omitan se sobrescriben con su valor por defecto. Ver las reglas de negocio.

Respuestas

201 Created — se creó el producto

HTTP/1.1 201 Created Location: /api/v1/productos/API-TEST-001 Content-Type: application/json
{ "idProducto": 919, "codigo": "API-TEST-001", "nombre": "Producto de prueba API", "descripcion": "Alta desde la API de integradores", "codigoCabys": "2342002000000", "unidadMedida": "Unid", "precioVenta": 1500.0000, "precioCosto": 1000.0000, "idCategoria": null, "idSubcategoria": null, "idMarca": 0, "idProveedor": 0, "estado": true, "impuestos": [ { "codigo": "01", "porcentaje": 13.0000, "codigoTarifa": "08" } ], "advertencia": null }

200 OK — se actualizó un producto existente

Mismo shape que el 201. La única forma de distinguir alta de actualización es el status code: el body no trae ningún indicador propio.

400 Bad Request

Dos orígenes posibles, con dos formatos distintos.

a) Validación de modelo — falta un campo requerido, o precioVenta/precioCosto no son mayores que cero (por ejemplo si se omiten del body, ya que quedan en 0). Formato detallado, igual que el resto de la API:

{ "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "title": "One or more validation errors occurred.", "status": 400, "errors": { "Codigo": ["The Codigo field is required."], "CodigoCabys": ["The CodigoCabys field is required."] } }

b) Reglas de negocio — formato simple { "error": "..." }, en el orden en que se validan:

{ "error": "Debe indicar 'codigo'." }
{ "error": "Debe indicar 'nombre'." }
{ "error": "El código CABYS debe tener 13 dígitos." }
{ "error": "Debe indicar 'unidadMedida'." }
{ "error": "Debe indicar 'precioVenta'." }
{ "error": "Debe indicar 'precioCosto'." }
{ "error": "Debe indicar al menos un impuesto en 'impuestos'." }
{ "error": "Cada línea de 'impuestos' debe indicar 'codigo'." }
{ "error": "El campo 'estado' no se puede cambiar al actualizar un producto: activar o desactivar es una operación aparte. Enviá el producto sin 'estado' o con el valor que ya tiene." }
{ "error": "La marca 7 no existe. Consultá GET /api/v1/catalogos/marcas." }
{ "error": "El proveedor 3 no existe." }
{ "error": "El tipo de código '05' no existe. Consultá GET /api/v1/catalogos/tipos-codigo." }

Y, tras el intento de guardado, tres casos borde poco frecuentes — el segundo suele ser una carrera con otro request creando o editando el mismo codigo en paralelo:

{ "error": "No se pudo guardar el producto." }
{ "error": "El código 'API-TEST-001' ya pertenece a otro producto." }
{ "error": "El producto 'API-TEST-001' se guardó pero no se pudo releer." }

Los tres llegan como 400, no como 500, aunque conceptualmente corresponden más a un fallo interno que a un error del request enviado.

401 Unauthorized

{ "error": "API key ausente, inválida o revocada." }

403 Forbidden

Key válida pero sin catalogo:write:

{ "error": "La API key no tiene el scope requerido para esta operación." }

429 Too Many Requests / 500 Internal Server Error

Igual que el resto de la API — ver Rate limiting y Códigos HTTP. El 500 de este endpoint responde:

{ "error": "Error interno guardando el producto." }

Reglas de negocio

  • Upsert por codigo, no por idProducto. El request nunca incluye idProducto — no existe forma de indicar “actualice el producto 919”; la resolución siempre se realiza buscando el codigo.

  • La actualización NO es parcial. A diferencia de clientes, este endpoint reconstruye el producto completo a partir del body en cada llamada. Un campo omitido no conserva su valor anterior:

    • descripcion omitida → queda en "" (string vacío, no null).
    • idCategoria / idSubcategoria omitidos → quedan en null.
    • porcentajeGanancia omitido → queda en 0.
    • tipoCodigo omitido → vuelve al default "01".
    • impuestos se reemplaza entero por lo que venga en el body — no se mezcla con lo que ya tenía.
  • Las dos excepciones, los únicos campos que conservan lo que había si se omiten:

    • estado: solo aplica al dar de alta (si se omite, el producto nace activo). En una actualización el producto conserva el estado que tenía, y mandar un valor distinto del actual devuelve 400 — no es un no-op silencioso. Activar o desactivar es una operación aparte: en el POS, desactivar un producto que nunca se usó lo borra, y una solicitud de actualización no debe derivar en un borrado no solicitado.
    • idMarca / idProveedor: conservan la marca y el proveedor que el producto ya tenía.
  • idMarca / idProveedor son opcionales y sin default arbitrario. Si se omiten al crear, el producto queda sin marca y sin proveedor — el sistema no le asigna ninguna por su cuenta. En la respuesta esto se ve como idMarca: 0 / idProveedor: 0, no como null: el 0 significa “sin asignar”, no “marca número 0”. Las marcas del tenant se listan con GET /catalogos/marcas y se crean con POST /marcas. Un id inexistente devuelve 400 nombrando el campo.

  • tipoCodigo se valida contra el catálogo de Hacienda (GET /catalogos/tipos-codigo). Si se omite se usa "01" (código del producto del vendedor), correcto en la gran mayoría de las integraciones.

  • codigoCabys y unidadMedida son obligatorios porque son datos fiscales: van directo al XML del comprobante cuando el producto se factura. El código CABYS debe tener al menos 13 dígitos — ver Códigos CAByS.

  • impuestos es obligatorio, con al menos una línea — el mismo mínimo que exige el formulario de producto del POS. Cada línea necesita codigo (por ejemplo "01" para IVA).

  • advertencia no bloquea nada. Se llena cuando el codigoCabys clasifica como servicio (primer dígito 6–9) y la unidadMedida enviada no es una unidad de servicio válida: el sistema la autocorrige a "Os" y lo notifica en la respuesta, en lugar de rechazar el request.

    El CABYS {codigoCabys} corresponde a un servicio; la unidad de medida se ajustó de "{anterior}" a "Os" (Otro tipo de servicio). Puede cambiarla por una unidad de servicio más específica si lo prefiere.
  • Auditoría: el producto queda registrado con usuario "api:" seguido del nombre de la API key, para poder rastrear qué integración modificó cada producto.

  • El stock no se gestiona por este endpoint. En un alta, el producto se crea con cantidad 0 en todas las bodegas. En una edición no se modifica el stock. Para moverlo existen Entradas y Traspasos, no este endpoint.