Crear o actualizar cliente
POST /api/v1/clientes
Da de alta o actualiza un cliente por identificacion. Antes de este endpoint, un integrador solo podía crear un cliente como efecto colateral de emitir un comprobante (el Modo B de FacturaApiClienteDto); aquí se puede dar de alta, actualizar o consultar sin emitir ningún comprobante.
Autenticación y permisos
| Atributo | Valor |
|---|---|
| Esquema | API key (X-Api-Key) |
| Scope requerido | catalogo:write |
| Idempotente | No acepta Idempotency-Key. El upsert por identificacion ya es reintentable, salvo que se trate de una actualización parcial y el segundo intento incluya menos campos. |
| Rate limit | 120 req/min por API key |
| Body máximo | 64 KB |
Headers
POST /api/v1/clientes HTTP/1.1
Host: api.pos.lxsyscr.com
X-Api-Key: lxs_live_a1b2c3d4_001122...
Content-Type: application/json
Accept: application/jsonRequest body
Ver ClienteApiRequestDto para el schema completo.
Ejemplo — alta:
{
"identificacion": "117890123",
"tipoIdentificacion": "01",
"nombre": "Cliente Prueba",
"apellidos": "Integracion API",
"correo": "cliente.prueba@ejemplo.cr",
"telefono": "88888888"
}Ejemplo — actualización parcial (solo cambia el teléfono):
{
"identificacion": "117890123",
"telefono": "87001234"
}Esto solo sobrescribe telefono. nombre, apellidos, correo y el resto quedan exactamente como estaban — no es necesario repetirlos.
Respuestas
201 Created — se creó el cliente
HTTP/1.1 201 Created
Location: /api/v1/clientes/117890123
Content-Type: application/json{
"idCliente": 46,
"identificacion": "117890123",
"tipoIdentificacion": "01",
"nombre": "Cliente Prueba",
"apellidos": "Integracion API",
"nombreInterno": null,
"correo": "cliente.prueba@ejemplo.cr",
"telefono": "8888-8888",
"direccion": null,
"comentario": null,
"estado": true,
"exonerado": false,
"pagaTimbre": false,
"limiteCredito": null
}Debe observarse "telefono": "8888-8888": se envió "88888888" y el sistema lo normalizó.
200 OK — se actualizó un cliente existente
Mismo shape que el 201, reflejando el estado final del cliente. Los campos que no se enviaron conservan su valor previo.
400 Bad Request
a) Validación de modelo — mismo formato detallado que el resto de la API. En la práctica es raro: el DTO no marca ningún campo como requerido a nivel de framework, así que casi todos los 400 de este endpoint vienen de las reglas de negocio.
b) Reglas de negocio — formato simple:
{ "error": "La identificación es requerida." }Al dar de alta (identificación nueva):
{ "error": "El nombre es requerido para dar de alta el cliente." }{ "error": "El tipo de identificación es requerido para dar de alta el cliente." }{ "error": "Debe ingresar al menos un número de teléfono." }{ "error": "Teléfono inválido: \"12345\". Debe tener 8 dígitos (formato 506 0000-0000)." }{ "error": "Debe ingresar al menos un correo electrónico." }{ "error": "Correo inválido: \"no-es-un-correo\". Use un formato como nombre@dominio.com." }
correoytelefonoson obligatorios al dar de alta, aunque el schema no los marque como tales: la validación rechaza la cadena vacía. Si la integración todavía no dispone de datos de contacto del cliente, no será posible darlo de alta por esta vía sin registrar un valor ficticio; se recomienda utilizar este endpoint únicamente cuando se cuente con datos reales.
Al actualizar, los mismos errores de teléfono y correo aplican si se envían esos campos. Ver la tabla de semántica más abajo.
Casos borde, poco frecuentes, que también llegan como 400:
{ "error": "No se pudo dar de alta el cliente 117890123." }{ "error": "El cliente 46 recién creado no se pudo leer." }{ "error": "No se pudo actualizar el cliente 117890123." }{ "error": "El cliente 46 actualizado no se pudo releer." }Y, en el alta, si hay una carrera con otro request creando el mismo cliente en paralelo:
{ "error": "Ya existe un cliente con identificación 117890123." }401 Unauthorized
{ "error": "API key ausente, inválida o revocada." }403 Forbidden
{ "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 cliente." }Reglas de negocio
- Upsert por
identificacion. Si existe un cliente con esa identificación en el tenant, se actualiza; si no, se crea. La identificación es la clave de búsqueda, no un campo editable: no hay forma de cambiarle la identificación a un cliente por este endpoint. - La actualización SÍ es parcial — a diferencia de productos. Se parte del registro existente y solo se sobrescriben los campos presentes en el body. No obstante, la semántica exacta de “presente” varía por campo: no se aplica de forma uniforme la equivalencia
null= “no modificar”.
| Campo | Ausente o null | "" (string vacío explícito) |
|---|---|---|
tipoIdentificacion | No cambia | Se ignora — ni error ni cambio |
nombre | No cambia | Se ignora — ni error ni cambio |
apellidos | No cambia | Se aplica — queda en "" |
nombreInterno | No cambia | Se aplica — queda en null |
telefono | No cambia | 400 — no se puede vaciar, solo reemplazar por uno válido |
correo | No cambia | 400 — mismo caso que teléfono |
direccion | No cambia | Se aplica — queda en null |
comentario | No cambia | Se aplica — queda en null |
exonerado / pagaTimbre | No cambia. Si el campo sí viene, false se aplica igual que true. | — (son booleanos) |
limiteCredito | No cambia | — (es numérico) |
En la práctica: para limpiar direccion, comentario o nombreInterno, debe enviarse "". Para cambiar telefono o correo es necesario enviar un valor válido. Para nombre o tipoIdentificacion, enviar "" no produce ningún efecto — si se requiere corregirlos, debe enviarse el valor real.
- El teléfono se normaliza. Si se envía
"88888888", se guarda y se devuelve como"8888-8888". Pueden enviarse varios separados por;y se validan todos. Actualmente solo se admite Costa Rica (8 dígitos, prefijo506); otra cantidad de dígitos produce400. pagaTimbre(timbre de máquina expendedora) se asumefalseal dar de alta si no se envía de forma explícita. Es la misma convención que usa el alta automática durante la emisión.exoneradoes solo un marcador booleano. Las exoneraciones en sí — documentos, vigencia, porcentaje — no se administran por este endpoint.