Product) is used to group and categorize your charges (debts, subscriptions, and payment links). Its use is completely optional: you can charge without associating any product.
You use it when you want to report your charges by product or service line, for example “Plan Premium”, “Gym monthly fee”, or “Internet 100MB”.
Natural key: name
The name is the product’s natural key and is unique per company. If an active product with that name already exists, POST /products answers 201 with the existing product, unchanged: it does not generate a duplicate or an error.
That is why
POST /products is idempotent by design: repeating the same name returns the same product, as it is stored (any other values you send in the body are ignored). The exception is externalProductId: if the existing product already has a different externalProductId, the API answers 409 RESOURCE_CONFLICT.Create a product
externalProductId, and active), not the request.
The
productId is the product’s public ID, with the prd_ prefix (e.g. prd_12985). In the path of GET, PATCH, and DELETE /products/{id} the number without prefix (12985) is also accepted; other prefixes or non-numeric values return 404 RESOURCE_NOT_FOUND.string
Your own identifier for the product (optional). It is what you later use to reference it when creating debts, without having to store Tapi’s
productId. It does not accept the characters ', ", \, or ; (returns 400).Validations
nameandexternalProductIdare trimmed before being stored.- Fields not declared in the body are ignored.
- If the
externalProductIdis already used by another product, or thenamealready exists with a differentexternalProductId, the API answers409 RESOURCE_CONFLICT(with the same message in both cases).
Clients with several companies
If your TapiPay client has a single company, there is nothing to indicate: everything you create belongs to it. If your client manages several companies (for example, a network of schools where each school is a Tapi company), tell us which one the product belongs to withcompanyCode in the body:
name and externalProductId are unique per company, so the same name can exist in each of your companies without conflict. To list the products of one company use GET /products?companyCode=MX-S-04937.
string
Tapi company (
MX-S-…) the product is created in. Optional if your client has a single company. It must belong to your client.This rule applies to the whole API, not just products: a client with several companies sends
companyCode on every request (in the body for POST/PATCH, or as ?companyCode= for GET). In a PATCH, the companyCode in the body is taken as context, not as a field to modify. See API conventions. When creating debts with POST /v2/debts, the product is looked up inside the request’s company: a product from one company cannot be used in a debt of another.Referencing a product in a debt
When creating a debt withPOST /v2/debts, the product must already exist and is referenced by productId (format prd_<number>; any other format returns 400 INVALID_REQUEST) or externalProductId (only one; sending both returns 400). The debts endpoint does not create products: sending productName returns 400 INVALID_REQUEST, and referencing a nonexistent product returns 404 RESOURCE_NOT_FOUND without creating the debt.
Inline creation (productName)
In subscriptions and payment links you can send the product inline with the productName field: if the name does not exist, it is created; if it already exists, it is reused without duplicating it.
createdInline field is true if the product was created in that same request, and false if it already existed and was reused. For debts it is always false.
List products
GET /products returns only active products, sorted by productId descending.
Pagination uses
page (default 1) and limit (default 50, maximum 500). See API conventions.
Update
PATCH /products/{id} accepts only two fields:
string
New name, non-empty string. If another product already uses it, even an inactive one, returns
409 RESOURCE_CONFLICT.boolean
Only
true or false. active: false deactivates the product just like DELETE. active: true changes nothing: a product you can read is already active, and an inactive one returns 404. It is accepted so you can send the full object.400 INVALID_REQUEST. Invalid types (for example "active": "no") return 400 with the detail in details[]. The companyCode in the body is taken as context, not as a field to modify.
Deactivate
boolean
default:"true"
Status of the product.
DELETE /products/{id} performs a soft delete: it sets active: false without deleting the product, so the history is preserved. A DELETE on an already deleted product returns 404 RESOURCE_NOT_FOUND.
Endpoints
Try it in the API Reference
Explore each endpoint with its interactive playground.

