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
| Atributo | Valor |
|---|---|
| Esquema | API key (X-Api-Key) |
| Scope requerido | catalogo:write |
| Idempotente | No 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 limit | 120 req/min por API key |
| Body máximo | 64 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/jsonRequest 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 poridProducto. El request nunca incluyeidProducto— no existe forma de indicar “actualice el producto 919”; la resolución siempre se realiza buscando elcodigo. -
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:
descripcionomitida → queda en""(string vacío, nonull).idCategoria/idSubcategoriaomitidos → quedan ennull.porcentajeGananciaomitido → queda en0.tipoCodigoomitido → vuelve al default"01".impuestosse 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 devuelve400— 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/idProveedorson 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 comoidMarca: 0/idProveedor: 0, no comonull: el0significa “sin asignar”, no “marca número 0”. Las marcas del tenant se listan conGET /catalogos/marcasy se crean conPOST /marcas. Un id inexistente devuelve400nombrando el campo. -
tipoCodigose 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. -
codigoCabysyunidadMedidason 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. -
impuestoses obligatorio, con al menos una línea — el mismo mínimo que exige el formulario de producto del POS. Cada línea necesitacodigo(por ejemplo"01"para IVA). -
advertenciano bloquea nada. Se llena cuando elcodigoCabysclasifica como servicio (primer dígito 6–9) y launidadMedidaenviada 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
0en todas las bodegas. En una edición no se modifica el stock. Para moverlo existen Entradas y Traspasos, no este endpoint.