Skip to main content
A debt (Debt) is a one-time charge to a known debtor: a fixed amount charged to an identified person or entity, with a due date and configurable payment methods. It is the most common use case of the API. You specify who pays, how much, and when it is due. TapiPay resolves all of the complexity internally (your organization, the modalities, and the internal data of the collections system) and returns a clean contract with the payment URL ready to share.

When to use a debt

Debt

A single charge to someone you know: an invoice, an installment, a one-time service.

Subscription

If the charge repeats over time (monthly payments, installments).

Payment link

If you do not know the debtor in advance (donations, one-off sales).

Create a debt

To create a debt you need, at a minimum, to identify the debtor, the amount, and the due date. The optional Idempotency-Key header is used as the debt’s externalRequestId, and the resulting debtId is debt_<key> (in the example, debt_INV-2026-001). If you don’t send it, the backend generates the externalRequestId. See Idempotency.

Identify the debtor

You must send one of these two fields (not both, not neither): See Contacts for details on the reusable debtor.
A contact may have more than one identifier. If you reference it by a secondary one, the debt is created with its primary identifier and contact.externalClientId in the response returns that value, not the one you sent.

Main fields

number
required
Amount in pesos, with up to 2 decimal places (minimum 0.01). For example, 1500.50 equals $1,500.50 MXN.
string
required
Due date in YYYY-MM-DD format. It must be a valid calendar date (2026-02-30 returns 400). It is not validated to be in the future.
string
default:"MXN"
Currency code. Allowed values: MXN, ARS, PEN, COP, CLP, USD. Default MXN.
string
Description visible to the debtor. It must be a string (any other type returns 400).
string
ID of an existing Tapi product, in prd_<number> format (for example, prd_12985). Any other format returns 400 INVALID_REQUEST. Mutually exclusive with externalProductId (sending both returns 400); never creates a product. See Products.
string
Your identifier of an existing product. Mutually exclusive with productId; never creates a product. It does not accept the characters ', ", \, ; (400).
boolean
default:"true"
Enables partial payments. With true, the debt moves to PARTIALLY_PAID until it is settled. Only the literal true or false is accepted: "true", 1, etc. return 400 with details[].
boolean
default:"true"
If false, the debt cannot be paid after the dueDate. Only the literal true or false is accepted, same as allowPartialPayments.
object
Data of your own system that you want to store on the debt. It must be a flat object (a scalar, such as a string or a number, returns 400) and it cannot use the reserved keys TapiPay writes internally: description, paymentMethods, productName, externalProductId, debtExpirationDate, overduePayment, allowPartialPayments, recurringDebt, debtReference. If any of them is present, the API responds 400 INVALID_REQUEST with details[].field = additionalData.
POST /v2/debts only accepts these body fields: externalClientId, contactData, amount, currency, dueDate, description, allowOverduePayment, allowPartialPayments, additionalData, productId, externalProductId. Any other one, including autopay, paymentMethods, productName, and paymentMethod, returns 400 INVALID_REQUEST with the field in details[].field. A JSON array at the root (without the { "debts": [...] } envelope) also returns 400.
The debt does not declare a payment method: automatic collection is governed by the default payment method each user registers once. Referencing a product (productId/externalProductId) is what enables that. The paymentMethods and autopay fields only apply to subscriptions. See Direct debit for the full picture.
You never send or receive internal system data (organization, modalities, generationData). You work only with business concepts.

The response

The creation and query response includes paymentUrl (the URL ready to share with your debtor; it is null only if the portal integration fails) and amountPaid (the accumulated amount paid). The creation response also includes autopay (whether automatic collection is active) and, when it is false, the reason in autopayReason. If the debt does not reference a product, the reason is NO_PRODUCT. See Direct debit.
  • debtId is always debt_ + externalRequestId.
  • description is returned on creation and on reads (GET /debts/{id}, GET /debts) when the debt has one; otherwise the field is absent.
  • contact.contactId carries the con_ prefix, also when the contact is created inline.
  • date-time values come in UTC with milliseconds and Z. See API conventions.
The paymentUrl is a deep link that points directly to that specific debt. It is an opaque URL: share it as-is with your debtor, and don’t parse it or build it by hand (its format may change). Debts generated by a subscription also carry their own deep link (you see it in GET /subscriptions/{id}/debts).

Lifecycle

Retrieve and list

GET /debts/{id} accepts the debtId (debt_...) or the externalRequestId without prefix. It also returns cancelled debts (status: CANCELLED). GET /debts returns your debts from newest to oldest, paginated with page and limit (see API conventions). It does not include cancelled debts: to read a cancelled one, use GET /debts/{id}.
  • They accept a date (YYYY-MM-DD) or an RFC 3339 date-time. Any other format returns 400.
  • A date alone covers the full UTC day, included at both ends: createdAtFrom=2026-09-24 starts at 00:00:00.000Z and createdAtTo=2026-09-24 ends at 23:59:59.999Z. With the same date in both, you get the debts of that day.
  • A date-time is converted to UTC and the filter has daily precision: the full UTC day that contains it is included. A createdAtTo at exactly 00:00:00Z does not include that day.
  • Without createdAtTo, the range runs until today. A future createdAtFrom without createdAtTo returns an empty list.
  • createdAtFrom later than createdAtTo returns 400.

Update and cancel

You can only update a debt in PENDING status. Attempting it in another status, including a cancelled debt, returns 422 BUSINESS_RULE_VIOLATION.Editable fields: amount and dueDate. Send at least one: an empty body or any other field returns 400 INVALID_REQUEST. dueDate uses the YYYY-MM-DD format and must be a valid calendar date. companyCode in the body is taken as context, not as a field to modify.The PATCH does not modify allowPartialPayments. The response carries dueDate as YYYY-MM-DD and amountPaid with the current amount paid.
You can cancel a debt in PENDING, PARTIALLY_PAID, or OVERDUE status. You cannot cancel a debt that is already PAID or CANCELLED: cancelling it a second time returns 422 BUSINESS_RULE_VIOLATION.The endpoint takes no body (if you send one, it is ignored). The cancelled debt can still be read with GET /debts/{id}, but it no longer appears in GET /debts.

Errors

On top of the general catalog, these are the most common error causes in the debt endpoints and the error.code each one returns. The error.message is informational and may change: branch your logic on the status, error.code, and error.details[].field, not on the text. Some real messages, for reference only: When the failure is not a validation one, the API keeps the real status instead of always turning it into a 500, so you can also get a 409 RESOURCE_CONFLICT or a 422 BUSINESS_RULE_VIOLATION outside the cases in the table. These errors carry no error.details, because they do not point at a specific field of your payload.

Endpoints

Try it in the API Reference

Explore each endpoint with its interactive playground.