Centro de Ayuda de Dardo

Clientes y tarjetas

Qué datos guarda un cliente, cómo identificarlo desde otro sistema y cómo llegar a su tarjeta

Son los dos recursos con los que vas a trabajar el 90% del tiempo. Esta página documenta qué campos tienen, cuáles pueden venir vacíos y qué identificadores tenés disponibles para reconocer a una persona desde un sistema que no es Dardo.

Un cliente, varias tarjetas

La confusión más común al arrancar: el cliente y la tarjeta son dos cosas distintas, y no hay una sola tarjeta por cliente.

  • El cliente es la persona: teléfono, email, nombre, fecha de nacimiento, segmentos.
  • La tarjeta es su participación en un programa concreto. Se emite contra una plantilla (templateId) y es la que tiene el saldo.

Un cliente tiene una tarjeta por cada plantilla en la que se registró. Si tu cuenta tiene tres programas activos, la misma persona puede tener tres tarjetas, cada una con su saldo y su mecánica.

Eso se ve en el modelo:

  • Card.customerId es requerido — toda tarjeta pertenece a un cliente.
  • POST /api/v2/cards exige templateId y customerId — la tarjeta se emite para un cliente, contra una plantilla.
  • GET /api/v2/cards?customerId=... devuelve una lista.
  • CustomerOutput no tiene un campo cards[]. La relación se navega desde el lado de la tarjeta, filtrando por cliente.

Consecuencia práctica: GET /api/v2/cards sin templateId puede traerte tarjetas de otros programas de la misma cuenta. Si tu integración trabaja contra un programa puntual, pasá siempre templateId en vez de asumir que va a venir una sola. El templateId lo sacás de GET /api/v2/templates.

Y una consecuencia menos obvia: los endpoints de acumulación y canje cuelgan de la tarjeta (/api/v2/cards/{id}/add-point), no del cliente. Sin el id de la tarjeta correcta no podés acreditar nada.

Crear un cliente: teléfono o email

Basta con uno de los dos. phone no es obligatorio.

El schema CreateCustomerInput no declara un required de nivel raíz. Declara esto:

"anyOf": [
  { "required": ["phone"] },
  { "required": ["email"] }
]

Traducido: mandá phone o email. Al menos uno. Podés mandar los dos. Ninguno de los dos es obligatorio por sí solo.

La referencia auto-generada lo muestra mal: colapsa el anyOf y marca phone con asterisco de obligatorio. No lo es. Si tu negocio solo captura email —una tienda online, por ejemplo— podés dar de alta clientes igual, sin inventar teléfonos falsos.

Cuerpo del pedido¿Válido?
{"phone": "+5491168421793"}
{"email": "marina.ferreyra@gmail.com"}
{"phone": "+5491168421793", "email": "marina.ferreyra@gmail.com"}
{"firstName": "Marina", "surname": "Ferreyra"}No — falta phone o email
{}No

Campos del alta — POST /api/v2/customers

CampoTipoRequeridoNullableFormatoQué es
phonestringCondicionalTeléfono. Requerido solo si no mandás email
emailstringCondicionalEmail. Requerido solo si no mandás phone
firstNamestringNoNombre
surnamestringNoApellido
genderintegerNoenum 0, 1, 20 desconocido, 1 masculino, 2 femenino
dateOfBirthstringNosin format declaradoFecha de nacimiento
externalUserIdstringNoEl ID del cliente en tu sistema
# Alta solo con email: es válido, aunque la referencia diga lo contrario
curl -X POST 'https://api.dardo.ai/api/v2/customers' \
  -H 'X-API-Key: TU_CLAVE_API' \
  -H 'Content-Type: application/json' \
  -d '{
    "email": "marina.ferreyra@gmail.com",
    "firstName": "Marina",
    "externalUserId": "CLI-4471"
  }'

Responde 201 con el cliente completo en data.

dateOfBirth en el alta no declara formato. En la respuesta sí viene tipado como date ("1991-04-17"), así que YYYY-MM-DD es lo razonable para mandar. Si vas a cargar cumpleaños en volumen, confirmalo con una prueba de una fila antes de correr la migración entera, o escribinos a soporte@dardo.ai.

Qué devuelve un cliente

Esta es la forma completa de CustomerOutput, y es la misma en todos los lugares donde aparece un cliente: el listado, el detalle, la respuesta del alta y la edición, y embebido dentro de Card.customer y Operation.customer.

CampoTipoSiempre presenteNullableFormatoQué es
idstringNoIdentificador único y estable del cliente
phonestringNoTeléfono
emailstringNoEmail
firstNamestringNoNombre
surnamestringNoApellido
genderintegerNo0 desconocido, 1 masculino, 2 femenino
dateOfBirthstringNoNodate (YYYY-MM-DD)Fecha de nacimiento
externalUserIdstringNoEl ID que vos guardaste al crearlo
createdAtstringNodate-timeAlta
updatedAtstringNodate-timeÚltima modificación
segmentsarrayNoSegmentos del cliente, embebidos

Solo id y segments están declarados como obligatorios en la respuesta. Todo lo demás puede venir ausente o en null, incluido el nombre y el email. Si tu código hace customer.firstName.trim() sin chequear, se va a romper con datos reales.

Ojo con dateOfBirth: no está en la lista de obligatorios y tampoco está marcado como nullable. En la práctica, tratá la ausencia de la clave y el null de la misma forma.

Cada ítem de segments trae id (integer), type (integer) y name (string), los tres obligatorios. Qué significan y por qué no conviene fijar los id en el código está en Segmentos.

{
  "responseId": "3f1a8b04-6d27-4c9e-9a15-2b8e7c04d913",
  "createdAt": "2026-09-07T22:47:39-03:00",
  "code": 200,
  "data": {
    "id": "9c4e1f80-5a3b-4d72-8e16-7f2a9b350c48",
    "phone": "+5491168421793",
    "email": "marina.ferreyra@gmail.com",
    "firstName": "Marina",
    "surname": "Ferreyra",
    "gender": 2,
    "dateOfBirth": "1991-04-17",
    "externalUserId": "CLI-4471",
    "createdAt": "2026-03-14T19:02:11-03:00",
    "updatedAt": "2026-09-02T10:55:17-03:00",
    "segments": [
      { "id": 2844321, "type": 2, "name": "RfmLoyal" }
    ]
  }
}

La forma del envelope (responseId, createdAt, code, data, y meta en los listados) está en Errores y Límites y paginación.

Los identificadores disponibles

IdentificadorCampoTipo¿Se puede buscar por él?
ID de cliente en Dardocustomer.idstringGET /api/v2/customers/{id}, y como customerId en tarjetas y operaciones
Teléfonocustomer.phonestring, nullable?phone= en clientes, ?customerPhone= en tarjetas
Emailcustomer.emailstring, nullable?email= en clientes, ?customerEmail= en tarjetas
ID en tu sistemacustomer.externalUserIdstring, nullableNo. Se guarda, pero no hay filtro
ID de la tarjeta (número de serie)card.idstringGET /api/v2/cards/{id}, y como cardId en operaciones
Código QR de la tarjetacard.qrLinkstringNo es un filtro de búsqueda

El ID único y estable del cliente es customer.id

Es el único campo, junto con segments, que la API garantiza en toda respuesta de cliente. Es el que usás en el path de /api/v2/customers/{id}, en el customerId de GET /api/v2/cards y de GET /api/v2/operations, y en el body de POST /api/v2/cards.

¿Es un UUID? El spec da señales cruzadas y conviene ser honesto:

DóndeCómo está tipado
CustomerOutput.idstring, sin format
Card.customerId, Operation.customerIdstring, sin format
Query customerId de /cards y /operationsuuid
customerId en los registros de automatizacionesstring, format: uuid

Los lugares que sí declaran formato dicen UUID, y los valores reales lo son. Dimensioná la columna de tu lado para un UUID (36 caracteres), pero guardalo como texto opaco: no parsees ni asumas estructura.

Sí, guarda fecha de nacimiento

En dos lugares que no hay que confundir:

Como campo del clientedateOfBirth. Sale como format: date ("1991-04-17"), entra sin formato declarado. Es opcional en todos lados.

Como campo del formulario de alta de la tarjeta — el enum de tipos de campo personalizado incluye DateOfBirth, junto con number, text, phone, email, contactEmail, url, FName, SName, date y photo. O sea: la plantilla puede pedirle la fecha al cliente cuando se registra, y ese valor termina en el campo del cliente.

Lo confirma la nota del propio spec en el alta de tarjeta: podés omitir los campos personalizados de tipo phone, email, FName, SName y DateOfBirth si el cliente ya los tiene cargados.

externalUserId: se guarda, no se busca

externalUserId existe para que escribas ahí el ID que la persona tiene en tu sistema. Se acepta en el alta y en la edición, y vuelve en toda respuesta de cliente.

GET /api/v2/customers no acepta un filtro externalUserId. Sus únicos filtros son phone, email, page e itemsPerPage. Tampoco lo acepta GET /api/v2/cards, que filtra por templateId, customerId, customerEmail y customerPhone.

Podés guardar tu ID en Dardo, pero no podés preguntarle a la API "¿qué cliente tiene el ID externo X?".

Esto es una decisión de arquitectura que tenés que tomar antes de escribir código: el mapeo ID tuyo → customer.id lo mantenés vos, de tu lado. Guardá el customer.id que devuelve el alta en tu propia base, en el momento de crearlo. Si lo perdés, la única forma de recuperarlo es buscar por teléfono o email —los dos campos que el cliente puede haber cargado mal, o no haber cargado.

externalUserId sigue siendo útil: te sirve para conciliar en la dirección inversa (mirás un cliente de Dardo y sabés a quién corresponde en tu sistema) y para auditar. Pero no reemplaza al mapeo propio.

El "ID de tarjeta de lealtad" es card.id

No existe un campo cardNumber, barcode ni number en el schema de la tarjeta. Los buscamos: no están. El identificador de la tarjeta es card.id.

El spec lo llama número de serie: la descripción de GET /api/v2/cards/{id} dice literalmente "Get card information by id (serial number)", y el filtro cardId de operaciones trae el ejemplo 430556-818-972. Es un string con guiones — no un UUID, no un autoincremental.

Es el {id} de todos los endpoints de acumulación y canje (/add-point, /add-stamp, /add-purchase, /receive-reward, /redeem-coupon, y el resto listado en Tipos de tarjeta).

Código QR y código de barras

Son dos cosas de niveles distintos:

QuéDónde viveTipoValores
qrLinkLa tarjetastring, obligatorioSin ejemplo ni descripción en el spec
appearance.barcodeTypeLa plantillastring, default pdf417pdf417 | qr

barcodeType define qué código se dibuja en el pase que el cliente lleva en la wallet, y se configura por plantilla. Es una decisión de diseño del programa, no un identificador por tarjeta. En la salida de la plantilla el campo viene tipado como string sin repetir el enum; los valores posibles son los dos de arriba.

qrLink es un enlace, no el contenido codificado del QR. El spec lo declara string obligatorio y no trae ni descripción ni ejemplo, así que no podemos afirmar qué cadena obtiene un lector cuando escanea el código impreso en el pase. Puede ser el card.id, una URL, u otra cosa.

Si tu integración depende de escanear la tarjeta para resolver de quién es —el caso típico de una caja con lector— escribinos a soporte@dardo.ai antes de diseñar ese flujo. Es el único dato de esta página que no podemos responder desde el spec.

Identificar a un cliente desde otro sistema

Hay dos caminos. El segundo casi siempre le gana al primero.

Buscar contra clientes

GET /api/v2/customers acepta phone, email, page e itemsPerPage. Te devuelve el cliente, sin la tarjeta ni el saldo.

curl -G 'https://api.dardo.ai/api/v2/customers' \
  -H 'X-API-Key: TU_CLAVE_API' \
  --data-urlencode 'email=marina.ferreyra@gmail.com'

Buscar contra tarjetas: una llamada y listo

GET /api/v2/cards acepta templateId, customerId, customerEmail y customerPhone. Devuelve la tarjeta con su saldo, el cliente embebido y sus segmentos, todo en la misma respuesta.

curl -G 'https://api.dardo.ai/api/v2/cards' \
  -H 'X-API-Key: TU_CLAVE_API' \
  --data-urlencode 'customerPhone=+5491168421793' \
  --data-urlencode 'templateId=1187'

De ahí salís con:

  • data[0].id → el número de serie, para acreditar. No es el customerId.
  • data[0].type → qué operación podés usar. Ver Tipos de tarjeta.
  • data[0].balance → el saldo actual, útil para mostrarlo o imprimirlo.
  • data[0].customer → el CustomerOutput completo, con segments.

Manejá el caso data: [] de forma explícita: es un cliente que existe pero no tiene tarjeta en esa plantilla, o directamente no existe.

El spec no declara si phone y email hacen coincidencia exacta o parcial, ni si el teléfono tiene que ir en formato internacional. Normalizá de tu lado antes de consultar —un mismo número guardado como 1168421793 y como +5491168421793 son dos cadenas distintas— y guardá el customer.id en tu base la primera vez que lo resolvés, para no depender de la búsqueda después.

Si el cliente no existe

Creá el cliente

POST /api/v2/customers con phone o email. Guardá el data.id de la respuesta en tu sistema: es el mapeo que después no vas a poder reconstruir con una consulta.

Emití la tarjeta

POST /api/v2/cards con templateId (integer, mayor a 0) y customerId (string), los dos obligatorios. Opcionalmente customFields.

Recién ahí, acreditá

Con el id de la tarjeta que devolvió el paso anterior. El flujo completo de acreditación está en Punto de venta.

Editar un cliente

PATCH /api/v2/customers/{id}. Dos cosas que hay que saber antes de escribir el cliente HTTP.

El teléfono no se puede cambiar por API. El schema de edición, comparado con el de alta, no tiene la propiedad phone. No es un olvido de esta página: la propiedad no existe en el endpoint.

Si un cliente cambió de número, la API no te da forma de corregirlo. Escribinos a soporte@dardo.ai si tu caso lo necesita.

El id va en el path y también en el body. El schema de edición declara id como campo obligatorio del cuerpo, además del {id} de la URL. El mismo valor, duplicado. No es un error de la documentación: es lo que pide el endpoint.

CampoEn el altaEn la edición
idNoSí, obligatorio
phoneNo — no existe
email
firstName
surname
gender
dateOfBirth
externalUserId
curl -X PATCH 'https://api.dardo.ai/api/v2/customers/9c4e1f80-5a3b-4d72-8e16-7f2a9b350c48' \
  -H 'X-API-Key: TU_CLAVE_API' \
  -H 'Content-Type: application/json' \
  -d '{
    "id": "9c4e1f80-5a3b-4d72-8e16-7f2a9b350c48",
    "email": "marina.ferreyra.nueva@gmail.com",
    "externalUserId": "CLI-4471"
  }'

Responde 200 con el cliente completo. DELETE /api/v2/customers/{id} responde 200 con un data reducido a { "id": "..." }.

El spec no declara la semántica de merge del PATCH: si un null explícito borra el valor o se ignora, ni qué pasa con las tarjetas al borrar un cliente. Mandá solo los campos que querés cambiar y no uses null para vaciar, hasta confirmarlo con soporte@dardo.ai.

customFields: la forma real

Los campos personalizados los define la plantilla, y se cargan al emitir o editar una tarjeta.

La referencia auto-generada muestra customFields como array<unknown>. No es que acepte cualquier cosa: al items del schema le falta el "type": "object", y varios renderers de OpenAPI se quedan sin saber qué mostrar.

La forma real es un array de objetos { id, value }.

Campo del ítemTipoQué es
idintegerID del campo del formulario, tal como lo definió la plantilla
valuestringEl valor a guardar
{
  "templateId": 1187,
  "customerId": "9c4e1f80-5a3b-4d72-8e16-7f2a9b350c48",
  "customFields": [
    { "id": 3312, "value": "Sucursal Centro" }
  ]
}

Qué mandar: los campos que define la plantilla, salvo (a) los que no son obligatorios y (b) los de tipo phone, email, FName, SName y DateOfBirth cuando el cliente ya los tiene cargados.

En la edición de tarjeta (PATCH /api/v2/cards/{id}), customFields es la única propiedad del cuerpo y, si la mandás, tiene que traer al menos un ítem.

En la respuesta la forma es otra

Cuando la tarjeta te vuelve, cada customField trae más campos que los dos del envío:

CampoTipoNullableSiempre presente
idintegerNo
namestringNo
typestringNo
orderintegerNo
valuestringNo
requiredbooleanNo
uniquebooleanNo

No devuelvas ese objeto completo como cuerpo de un envío. El endpoint solo lee id y value; el resto es informativo.

Un detalle del listado de tarjetas

Estos campos de la tarjeta vienen siempre vacíos cuando pedís una lista, según la descripción del propio spec: countVisits, totalRewardsRedeemed, totalRewardsEarned, totalStampsEarnedByReferral, totalPointsEarnedByReferral, lastRewardRedeemedAt, lastRewardEarnedAt y lastStampEarnedAt.

Si los necesitás, tenés que pedir la tarjeta de a una con GET /api/v2/cards/{id}. Para reportería sobre muchas tarjetas conviene ir por Operaciones en vez de recorrer tarjetas de a una.

Ver también

On this page