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

# What's new

> Changes to the TapiPay API contract: first the ones that can affect your integration, then the fixes.

<Update label="YYYY-MM-DD" description="API contract revision">
  This version makes input validation stricter and fixes several responses so they match what is documented. Review the list of changes that can affect your integration first.

  ## Changes that can affect your integration

  These are changes where a request that used to pass (or returned `5xx`) now returns `4xx`, or where a response value your code might be reading changes.

  * **Strict booleans** in `POST /v2/debts` and `POST`/`PATCH /subscriptions`: `allowOverduePayment`, `allowPartialPayments`, `autopay`, and `enforcePaymentOrder` only accept `true`/`false`. `"true"`, `1`, etc. return `400` with `details[]`. `description` must be a string (`400`). See [API conventions](/en/concepts/api-conventions#strict-types).
  * **`additionalData`** in `POST /v2/debts`, `POST /payment-links`, and `POST`/`PATCH /subscriptions`: it must be a flat object and cannot use reserved keys (`description`, `paymentMethods`, `productName`, etc.). Otherwise, `400`. See [API conventions](/en/concepts/api-conventions#additionaldata).
  * **`POST /payment-links`**: an `externalClientId` that does not match a contact returns `422 BUSINESS_RULE_VIOLATION` (the link used to be created anyway). See [Payment links](/en/resources/payment-links).
  * **`POST`/`PATCH /payment-links`**: `expiresAt` must be an RFC 3339 date-time (`2026-12-31 23:59` or a date alone return `400`). In the `PATCH`, `expiresAt: null` returns `400` (it used to be `500`; links always expire), `metadata` `null` or not an object returns `400`, and an empty `description` returns `400`. See [Payment links](/en/resources/payment-links).
  * **`PATCH /subscriptions/{id}`**: validates types, the `paymentMethods` enum, and the exclusion between `autopay: true` and `paymentMethods` (`400`). A `CANCELLED` or `ENDED` subscription returns `422` (it used to be `200`). See [Subscriptions](/en/resources/subscriptions).
  * **`POST /subscriptions`**: `intervalUnit` is case-sensitive (`MONTH` returns `400`) and, if the scheduling is invalid, neither the product nor the inline contact is created. See [Subscriptions](/en/resources/subscriptions).
  * **`PATCH /contacts/{id}`**: `phones: []` returns `400` (it used to be `200` with no effect). See [Contacts](/en/resources/contacts).
  * **`GET /contacts`**: `identifier` and `externalClientId` with different values return `400`. See [Contacts](/en/resources/contacts).
  * **`GET /products`**: an `active` value other than `true`/`false` returns `400` (it used to be read as `false`). See [Products](/en/resources/products).
  * **`GET /payment-links`**: `createdAtFrom`/`createdAtTo` with an invalid format return `400` (they used to be compared as text). See [Payment links](/en/resources/payment-links).
  * **`GET /subscriptions`**: `externalClientId` now filters (it used to be ignored and returned subscriptions of other contacts). Empty or blank returns `400`. See [Subscriptions](/en/resources/subscriptions).
  * **`GET /debts/{id}`, `PATCH /debts/{id}`, `POST /debts/{id}/cancel`**: a canceled debt can be read (`200`, `status: CANCELLED`); canceling it again or sending a `PATCH` returns `422` (it used to be `404`). See [Debts](/en/resources/debts).
  * **`POST /v2/debts`**: new value `autopayReason: "NO_PRODUCT"`. If you validate `autopayReason` against a closed list, add it. See [Direct debit](/en/resources/direct-debit).
  * **Dates in contacts, debts, and link payments**: `date-time` values are returned in UTC with milliseconds and `Z` (`2026-09-24T21:09:41.835Z`). They used to come without a zone, with `+00:00`, or with microseconds. See [API conventions](/en/concepts/api-conventions#dates).
  * **`PATCH /debts/{id}`**: `dueDate` in the response becomes `YYYY-MM-DD` (it used to be a date-time). See [Debts](/en/resources/debts).
  * **`POST /payment-links` without a debtor**: `paymentUrl` becomes the short URL (it no longer contains the `identifierValue`) and can be `null` if its generation fails. Treat it as an opaque URL. See [Payment links](/en/resources/payment-links).
  * **Payment links, every response**: `expiresAt` is always returned in UTC with milliseconds and `Z` (`2026-12-31T23:59:59.000Z`), even if it was sent with an offset (`-06:00`). Older links stored with a date alone (`2026-12-01`) come out as the start of that UTC day (`2026-12-01T00:00:00.000Z`). See [Payment links](/en/resources/payment-links).
  * **Payment links without an external key, all responses**: `externalPaymentLinkId` is `null` when the link was created without `externalPaymentLinkId` or an `Idempotency-Key` header (it used to return a `plk_<uuid>` value equal to the `paymentLinkId`). See [Payment links](/en/resources/payment-links#idempotency).
  * **`GET /debts` and `GET /payment-links/{id}/payments`**: `createdAtTo` as a date alone now includes that day (it used to filter up to its start). If you sent the next day to compensate, you now get one extra day. `createdAtFrom` later than `createdAtTo` returns `400` with the message `createdAtFrom must be earlier than or equal to createdAtTo.` See [Debts](/en/resources/debts).

  ## Fixes

  * **`PATCH /debts/{id}` and `PATCH /subscriptions/{id}`**: the `PATCH` no longer resets `allowPartialPayments` to `false`, and `amountPaid` comes out correctly (not `null`). See [Debts](/en/resources/debts).
  * **`GET /debts/{id}` and `GET /debts`**: `description` appears in reads. See [Debts](/en/resources/debts).
  * **`POST /v2/debts` and `POST /subscriptions`**: `contact.contactId` comes with the `con_` prefix also when the contact is created inline. See [Direct debit](/en/resources/direct-debit).
  * **`GET /contacts`**: `identifier` filters, and `contactCode` accepts the `con_` prefix. See [Contacts](/en/resources/contacts).
  * **Contacts**: contacts without an email created inline return `email: null` (it used to be the text `"null"`), also when reading existing ones. See [Contacts](/en/resources/contacts).
  * **`GET /payment-links?externalPaymentLinkId=`**: no longer returns `500`. See [Payment links](/en/resources/payment-links).
  * **`POST /payment-links`**: a repeated `externalPaymentLinkId` returns `409` without creating the debt. A link with a debtor no longer fails with `500` if the portal configuration does not respond. See [Payment links](/en/resources/payment-links).
  * **`PATCH /payment-links/{id}`**: `expiresAt` also updates the due date of the link's debt. If that debt was already cancelled, only the link is updated (the `PATCH` used to fail). See [Payment links](/en/resources/payment-links).
  * **Payment link response**: always carries `externalPaymentLinkId`, `expiresAt`, `successUrl`, `metadata`, `identifierValue`, `contact`, and `product`, with `null` when there is no value. See [Payment links](/en/resources/payment-links).
  * **`POST /products`**: returns what was stored (timestamps, `externalProductId`, `active`) instead of echoing the request. See [Products](/en/resources/products).
  * **`PATCH /products/{id}`**: `active: false` deactivates the product; renaming to a name already in use, including one used by an inactive product, returns `409` (it used to be `500`); invalid types return `400` (they used to be `500`). See [Products](/en/resources/products).
  * **`POST /subscriptions`**: `paymentUrl` is no longer `null`, and `scheduling.billingDay` is inferred from `startDate` (for `month` and `year`). See [Subscriptions](/en/resources/subscriptions).
  * **`GET /debts` and `GET /payment-links/{id}/payments`**: `createdAtFrom`/`createdAtTo` accept an RFC 3339 date-time in addition to a date. The same date in both returns that day's debts (it used to be `400`), and a future `createdAtFrom` without `createdAtTo` returns an empty page (it used to be `400`). See [Debts](/en/resources/debts).
  * **`PATCH` of contacts, products, payment links, subscriptions, and debts**: `companyCode` in the body is taken as context and is no longer rejected as a non-editable field. See [API conventions](/en/concepts/api-conventions#companycode).
  * **`POST /v2/debts` (batch)**: each error in `details[]` carries `field`. See [Direct debit](/en/resources/direct-debit).
  * **`PATCH /debts/{id}`**: impossible dates (`2026-02-30`) return `400`. See [Debts](/en/resources/debts).
  * **`POST /v2/debts`, `POST /payment-links`, and subscriptions**: `additionalData` sent as an object is stored on the debt (it used to be lost in debts and links). See [API conventions](/en/concepts/api-conventions#additionaldata).
  * **`PATCH /subscriptions/{id}`**: if updating one of the subscription's debts fails, the response is `207` with `error.code: PARTIAL_UPDATE` (it used to be `500 INTERNAL_ERROR`). The subscription is updated. See [Subscriptions](/en/resources/subscriptions#update-a-subscription).
  * **`paymentUrl` in debts, subscriptions and payment links**: if your company has no private links configuration, `paymentUrl` is no longer `null`: it returns your company's portal URL. Still treat it as an opaque URL. See [Debts](/en/resources/debts).
</Update>
