# Suscriptores (Customers) --- - [Suscriptores (Customers)](#suscriptores) - [¿Qué es un suscriptor?](#overview) - [Listar suscriptores](#get-all) - [Buscar un suscriptor](#get) - [Cuándo deja de servir un `subscriber_id`](#vigencia) - [Crear suscriptor](#create) - [Actualizar suscriptor](#update) - [Metadata](#metadata) - [Eliminar suscriptor](#delete) - [Alias `customer`](#alias) ## ¿Qué es un suscriptor? [#overview] Un **suscriptor** es tu cliente dentro de nuestro sistema: guarda su identidad y sus datos de facturación, y es a quien después le asocias un [plan](/plan) para crear la [suscripción](/subscription). Créalo una sola vez y reutilízalo: un mismo suscriptor puede tener varias suscripciones y varias tarjetas guardadas, con una marcada como predeterminada (`default_payment_method`). :::note **Nombre alternativo (estilo Stripe).** Cada ruta de esta página existe también bajo `customer`, con el mismo comportamiento y la misma respuesta: | Nombre original | Alias | | - | - | | `GET /api/v1/subscriptions/subscriber` | `GET /api/v1/subscriptions/customer` | | `GET /api/v1/subscriptions/subscriber/{emailOrId}` | `GET /api/v1/subscriptions/customer/{emailOrId}` | | `POST /api/v1/subscriptions/subscriber` | `POST /api/v1/subscriptions/customer` | | `PUT /api/v1/subscriptions/subscriber/{id}` | `PUT /api/v1/subscriptions/customer/{id}` | | `DELETE /api/v1/subscriptions/subscriber/{id}` | `DELETE /api/v1/subscriptions/customer/{id}` | ::: :::caution El correo es único **por sucursal**, no por comercio. El mismo cliente en dos sucursales son dos suscriptores distintos. ::: ## Listar suscriptores [#get-all] **Descripción:** Devuelve todos los suscriptores de las sucursales a las que tienes acceso. `GET /api/v1/subscriptions/subscriber` :::caution Este listado **no está paginado**, **no viene envuelto en `data`** y **no separa por ambiente**: es el arreglo completo de suscriptores, en `snake_case`. Si tienes muchos clientes, la respuesta puede ser grande. ::: `GET /api/v1/subscriptions/subscriber` Puedes filtrar por tu [metadata](#metadata) con coincidencia exacta: `GET /api/v1/subscriptions/subscriber?filter[metadata][crm_id]=C-881`. ```bash curl -X GET \ '/api/v1/subscriptions/subscriber' \ -H 'Authorization: Bearer ACCESS_TOKEN' \ -H 'Accept: application/json' ``` :::tip Respuesta satisfactoria ::: Código de respuesta: 200 ```json [ { "id": "9b0f2a13-2c44-4a1a-9f2e-1d3b4c5d6e7f", "identification_type": "CC", "id_number": "1020304050", "name": "Ana", "last_name": "Gómez", "email": "ana@ejemplo.com", "phone_code": 57, "cellphone_number": "3001234567", "billing_address": "Calle 100 # 20-30", "billing_city": "Bogotá", "billing_country": "Colombia", "metadata": { "crm_id": "C-881" }, "office_id": 1, "commerce_id": 42, "created_at": "2026-07-31 10:15:00" } ] ``` ## Buscar un suscriptor [#get] **Descripción:** Devuelve un suscriptor por su **id**, su **correo** o su **número de documento**. El mismo endpoint acepta las tres cosas, así que no necesitas guardar nuestro id si ya tienes cualquiera de los otros dos. `GET /api/v1/subscriptions/subscriber/{emailOrId}` | Puedes buscar por | Ejemplo | | - | - | | Id (uuid) | `/subscriber/9b0f2a13-2c44-4a1a-9f2e-1d3b4c5d6e7f` | | Correo | `/subscriber/ana@ejemplo.com` | | Documento | `/subscriber/1020304050` | :::note La búsqueda queda acotada a tu comercio y a las sucursales a las que tenga acceso el usuario del token. Un documento que exista en otro comercio devuelve `404`. ::: `GET /api/v1/subscriptions/subscriber/ana@ejemplo.com` ```bash curl -X GET \ '/api/v1/subscriptions/subscriber/ana@ejemplo.com' \ -H 'Authorization: Bearer ACCESS_TOKEN' \ -H 'Accept: application/json' ``` :::tip Respuesta satisfactoria ::: Código de respuesta: 200 ```json { "id": "9b0f2a13-2c44-4a1a-9f2e-1d3b4c5d6e7f", "identification_type": "CC", "id_number": "1020304050", "name": "Ana", "last_name": "Gómez", "email": "ana@ejemplo.com", "phone_code": 57, "cellphone_number": "3001234567", "billing_address": "Calle 100 # 20-30", "billing_city": "Bogotá", "billing_country": "Colombia", "default_payment_method_id": "9b0f2a13-2c44-4a1a-9f2e-1d3b4c5d6e81", "balance": 0, "currency": "COP", "active": true, "office_id": 1, "commerce_id": 42, "created_at": "2026-07-31 10:15:00" } ``` :::danger No existe, o no pertenece a una sucursal a la que tengas acceso ::: Código de respuesta: 404 ## Cuándo deja de servir un `subscriber_id` [#vigencia] Un id que guardaste y que funcionaba puede empezar a devolver `422` al [crear una suscripción](/subscription#create). Hay **tres** causas y cada una tiene su propio mensaje en `errors.subscriber_id`: | Mensaje | Qué pasó | Cómo se arregla | | - | - | - | | «El suscriptor pertenece a la sucursal **N** y estás enviando `office=M`» | El suscriptor vive en otra sede | Manda el `office` de **su** sede, o crea el suscriptor en la sede que estás usando | | «El suscriptor fue eliminado» | Alguien lo borró. La fila sigue existiendo, por eso el id "parece" válido | Créalo de nuevo | | «El suscriptor pertenece a otro comercio» | El token es de otro comercio | Revisa el token | | «No existe un suscriptor con ese id» | El id nunca existió | Revisa el id | :::danger **La causa más común es la sucursal.** El suscriptor y el plan deben ser de la **misma sede** que el `office` que mandas al crear la suscripción. Los tres tienen que coincidir; si no, el `422` llega aunque el id sea correcto. ::: Lo que **no** invalida un suscriptor: - **El paso del tiempo.** Un `subscriber_id` no caduca. - **Tokenizar o cambiar la tarjeta.** Las tarjetas cuelgan del suscriptor; cambiarlas no lo toca. - **El ambiente.** A diferencia de planes y suscripciones, los suscriptores **no** tienen eje prueba/producción: el mismo suscriptor sirve para ambos. :::tip Si dudas de un id guardado, confírmalo antes de cobrar con `GET /api/v1/subscriptions/subscriber/{id}`. Un `200` te devuelve además su `office_id`, que es el que debes mandar como `office`. ::: ## Crear suscriptor [#create] **Descripción:** Registra un cliente. Los datos de facturación son obligatorios porque viajan a la red en cada cobro recurrente. `POST /api/v1/subscriptions/subscriber` | Nombre del campo | Descripción | Reglas | | - | - | - | | identification_type | Tipo de documento. Ver [enumeraciones](/resources) | `['required', 'string', 'in:CC,CE,TI,PPT,DNI,NIT,Pasaporte,Otro']` | | id_number | Número de documento. **No puede ser un número de tarjeta**: lo rechazamos a propósito | `['required', 'numeric', 'digits_between:5,15']` | | name | Nombres del cliente | `['required', 'string', 'max:255']` | | last_name | Apellidos del cliente | `['required', 'string', 'max:255']` | | email | Correo del cliente. Único dentro de la sucursal | `['required', 'email', 'max:255', 'unique:subscribers,email']` | | phone_code | Indicativo del país, sin `+` (Colombia: `57`) | `['required', 'integer', 'min_digits:1', 'max_digits:3']` | | cellphone_number | Celular. Se valida como número real del país que resulte de `phone_code` | `['required', 'numeric', 'phone']` | | billing_address | Dirección de facturación | `['required', 'string', 'max:255']` | | billing_city | Ciudad de facturación | `['required', 'string', 'max:255']` | | billing_country | País de facturación. Ver [lista de países](/resources) | `['required', 'string', 'max:255']` | | office | Sucursal a la que pertenece el suscriptor. Debe ser una de [tus sucursales](/commercio) | `['required', 'exists:offices,id']` | | metadata | Tus pares clave-valor. Ver [Metadata](#metadata) | `['sometimes', 'nullable', 'metadata']` | `POST /api/v1/subscriptions/subscriber` Cuerpo de ejemplo: ```json { "identification_type": "CC", "id_number": "1020304050", "name": "Ana", "last_name": "Gómez", "email": "ana@ejemplo.com", "phone_code": "57", "cellphone_number": "3001234567", "billing_address": "Calle 100 # 20-30", "billing_city": "Bogotá", "billing_country": "Colombia", "office": 1 } ``` ```bash curl -X POST \ '/api/v1/subscriptions/subscriber' \ -H 'Authorization: Bearer ACCESS_TOKEN' \ -H "Content-type: application/json" \ -d '{ "identification_type": "CC", "id_number": "1020304050", "name": "Ana", "last_name": "Gómez", "email": "ana@ejemplo.com", "phone_code": "57", "cellphone_number": "3001234567", "billing_address": "Calle 100 # 20-30", "billing_city": "Bogotá", "billing_country": "Colombia", "office": 1 }' ``` :::tip Suscriptor creado ::: Código de respuesta: 201 ```json { "saved": true, "subscriber": { "id": "9b0f2a13-2c44-4a1a-9f2e-1d3b4c5d6e7f", "name": "Ana", "lastName": "Gómez", "email": "ana@ejemplo.com", "phoneCode": 57, "cellphoneNumber": "3001234567", "billingAddress": "Calle 100 # 20-30", "billingCity": "Bogotá", "billingCountry": "Colombia", "metadata": null, "active": true, "subscriptionsCount": 0, "activeSubscriptionsCount": 0, "inactiveSubscriptionsCount": 0, "createdAt": "2026-07-31 10:15:00" } } ``` :::danger Correo repetido en la misma sucursal ::: Código de respuesta: 422 ```json { "message": "El campo email ya está en uso.", "errors": { "email": ["El campo email ya está en uso."] }, "error": { "type": "invalid_request_error", "code": "validation_failed", "message": "El campo email ya está en uso.", "param": "email" } } ``` ## Actualizar suscriptor [#update] **Descripción:** Cambia los datos de un suscriptor. **Envía solo los campos que quieres cambiar**; los que omitas se quedan como estaban. El tipo y número de documento no se modifican, ni la sucursal. `PUT /api/v1/subscriptions/subscriber/{subscriber-id}` | Nombre del campo | Descripción | Reglas | | - | - | - | | name | Nombres del cliente | `['sometimes', 'required', 'string', 'max:255']` | | last_name | Apellidos del cliente | `['sometimes', 'required', 'string', 'max:255']` | | email | Nuevo correo. Único dentro de la misma sucursal, ignorando a este suscriptor | `['sometimes', 'required', 'email', 'max:255', 'unique:subscribers,email']` | | phone_code | Indicativo del país | `['sometimes', 'required', 'integer', 'min_digits:1', 'max_digits:3']` | | cellphone_number | Celular. Aquí **no** se valida contra el formato del país, a diferencia de crear | `['sometimes', 'required', 'numeric']` | | billing_address | Dirección de facturación | `['sometimes', 'required', 'string', 'max:255']` | | billing_city | Ciudad de facturación | `['sometimes', 'required', 'string', 'max:255']` | | billing_country | País de facturación | `['sometimes', 'required', 'string', 'max:255']` | | metadata | Se **combina** con la existente. Una clave con `""` o `null` se borra; `metadata: null` borra todo. Ver [Metadata](#metadata) | `['sometimes', 'nullable', 'metadata']` | `PUT /api/v1/subscriptions/subscriber/your-subscriber-id` Cuerpo de ejemplo: ```json { "email": "ana.gomez@ejemplo.com", "cellphone_number": "3009876543" } ``` ```bash curl -X PUT \ '/api/v1/subscriptions/subscriber/9b0f2a13-2c44-4a1a-9f2e-1d3b4c5d6e7f' \ -H 'Authorization: Bearer ACCESS_TOKEN' \ -H "Content-type: application/json" \ -d '{ "email": "ana.gomez@ejemplo.com", "cellphone_number": "3009876543" }' ``` :::tip Respuesta satisfactoria ::: Código de respuesta: 200 ```json { "saved": true, "subscriber": { "id": "9b0f2a13-2c44-4a1a-9f2e-1d3b4c5d6e7f", "name": "Ana", "lastName": "Gómez", "email": "ana.gomez@ejemplo.com", "cellphoneNumber": "3009876543", "subscriptionsCount": 2, "activeSubscriptionsCount": 1, "inactiveSubscriptionsCount": 1, "createdAt": "2026-07-31 10:15:00" } } ``` ## Metadata [#metadata] `metadata` guarda tus propias referencias del cliente (id en tu CRM, segmento…), al estilo de Stripe. Mismas reglas que en [suscripciones](/subscription#metadata): | Límite | Valor | | - | - | | Claves | Hasta 50, de 1 a 40 caracteres `[A-Za-z0-9_-]` | | Valor | Texto o número, hasta 500 caracteres. Se guarda como texto | | Al actualizar | Las claves se combinan; `""` o `null` borra una clave; `metadata: null` borra todo | Llega en los `GET`, en el listado y en los [webhooks de suscripción](/subscription-webhook), dentro de `subscription.subscriber.metadata`. Un valor inválido responde `422` con `error.code: "validation_failed"` y `error.param: "metadata"`. ## Eliminar suscriptor [#delete] **Descripción:** Elimina un suscriptor. `DELETE /api/v1/subscriptions/subscriber/{subscriber-id}` :::caution No podrás eliminar un suscriptor que tenga una suscripción activa. Cancélala primero, o déjala terminar. ::: `DELETE /api/v1/subscriptions/subscriber/your-subscriber-id` :::tip Respuesta satisfactoria ::: Código de respuesta: 200 ```json { "deleted": true } ``` :::danger El suscriptor tiene una suscripción activa ::: Código de respuesta: 400 ```json { "message": "Este suscriptor no puede ser eliminado ya que tiene una suscripción activa" } ``` ## Alias `customer` [#alias] Las mismas operaciones, con el nombre de Stripe. Comportamiento, parámetros y respuestas son idénticos a los de arriba; solo cambia el segmento de la ruta. **Listar** — `GET /api/v1/subscriptions/customer` `GET /api/v1/subscriptions/customer` **Buscar por correo o id** — `GET /api/v1/subscriptions/customer/{emailOrId}` `GET /api/v1/subscriptions/customer/ana@ejemplo.com` **Crear** — `POST /api/v1/subscriptions/customer` `POST /api/v1/subscriptions/customer` Cuerpo de ejemplo: ```json { "identification_type": "CC", "id_number": "1020304050", "name": "Ana", "last_name": "Gómez", "email": "ana@ejemplo.com", "phone_code": "57", "cellphone_number": "3001234567", "billing_address": "Calle 100 # 20-30", "billing_city": "Bogotá", "billing_country": "Colombia", "office": 1 } ``` **Actualizar** — `PUT /api/v1/subscriptions/customer/{customer-id}` `PUT /api/v1/subscriptions/customer/your-subscriber-id` Cuerpo de ejemplo: ```json { "email": "ana.gomez@ejemplo.com", "cellphone_number": "3009876543" } ``` **Eliminar** — `DELETE /api/v1/subscriptions/customer/{customer-id}` `DELETE /api/v1/subscriptions/customer/your-subscriber-id`