> ## Documentation Index
> Fetch the complete documentation index at: https://devs.tapipay.la/llms.txt
> Use this file to discover all available pages before exploring further.

# Débito automático

> Cobra tus deudas automáticamente con el medio de pago default de cada usuario, usando POST /v2/debts.

TapiPay puede cobrar tus deudas por domiciliación bancaria **sin que declares un medio de pago en cada deuda**: el cobro lo gobierna el **medio de pago default** que cada usuario final registra una sola vez. Este flujo vive en `POST /v2/debts`.

## Cómo funciona

El modelo se apoya en tres piezas:

<CardGroup cols={3}>
  <Card title="Productos" icon="box" href="/es/recursos/productos">
    Cada deuda pertenece a un producto (por ejemplo "Colegiatura"). La domiciliación se gestiona por producto: al enrolarse, el usuario se adhiere al producto completo.
  </Card>

  <Card title="Medio de pago default" icon="credit-card">
    El usuario registra su cuenta una sola vez en la [página de enrolamiento](/es/portal-de-pagos/sdk-adhesiones) de TapiPay (no por API). Ese medio queda como default y ejecuta los cobros.
  </Card>

  <Card title="Deudas v2" icon="file-invoice-dollar">
    Creas las deudas por API referenciando el producto. La respuesta te dice si el cobro automático quedó activo y por qué.
  </Card>
</CardGroup>

<Note>
  En `POST /v2/debts` la deuda **no declara medio de pago**: los campos `paymentMethods` y `autopay` no se aceptan y devuelven `400 INVALID_REQUEST` (solo aplican a [suscripciones](/es/recursos/suscripciones)). Si el usuario tiene un default debitable, la adhesión se gestiona sola en cada creación de deuda.
</Note>

## Requisitos previos

* Tu compañía debe tener habilitado el medio de pago default (se configura con tu ejecutivo de Tapi).
* Tus productos deben existir antes de crear deudas. Puedes crearlos con tu propio identificador (`externalProductId`) para no guardar IDs de Tapi.

## Crear una deuda

La identidad del producto viaja por **uno solo** de estos dos campos (nunca ambos):

| Campo               | Tipo   | Descripción                                                                   |
| ------------------- | ------ | ----------------------------------------------------------------------------- |
| `productId`         | string | ID del producto en Tapi, formato `prd_<número>` (otro formato devuelve `400`) |
| `externalProductId` | string | Tu identificador del producto (no admite `'`, `"`, `\`, `;`)                  |

El resto del contrato es el mismo de [crear una deuda](/es/recursos/deudas): monto, vencimiento, deudor.

```bash theme={null}
curl --request POST \
  --url 'https://tapipay-facade.homo.tapila.cloud/v2/debts' \
  --header 'x-api-key: TU_API_KEY' \
  --header 'x-authorization-token: TU_TOKEN_TAPI' \
  --header 'Idempotency-Key: COLE-OCT-00042' \
  --header 'Content-Type: application/json' \
  --data '{
    "externalClientId": "CLI-00042",
    "externalProductId": "COLEGIATURA-2026",
    "amount": 1500.00,
    "dueDate": "2026-10-05",
    "description": "Colegiatura octubre"
  }'
```

```json Respuesta 201 theme={null}
{
  "data": {
    "debtId": "debt_COLE-OCT-00042",
    "externalRequestId": "COLE-OCT-00042",
    "status": "PENDING",
    "amount": 1500.00,
    "amountPaid": 0,
    "currency": "MXN",
    "dueDate": "2026-10-05",
    "paymentUrl": "https://app.tapipay.la/s/acme-corp/portal/aB3dE5fG7h/debts/payment-methods?externalRequestId=COLE-OCT-00042",
    "createdAt": "2026-09-17T10:00:00.000Z",
    "allowOverduePayment": true,
    "allowPartialPayments": true,
    "description": "Colegiatura octubre",
    "contact": {
      "contactId": "con_cmruu1lx2000002l4ctm7b0et",
      "externalClientId": "CLI-00042",
      "createdInline": false
    },
    "product": {
      "productId": "prd_7",
      "name": "Colegiatura",
      "active": true,
      "createdInline": false
    },
    "autopay": true,
    "autopayInstrument": {
      "paymentMethodId": "8b2e2a1e-1111-4222-8333-444455556666",
      "paymentMethodType": "BANK_ACCOUNT",
      "isDefault": true,
      "status": "ACTIVE",
      "createdAt": "2026-09-10T14:32:00.000Z"
    }
  },
  "requestId": "req_a1b2c3d4e5f6"
}
```

<Note>
  `autopayReason` solo aparece en la respuesta cuando `autopay` es `false` (el motivo del rechazo). Cuando `autopay` es `true`, la clave no está presente. `autopayInstrument` solo aparece cuando `autopay` es `true`.
</Note>

<Warning>
  `productName` no se acepta en `POST /v2/debts`: los productos nunca se crean desde este endpoint. Enviarlo devuelve `400 INVALID_REQUEST`. Los campos `paymentMethods`, `paymentMethod` y `autopay` tampoco se aceptan (`400 INVALID_REQUEST`): el medio con el que se cobra automáticamente siempre lo define el default del usuario, nunca el body de la deuda. En general, `POST /v2/debts` rechaza con `400` cualquier campo fuera de su contrato e indica el campo en `details[].field`. Ver los campos aceptados en [Deudas](/es/recursos/deudas#campos-principales).
</Warning>

## Crear varias deudas en un request

El mismo endpoint acepta hasta **50 deudas** por request enviando el sobre `debts`. Cada ítem tiene el mismo contrato que una deuda suelta más un `externalRequestId` **obligatorio y único dentro del lote**: es la idempotencia de cada deuda (el header `Idempotency-Key` no se usa en este modo).

Si el body trae `debts`, solo se acepta `debts` en el nivel superior: cualquier otro campo en la raíz devuelve `400`. Un array JSON en la raíz, sin el sobre `{ "debts": [...] }`, también devuelve `400`.

```bash theme={null}
curl --request POST \
  --url 'https://tapipay-facade.homo.tapila.cloud/v2/debts' \
  --header 'x-api-key: TU_API_KEY' \
  --header 'x-authorization-token: TU_TOKEN_TAPI' \
  --header 'Content-Type: application/json' \
  --data '{
    "debts": [
      { "externalRequestId": "COLE-OCT-00042", "externalClientId": "CLI-00042", "externalProductId": "COLEGIATURA-2026", "amount": 1500.00, "dueDate": "2026-10-05" },
      { "externalRequestId": "COLE-OCT-00043", "externalClientId": "CLI-00043", "externalProductId": "COLEGIATURA-2026", "amount": 1500.00, "dueDate": "2026-10-05" }
    ]
  }'
```

Cómo se comporta:

| Situación                                                                                        | Respuesta                                                                                                                              |
| ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| Algún ítem inválido (formato, campo fuera del contrato, `externalRequestId` faltante o repetido) | `400 INVALID_REQUEST`. Cada entrada de `details[]` trae `index`, `externalRequestId`, `field` e `issue`. **No se crea ninguna deuda.** |
| Todas las deudas creadas                                                                         | `201` con `data` como array de deudas, en el orden enviado                                                                             |
| Algunas creadas y otras fallaron                                                                 | `207` con `data` (las creadas) y `errors[]` con `{ index, externalRequestId, code, message }`                                          |
| Ninguna pudo crearse                                                                             | `422` con `data` vacío y todos los ítems en `errors[]`                                                                                 |

<Note>
  Las deudas del lote se crean de forma **independiente**: una que falle en el sistema de cobranzas no frena a las demás. Los `code` de `errors[]` son los mismos que devolvería el endpoint para una deuda suelta. Un `externalRequestId` que ya existía devuelve la deuda existente sin duplicarla.
</Note>

<Tip>
  Cuando varias deudas del lote son del mismo deudor o del mismo producto, esa información se resuelve una sola vez para todo el request. Agrupar las deudas de un mismo usuario en el mismo lote hace el request más rápido.
</Tip>

## El resultado del cobro automático

Cada respuesta incluye `autopay` y, cuando es `false`, el motivo en `autopayReason`:

| `autopay` | `autopayReason`                                | Significado                                                                                                 |
| --------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `true`    | *(ausente)*                                    | La solicitud de adhesión fue emitida (se procesa de forma asíncrona); la deuda se debita automáticamente    |
| `false`   | `DEFAULT_PAYMENT_METHODS_DISABLED_FOR_COMPANY` | Tu compañía no tiene la función habilitada                                                                  |
| `false`   | `NO_DEFAULT_PAYMENT_METHOD`                    | El usuario todavía no registró su medio de pago                                                             |
| `false`   | `DEFAULT_PAYMENT_METHOD_TYPE_NOT_SUPPORTED`    | El default del usuario no es debitable (por ejemplo, wallet)                                                |
| `false`   | `COMPANY_WITHOUT_PRODUCT_MODALITY`             | Tu compañía no tiene configurada la modalidad de productos para domiciliación                               |
| `false`   | `ADHESION_EVENT_FAILED`                        | Fallo transitorio al emitir la solicitud de adhesión; se reintenta al crear la siguiente deuda del producto |
| `false`   | `NO_PRODUCT`                                   | La deuda no referencia un producto: la domiciliación se gestiona por producto, así que no se evalúa         |

<Tip>
  La deuda **siempre se crea**, tenga o no cobro automático. `autopay: false` no es un error: esa deuda se paga por los medios tradicionales (su `paymentUrl`) hasta que el usuario se enrole. Al crear la siguiente deuda del producto, la adhesión se retoma sola.
</Tip>

`autopayInstrument` es una **referencia** al instrumento: nunca viajan datos sensibles (la CLABE no se expone ni se almacena en este servicio).

## Consultar los medios de pago de un usuario

`GET /payment-methods` lista las referencias de los medios que registró un usuario. `externalClientId` es obligatorio, debe ser exactamente el mismo identificador del enrolamiento y no admite los caracteres `'`, `"`, `\`, `;`. La respuesta no está paginada.

```bash theme={null}
curl --request GET \
  --url 'https://tapipay-facade.homo.tapila.cloud/payment-methods?externalClientId=CLI-00042' \
  --header 'x-api-key: TU_API_KEY' \
  --header 'x-authorization-token: TU_TOKEN_TAPI'
```

```json Respuesta 200 theme={null}
{
  "data": [
    {
      "paymentMethodId": "8b2e2a1e-1111-4222-8333-444455556666",
      "paymentMethodType": "BANK_ACCOUNT",
      "isDefault": true,
      "status": "ACTIVE",
      "createdAt": "2026-09-10T14:32:00.000Z"
    }
  ],
  "requestId": "req_a1b2c3d4e5f6"
}
```

`paymentMethodType` puede ser:

| Valor          | Medio                              | Ejecuta débito     |
| -------------- | ---------------------------------- | ------------------ |
| `BANK_ACCOUNT` | Cuenta bancaria (domiciliación)    | Sí                 |
| `YUNO`         | Tarjeta tokenizada en el proveedor | No en esta versión |

Si el `externalClientId` no tiene medios registrados o no existe, la respuesta es `200` con `data: []`. Además del `400` por `externalClientId` faltante o inválido, el endpoint puede responder `404 RESOURCE_NOT_FOUND` o `422 BUSINESS_RULE_VIOLATION`.

## Reglas a tener en cuenta

* **Un solo default por usuario**: el primer medio registrado queda como default automáticamente; los siguientes no lo desplazan salvo pedido explícito durante el enrolamiento.
* **Producto inexistente**: un `productId` o `externalProductId` que no existe devuelve `404 RESOURCE_NOT_FOUND` y la deuda no se crea.
