SimplePuntos

Desarrolladores / Referencia

Referencia de la API

Los ejemplos usan un negocio de muestra. Ya conectado, el panel de cada negocio muestra esta misma referencia con sus reglas.

Para empezar

La API habla JSON y todas sus rutas cuelgan de esta dirección. Si es la primera vez que conectas una caja, empieza por la guía paso a paso.

https://simplepuntos.com/api/v1

Cada petición lleva una llave de API en el encabezado Authorization. Las llaves se crean en Configuración › Conexiones; usa una por caja para poder revocar una sin afectar a las demás.

Authorization: Bearer pts_…

Para programar y probar, crea una llave de prueba (empieza con pts_test_). Trabaja con una copia de las reglas de Café Brisa: no ve a sus clientes reales, el código de canje siempre es 000000 y no manda WhatsApp ni correos. Cuando la caja esté lista, cámbiala por una llave normal.

  • Los sellos viajan como texto decimal con 0 decimales ("120"); los pesos siempre con dos ("60.25"). Al mandarlos puedes usar texto o número, sin más decimales de esos.
  • Las fechas van en ISO 8601 con zona horaria y los ids son UUID.
  • Hasta 300 peticiones por minuto por llave; arriba de eso responde 429.
  • Manda el cuerpo como JSON con Content-Type: application/json. Los errores siempre responden JSON, también un cuerpo mal formado o una ruta que no existe (mira errores).
  • Los ejemplos usan variables de la terminal: $LLAVE para tu llave y $CLIENTE, $CANJE y $MOVIMIENTO para los ids que te devuelve la API.
  • Si prefieres importarla en Postman o generar tu cliente, la misma API está descrita en OpenAPI.

Leer las reglas del programa

GET/program

Lo que tu caja necesita para calcular y mostrar antes de cobrar, tal como está hoy en la configuración de Café Brisa. Léelo al arrancar la caja en vez de copiar las reglas a mano: si el dueño las cambia, la caja se entera sola. Lo que no aplica a la tarjeta de sellos viene en null.

  • stamps_per_card: los sellos de una tarjeta; stamp_rewards: en qué sello se gana cada premio.
  • points_expire_after_months: los meses sin compras tras los que vence el saldo; null si no vence.
  • identifier: con qué se identifica al cliente (phone, email o text) y, si hay, el patrón que debe cumplir.
  • sandbox: true mientras la caja use una llave de prueba; muéstralo en pantalla para que nadie cobre con ella.
curl https://simplepuntos.com/api/v1/program \
  -H "Authorization: Bearer $LLAVE"
{
  "business": "Café Brisa",
  "sandbox": false,
  "program": "sellos",
  "points_label": "sellos",
  "points_precision": 0,
  "points_per_100": null,
  "point_value": null,
  "otp": null,
  "points_expire_after_months": null,
  "stamps_per_card": 10,
  "stamp_rewards": [
    {
      "stamps": 5,
      "reward": "Bebida al 50%"
    },
    {
      "stamps": 10,
      "reward": "Bebida gratis"
    }
  ],
  "identifier": {
    "kind": "phone",
    "label": "Celular",
    "pattern": null
  },
  "portal_url": "https://simplepuntos.com/c/tu-negocio"
}

Escanear la tarjeta

En Café Brisa cada cliente se identifica por su celular (teléfono celular). Es único por cliente: con él se busca, se acumula y se canjea.

  1. El lector de código de barras o QR funciona como un teclado: escribe el identificador y manda Enter.
  2. Deja el cursor en un campo de búsqueda; al recibir el Enter, el punto de venta llama POST /members/lookup con lo que leyó.
  3. La respuesta trae el id del cliente: con él van todas las demás rutas (/members/{id}/earnings, /members/{id}/redemptions…).
  4. Si responde 404, el cliente no está registrado: ofrécele registrarse con POST /members.

El celular viaja en el cuerpo de la petición y nunca en la dirección, para que no quede escrito en los registros de servidores y proxies. El teléfono se acepta tal como lo teclea el cajero: 229 123 4567 encuentra a +522291234567.

Clientes

Buscar un cliente

POST/members/lookup

Devuelve sus datos, su metadata y los sellos que lleva en su tarjeta (balance); balance_amount viene vacío porque los sellos no valen pesos.

curl -X POST https://simplepuntos.com/api/v1/members/lookup \
  -H "Authorization: Bearer $LLAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "identifier": "229 123 4567"
  }'
{
  "id": "0199a1b2-3c4d-7e5f-8a9b-0c1d2e3f4a5b",
  "identifier": "+522291234567",
  "name": "María López",
  "email": "maria@correo.mx",
  "phone": "+522291234567",
  "metadata": {
    "mascota": "Firulais",
    "sucursal": "Riviera"
  },
  "balance": "4",
  "balance_amount": null,
  "created_at": "2026-09-14T11:32:08.120-06:00",
  "updated_at": "2026-10-01T18:05:44.310-06:00"
}

Registrar un cliente

POST/members

Responde 201 con el cliente nuevo. Si el identificador ya existía, actualiza el nombre, fusiona la metadata y responde 200; su teléfono y su correo se quedan como estaban. En la metadata guarda lo que te ayude a reconocerlo: su mascota, su sucursal, su número de cliente.

curl -X POST https://simplepuntos.com/api/v1/members \
  -H "Authorization: Bearer $LLAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "identifier": "+522291234567",
    "name": "María López",
    "email": "maria@correo.mx",
    "metadata": {
      "mascota": "Firulais",
      "sucursal": "Riviera"
    }
  }'

Consultar un cliente

GET/members/{id}

Lo mismo que la búsqueda, con el id que ya tienes; sirve para refrescar el saldo antes de cobrar.

curl https://simplepuntos.com/api/v1/members/$CLIENTE \
  -H "Authorization: Bearer $LLAVE"

Actualizar un cliente

PATCH/members/{id}

Cambia el nombre y la metadata, que se fusiona con la que ya tenía; una llave con null se borra. El identifier, el teléfono y el correo no cambian por la API porque deciden a quién le llega el código de cada canje: si mandas uno distinto responde 422 contact_locked y no guarda nada. Esos cambios se hacen en el panel y quedan marcados en la bitácora.

curl -X PATCH https://simplepuntos.com/api/v1/members/$CLIENTE \
  -H "Authorization: Bearer $LLAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "María López Ruiz",
    "metadata": {
      "sucursal": null,
      "especie": "Perro"
    }
  }'

Movimientos de un cliente

GET/members/{id}/entries

Del más nuevo al más viejo. limit da el tamaño de página (25 por omisión, máximo 100); para la siguiente, manda en before el next_before de la respuesta. Cuando next_before viene vacío ya no hay más.

curl https://simplepuntos.com/api/v1/members/$CLIENTE/entries?limit=10 \
  -H "Authorization: Bearer $LLAVE"

Acumular

Acumular

POST/members/{id}/earnings

Manda el monto de la compra en amount, o points: 1 si tu caja no lo tiene: cada acumulación sella una visita en la tarjeta del cliente, sin importar el monto. Si con ese sello gana un premio, llega en rewards para imprimirlo en el ticket. Si con él llena la tarjeta, card_completed viene en true: balance_after dice el sello que la llenó y balance el saldo que quedó, 0, porque empieza una tarjeta nueva. Imprime balance. Uno de los dos es obligatorio. reference es el folio del ticket (mira reintentos seguros); description y metadata son opcionales y quedan guardados en el movimiento. Responde 201 con el movimiento, el saldo que dejó (balance_after) y el saldo actual del cliente (balance).

curl -X POST https://simplepuntos.com/api/v1/members/$CLIENTE/earnings \
  -H "Authorization: Bearer $LLAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "850.00",
    "reference": "riviera-3001",
    "description": "Venta 3001"
  }'
{
  "id": "0199a1b5-77e0-7c21-b0f4-5d6e7f8a9b0c",
  "member_id": "0199a1b2-3c4d-7e5f-8a9b-0c1d2e3f4a5b",
  "member_identifier": "+522291234567",
  "kind": "earn",
  "points": "1",
  "balance_after": "5",
  "source": "api",
  "reference": "riviera-3001",
  "description": "Venta 3001",
  "metadata": {},
  "reversed_entry_id": null,
  "redemption_id": null,
  "created_at": "2026-10-02T13:41:09.502-06:00",
  "replayed": false,
  "rewards": [
    {
      "id": "0199a1d2-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
      "code": "K7QM3XPA",
      "title": "Bebida al 50%",
      "source": "stamps",
      "status": "available",
      "member_id": "0199a1b2-3c4d-7e5f-8a9b-0c1d2e3f4a5b",
      "member_identifier": "+522291234567",
      "expires_on": null,
      "used_at": null,
      "used_by": null,
      "reference": null,
      "created_at": "2026-10-02T13:41:09.502-06:00"
    }
  ],
  "balance": "5",
  "card_completed": false
}

En la tarjeta de sellos no hay ajustes: /adjustments responde 422 not_adjustable. Una visita sellada por error se corrige revirtiendo su movimiento (mira corregir una visita).

Consultar un movimiento

GET/entries/{id}

curl https://simplepuntos.com/api/v1/entries/$MOVIMIENTO \
  -H "Authorization: Bearer $LLAVE"

Corregir una visita

Si una visita se selló por error (otro cliente, dos sellos por una visita), revierte su movimiento: el cliente pierde ese sello. Si con él había ganado un premio que todavía no usa, el premio se cancela y llega en voided_rewards para que la caja lo anule; si ya lo usó, responde 422 not_reversible con su código y no revierte nada.

La visita que llenó la tarjeta se revierte mientras la tarjeta nueva siga vacía: vuelve a quedar a un sello de llenarse y su premio se cancela. Si la nueva ya lleva sellos, revierte primero esas visitas.

Buscar movimientos por referencia

GET/entries?reference={folio}

Si tu caja no guardó el id del movimiento, búscalo por el folio con el que lo mandó. Responde la lista como los movimientos de un cliente, con next_before para paginar; vacía si nadie usó esa referencia.

curl https://simplepuntos.com/api/v1/entries?reference=riviera-3001 \
  -H "Authorization: Bearer $LLAVE"
{
  "entries": [
    {
      "id": "0199a1b5-77e0-7c21-b0f4-5d6e7f8a9b0c",
      "member_id": "0199a1b2-3c4d-7e5f-8a9b-0c1d2e3f4a5b",
      "member_identifier": "+522291234567",
      "kind": "earn",
      "points": "1",
      "balance_after": "5",
      "source": "api",
      "reference": "riviera-3001",
      "description": "Venta 3001",
      "metadata": {},
      "reversed_entry_id": null,
      "redemption_id": null,
      "created_at": "2026-10-02T13:41:09.502-06:00"
    }
  ],
  "next_before": null
}

Revertir un movimiento

POST/entries/{id}/reverse

curl -X POST https://simplepuntos.com/api/v1/entries/$MOVIMIENTO/reverse \
  -H "Authorization: Bearer $LLAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "reference": "barra-dev-3001",
    "description": "Se selló al cliente equivocado"
  }'
{
  "id": "0199a1e4-2c3d-7e4f-8a5b-6c7d8e9f0a1b",
  "member_id": "0199a1b2-3c4d-7e5f-8a9b-0c1d2e3f4a5b",
  "member_identifier": "+522291234567",
  "kind": "reversal",
  "points": "-1",
  "balance_after": "4",
  "source": "api",
  "reference": "barra-dev-3001",
  "description": "Se selló al cliente equivocado",
  "metadata": {},
  "reversed_entry_id": "0199a1b5-77e0-7c21-b0f4-5d6e7f8a9b0c",
  "redemption_id": null,
  "created_at": "2026-10-02T14:02:51.330-06:00",
  "replayed": false,
  "requested_points": "1",
  "reversed_points": "1",
  "voided_rewards": [
    {
      "id": "0199a1d2-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
      "code": "K7QM3XPA",
      "title": "Bebida al 50%",
      "source": "stamps",
      "status": "cancelled",
      "member_id": "0199a1b2-3c4d-7e5f-8a9b-0c1d2e3f4a5b",
      "member_identifier": "+522291234567",
      "expires_on": null,
      "used_at": null,
      "used_by": null,
      "reference": null,
      "created_at": "2026-10-02T13:41:09.502-06:00"
    }
  ]
}

Premios y cupones

En Café Brisa cada acumulación sella una visita en una tarjeta de 10 sellos: en el sello 5 el cliente gana «Bebida al 50%» y en el sello 10 el cliente gana «Bebida gratis»; al llenarla empieza otra. Lo que gana con una visita llega en rewards de esa misma respuesta, para imprimirlo en el ticket. Cada premio de la tarjeta de sellos y cada cupón de una campaña trae un código de 8 caracteres, único en el negocio, que el cliente muestra o dicta en caja.

  1. Al escanear la tarjeta, GET /members/{id}/rewards da los premios y cupones que puede usar.
  2. Para usar uno, manda su código a POST /rewards/{código}/use y aplica el premio en la venta.

Premios y cupones disponibles

GET/members/{id}/rewards

Solo los que todavía se pueden usar, del más nuevo al más viejo.

curl https://simplepuntos.com/api/v1/members/$CLIENTE/rewards \
  -H "Authorization: Bearer $LLAVE"
{
  "member_id": "0199a1b2-3c4d-7e5f-8a9b-0c1d2e3f4a5b",
  "member_identifier": "+522291234567",
  "rewards": [
    {
      "id": "0199a1d2-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
      "code": "K7QM3XPA",
      "title": "Bebida al 50%",
      "source": "stamps",
      "status": "available",
      "member_id": "0199a1b2-3c4d-7e5f-8a9b-0c1d2e3f4a5b",
      "member_identifier": "+522291234567",
      "expires_on": null,
      "used_at": null,
      "used_by": null,
      "reference": null,
      "created_at": "2026-10-02T13:41:09.502-06:00"
    }
  ]
}

Usar un premio o cupón

POST/rewards/{código}/use

El código se acepta con o sin guion y en minúsculas: k7qm-3xpa encuentra a K7QM3XPA. Cada uno se usa una sola vez. Repetir con la misma reference responde 200 con replayed: true; si ya se había usado de otra forma responde 409 already_used, y si venció, 422 expired.

curl -X POST https://simplepuntos.com/api/v1/rewards/K7QM3XPA/use \
  -H "Authorization: Bearer $LLAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "reference": "riviera-3002"
  }'
{
  "id": "0199a1d2-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
  "code": "K7QM3XPA",
  "title": "Bebida al 50%",
  "source": "stamps",
  "status": "used",
  "member_id": "0199a1b2-3c4d-7e5f-8a9b-0c1d2e3f4a5b",
  "member_identifier": "+522291234567",
  "expires_on": null,
  "used_at": "2026-10-02T13:52:40.118-06:00",
  "used_by": "Llave «Caja Riviera»",
  "reference": "riviera-3002",
  "created_at": "2026-10-02T13:41:09.502-06:00",
  "replayed": false
}
CódigoHTTPCuándo
not_found 404 No hay ningún premio o cupón con ese código en tu negocio.
already_used 409 El premio o cupón ya se usó, con otra referencia o sin ella, o se canceló al revertir la visita que lo dio.
expired 422 Pasó su fecha de vencimiento.

Reintentos seguros

Las redes fallan. Si una petición se cortó y no sabes si llegó, repítela igual, con la misma reference: si ya existía, la API responde lo mismo que la primera vez con replayed: true (200, o 202 si es un canje que sigue pendiente), sin acumular dos veces. Vale para acumulaciones y reversos; manda siempre reference.

La referencia es única por tipo de movimiento. Usa el folio con un prefijo por sucursal (riviera-3001, coyol-3001) y no lo reinicies: si tu caja reinicia folios por día o por terminal, agrégale la fecha o la caja (riviera-20261002-3001). Si repites una referencia con otro movimiento u otro cliente, responde 409 reference_taken con cuándo y con cuánto se usó, y no mueve nada. Como cada visita suma un sello, un folio repetido para el mismo cliente siempre se toma como reintento.

Errores

Todo error responde JSON con la misma forma, también un cuerpo que no es JSON válido o una ruta que la API no tiene: un code estable para tu programa y un message en español para mostrarle al cajero.

{
  "error": {
    "code": "reference_taken",
    "message": "La referencia «riviera-3001» ya se usó el 02/10/2026 a las 13:41 con 1 sellos"
  }
}
CódigoHTTPCuándo
bad_request 400 Falta un parámetro obligatorio, el cuerpo no es JSON válido, mandaste amount y points juntos o un reverso trae una llave que no conoce.
unauthorized 401 Falta la llave o fue revocada.
not_found 404 El cliente, movimiento o canje no existe en tu negocio, la ruta no existe en la API o el cursor before no está en la lista.
reference_taken 409 La referencia ya se usó en ese tipo de movimiento con otros sellos u otro cliente; el mensaje dice cuándo y con cuánto.
invalid 422 Los datos tienen errores; details trae los mensajes por campo.
contact_locked 422 Intentaste cambiar el identificador, el teléfono o el correo; eso solo se hace en el panel.
not_adjustable 422 La tarjeta de sellos no tiene ajustes; una visita se corrige revirtiéndola.
not_reversible 422 La visita ya se revirtió, su premio ya se usó o llenó una tarjeta que ya lleva sellos.
rate_limited 429 Más de 300 peticiones por minuto con la misma llave.
¿Dudas? Escríbenos