> ## 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.

# Direct debit

> Collect your debts automatically with each user's default payment method, using POST /v2/debts.

TapiPay can collect your debts through bank direct debit **without declaring a payment method on each debt**: collection is governed by the **default payment method** each end user registers once. This flow lives in `POST /v2/debts`.

## How it works

The model relies on three pieces:

<CardGroup cols={3}>
  <Card title="Products" icon="box" href="/en/resources/products">
    Every debt belongs to a product (for example "Tuition"). Direct debit is managed per product: when a user enrolls, they enroll the whole product.
  </Card>

  <Card title="Default payment method" icon="credit-card">
    The user registers their account once through the TapiPay [enrollment page](/en/payment-portal/autopay-sdk) (not via API). That method becomes the default and executes collections.
  </Card>

  <Card title="Debts v2" icon="file-invoice-dollar">
    You create debts via API referencing the product. The response tells you whether automatic collection is active and why.
  </Card>
</CardGroup>

<Note>
  On `POST /v2/debts` the debt **does not declare a payment method**: the `paymentMethods` and `autopay` fields are not accepted and return `400 INVALID_REQUEST` (they only apply to [subscriptions](/en/resources/subscriptions)). If the user has a debitable default, the adhesion is managed automatically on every debt creation.
</Note>

## Prerequisites

* Your company must have the default payment method feature enabled (configured with your Tapi account executive).
* Your products must exist before creating debts. You can create them with your own identifier (`externalProductId`) so you never store Tapi IDs.

## Create a debt

Product identity travels through **exactly one** of these two fields (never both):

| Field               | Type   | Description                                                             |
| ------------------- | ------ | ----------------------------------------------------------------------- |
| `productId`         | string | Tapi product ID, format `prd_<number>` (any other format returns `400`) |
| `externalProductId` | string | Your own product identifier (does not accept `'`, `"`, `\`, `;`)        |

The rest of the contract matches [creating a debt](/en/resources/debts): amount, due date, debtor.

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

```json 201 response theme={null}
{
  "data": {
    "debtId": "debt_TUITION-OCT-00042",
    "externalRequestId": "TUITION-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=TUITION-OCT-00042",
    "createdAt": "2026-09-17T10:00:00.000Z",
    "allowOverduePayment": true,
    "allowPartialPayments": true,
    "description": "October tuition",
    "contact": {
      "contactId": "con_cmruu1lx2000002l4ctm7b0et",
      "externalClientId": "CLI-00042",
      "createdInline": false
    },
    "product": {
      "productId": "prd_7",
      "name": "Tuition",
      "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` only appears in the response when `autopay` is `false` (the rejection reason). When `autopay` is `true`, the key is absent. `autopayInstrument` only appears when `autopay` is `true`.
</Note>

<Warning>
  `productName` is not accepted on `POST /v2/debts`: products are never created from this endpoint. Sending it returns `400 INVALID_REQUEST`. The `paymentMethods`, `paymentMethod`, and `autopay` fields are not accepted either (`400 INVALID_REQUEST`): the method used for automatic collection is always defined by the user's default, never by the debt body. In general, `POST /v2/debts` rejects any field outside its contract with a `400` that names the field in `details[].field`. See the accepted fields in [Debts](/en/resources/debts#main-fields).
</Warning>

## Create several debts in one request

The same endpoint accepts up to **50 debts** per request through the `debts` envelope. Each item has the same contract as a single debt plus a **mandatory `externalRequestId`, unique within the batch**: it is the idempotency key of each debt (the `Idempotency-Key` header is not used in this mode).

If the body carries `debts`, only `debts` is accepted at the top level: any other root field returns `400`. A JSON array at the root, without the `{ "debts": [...] }` envelope, also returns `400`.

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

How it behaves:

| Situation                                                                                      | Response                                                                                                                          |
| ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| Any invalid item (format, field outside the contract, missing or repeated `externalRequestId`) | `400 INVALID_REQUEST`. Each `details[]` entry carries `index`, `externalRequestId`, `field`, and `issue`. **No debt is created.** |
| Every debt created                                                                             | `201` with `data` as an array of debts, in the order sent                                                                         |
| Some created, some failed                                                                      | `207` with `data` (the created ones) and `errors[]` with `{ index, externalRequestId, code, message }`                            |
| None could be created                                                                          | `422` with an empty `data` and every item in `errors[]`                                                                           |

<Note>
  Debts in a batch are created **independently**: one failing in the collections system does not stop the others. The `code` values in `errors[]` are the same the endpoint returns for a single debt. An `externalRequestId` that already existed returns the existing debt without duplicating it.
</Note>

<Tip>
  When several debts in the batch belong to the same debtor or product, that information is resolved once for the whole request. Grouping a user's debts in the same batch makes the request faster.
</Tip>

## The automatic collection outcome

Every response includes `autopay` and, when it is `false`, the reason in `autopayReason`:

| `autopay` | `autopayReason`                                | Meaning                                                                                                     |
| --------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `true`    | *(absent)*                                     | The adhesion request was emitted (processed asynchronously); the debt is debited automatically              |
| `false`   | `DEFAULT_PAYMENT_METHODS_DISABLED_FOR_COMPANY` | Your company does not have the feature enabled                                                              |
| `false`   | `NO_DEFAULT_PAYMENT_METHOD`                    | The user has not registered a payment method yet                                                            |
| `false`   | `DEFAULT_PAYMENT_METHOD_TYPE_NOT_SUPPORTED`    | The user's default is not debitable (for example, a wallet)                                                 |
| `false`   | `COMPANY_WITHOUT_PRODUCT_MODALITY`             | Your company has no product modality configured for direct debit                                            |
| `false`   | `ADHESION_EVENT_FAILED`                        | Transient failure emitting the adhesion request; it is retried when the next debt of the product is created |
| `false`   | `NO_PRODUCT`                                   | The debt does not reference a product: direct debit is managed per product, so it is not evaluated          |

<Tip>
  The debt is **always created**, with or without automatic collection. `autopay: false` is not an error: that debt is payable through the traditional methods (its `paymentUrl`) until the user enrolls. When the next debt of the product is created, the adhesion resumes on its own.
</Tip>

`autopayInstrument` is a **reference** to the instrument: sensitive data never travels (the bank account number is neither exposed nor stored by this service).

## Query a user's payment methods

`GET /payment-methods` lists the references of the methods a user registered. `externalClientId` is required, must be exactly the same identifier used at enrollment, and does not accept the characters `'`, `"`, `\`, `;`. The response is not paginated.

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

```json 200 response 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` can be:

| Value          | Method                         | Executes debit      |
| -------------- | ------------------------------ | ------------------- |
| `BANK_ACCOUNT` | Bank account (direct debit)    | Yes                 |
| `YUNO`         | Card tokenized at the provider | Not in this version |

If the `externalClientId` has no registered methods or does not exist, the response is `200` with `data: []`. Besides the `400` for a missing or invalid `externalClientId`, the endpoint can respond `404 RESOURCE_NOT_FOUND` or `422 BUSINESS_RULE_VIOLATION`.

## Rules to keep in mind

* **One default per user**: the first registered method becomes the default automatically; later ones do not displace it unless explicitly requested during enrollment.
* **Nonexistent product**: a `productId` or `externalProductId` that does not exist returns `404 RESOURCE_NOT_FOUND` and the debt is not created.
