Skip to Content
v1.5Official documentation for the LX CloudPos API · stable in production
v1.5 (current)Endpoint referenceCreate or update customer

Create or update customer

POST /api/v1/clientes

Creates or updates a customer by identificacion. Before this endpoint existed, an integrator could only create a customer as a side effect of issuing a document (the Mode B of FacturaApiClienteDto); here you can create, update, or query without issuing any document.

Authentication and permissions

AttributeValue
SchemeAPI key (X-Api-Key)
Required scopecatalogo:write
IdempotentDoes not accept Idempotency-Key. The upsert by identificacion is already retry-safe, except when it’s a partial update and the second attempt includes fewer fields.
Rate limit120 req/min per API key
Max body64 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/json

Request body

See ClienteApiRequestDto for the full schema.

Example — create:

{ "identificacion": "117890123", "tipoIdentificacion": "01", "nombre": "Cliente Prueba", "apellidos": "Integracion API", "correo": "cliente.prueba@ejemplo.cr", "telefono": "88888888" }

Example — partial update (only the phone changes):

{ "identificacion": "117890123", "telefono": "87001234" }

This only overwrites telefono. nombre, apellidos, correo, and the rest stay exactly as they were — there’s no need to repeat them.

Responses

201 Created — the customer was created

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, "exoneraciones": [] }

Note "telefono": "8888-8888": "88888888" was sent and the system normalized it. exoneraciones comes back empty because this endpoint doesn’t manage them — they’re registered from the POS; see ClienteApiResponseDto and Tax exemptions.

200 OK — an existing customer was updated

Same shape as the 201, reflecting the customer’s final state. Fields that weren’t sent keep their previous value. exoneraciones reflects whatever the customer already had registered in the POS, if anything.

400 Bad Request

a) Model validation — same detailed format as the rest of the API. In practice this is rare: the DTO doesn’t mark any field as required at the framework level, so almost every 400 from this endpoint comes from business rules.

b) Business rules — simple format:

{ "error": "La identificación es requerida." }

When creating (new identification):

{ "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." }

correo and telefono are required when creating, even though the schema doesn’t mark them as such: validation rejects an empty string. If the integration doesn’t yet have the customer’s contact details, you won’t be able to create it through this endpoint without registering a placeholder value; use this endpoint only once you have real data.

When updating, the same phone and email errors apply if those fields are sent. See the semantics table below.

Rare edge cases that also arrive as 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." }

And, on creation, if there’s a race with another request creating the same customer in parallel:

{ "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

Same as the rest of the API — see Rate limiting and HTTP status codes. The 500 from this endpoint responds:

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

Business rules

  • Upsert by identificacion. If a customer with that identification already exists in the tenant, it’s updated; otherwise, it’s created. The identification is the lookup key, not an editable field: there’s no way to change a customer’s identification through this endpoint.
  • The update IS partial — unlike products. It starts from the existing record and only overwrites the fields present in the body. That said, the exact semantics of “present” vary by field: the null = “don’t change” equivalence is not applied uniformly.
FieldAbsent or null"" (explicit empty string)
tipoIdentificacionNo changeIgnored — no error, no change
nombreNo changeIgnored — no error, no change
apellidosNo changeApplied — ends up ""
nombreInternoNo changeApplied — ends up null
telefonoNo change400 — cannot be cleared, only replaced with a valid one
correoNo change400 — same case as phone
direccionNo changeApplied — ends up null
comentarioNo changeApplied — ends up null
exonerado / pagaTimbreNo change. If the field is sent, false applies just like true.— (they are booleans)
limiteCreditoNo change— (it is numeric)

In practice: to clear direccion, comentario, or nombreInterno, send "". To change telefono or correo you need to send a valid value. For nombre or tipoIdentificacion, sending "" has no effect — if you need to correct them, send the real value.

  • The phone is normalized. If you send "88888888", it’s stored and returned as "8888-8888". You can send several separated by ; and all of them are validated. Currently only Costa Rica is supported (8 digits, prefix 506); any other digit count produces 400.
  • pagaTimbre (vending-machine stamp tax) is assumed false on creation if not sent explicitly. This is the same convention used by the automatic creation that happens during issuance.
  • exonerado is just a boolean flag. Tax exemptions themselves — documents, validity period, percentage — are not managed through this endpoint: they’re created from the POS and queried (with their idExoneracion) in the response’s exoneraciones array. See Tax exemptions.