Subscription) is a recurring charge that automatically generates a series of debts, one per cycle. You define what to charge (amount, frequency, debtor, and duration) and TapiPay generates the cycles and resolves all of the internal complexity for you.
When to use it: charges that repeat over time (monthly payments, credit installments, periodic plans). For a single charge use a debt; to charge without a known debtor, use a payment link.
Upfront generation of cycles
When you create the subscription, TapiPay calculates the dates of all cycles and generates the debts upfront (there is no process that creates one cycle per month). The generation of those debts happens asynchronously: the subscription is returned inACTIVE status as soon as the calculation and queuing finish successfully.
If the generation of the debts fails, the subscription is automatically reverted so that you can retry with the same
externalSubscriptionId without duplicating charges.Create a subscription
Main fields
string
required
Natural key of the subscription. Unique per organization. If you repeat it, the API responds
409 RESOURCE_CONFLICT. See Idempotency.string | object
required
The debtor. One of the two is required and they are mutually exclusive (do not send both), just like in a debt. In v1 each subscription has exactly one debtor.
number
required
Amount per cycle in pesos, with up to 2 decimal places (minimum
0.01).string
required
Interval unit:
day, week, month, or year, in exact lowercase. It is case-sensitive: MONTH returns 400.integer
required
Number of units per cycle, an integer greater than or equal to
1. For example, intervalUnit: "month" + intervalCount: 3 is quarterly.string
required
Start date in
YYYY-MM-DD format (a valid calendar date).string
required
Name of the product being charged. If the name already exists in your organization, it is reused; otherwise the product is created inline. See Inline creation.
integer
Number of cycles to generate, from
1 to 520. Mutually exclusive with endDate.string
End date (
YYYY-MM-DD), later than startDate. Mutually exclusive with totalCycles.object
Override of the billing day:
{ day, month? }. Only valid with month or year intervals, and month (from 1 to 12) only with year. See below.boolean
default:"false"
If
true, the debtor must pay the cycles in chronological order.boolean
default:"true"
If
false, the cycles cannot be paid after their due date.boolean
default:"true"
Enables partial payments on the generated debts.
boolean
default:"false"
Requests autopay for the cycles.
autopay: true and paymentMethods are mutually exclusive: sending both returns 400.string[]
Payment methods enabled for the cycles. Mutually exclusive with
autopay: true. See Payment methods.string
Description of the subscription. Must be a string (otherwise
400).object
Data from your own system. Must be a flat object (a scalar returns
400) and cannot use reserved keys (description, paymentMethods, productName, externalProductId, debtExpirationDate, overduePayment, allowPartialPayments, recurringDebt, debtReference); otherwise 400. It is stored on the generated debts.Validation rules
- Booleans (
autopay,allowOverduePayment,allowPartialPayments,enforcePaymentOrder) only accept a literaltrueorfalse:"true",1, etc. return400withdetails[]. - Undeclared fields are ignored.
- If the schedule (
intervalUnit,intervalCount,startDate,totalCycles,endDate,billingDay) is invalid, the API responds400and nothing is created: not the subscription, the product, or the inline contact.
Idempotency
Subscription creation does not use theIdempotency-Key header. The idempotency key is externalSubscriptionId: repeating it returns 409 RESOURCE_CONFLICT and no new subscription is created. The generated debts use externalRequestId = <externalSubscriptionId>-cycle-<n>, with n starting at 0 (the debt of the first cycle is -cycle-0).
Frequencies with intervalUnit + intervalCount
Billing day (billingDay)
By default, the billing day is inferred from the startDate (anniversary model): if the subscription starts on the 15th, all charges fall on the 15th. The billingDay override decouples the billing day from the startDate.
In the response, scheduling.billingDay always shows the effective billing day: if you did not send it, it is inferred from the startDate as { day } for month and { day, month } for year. For day and week it is null.
End of month: if the configured day exceeds the last day of the month (for example, 31 in February), the charge is made on the last day of the month; the following month returns to the original day.
Indefinite duration
If you do not sendtotalCycles or endDate, the subscription stays ACTIVE and only the debt for the first cycle is generated (to avoid generating charges without limit). Define totalCycles or endDate when you want to generate all cycles upfront.
The response
The scheduling data travels nested in thescheduling object.
description,additionalData, andpaymentMethodsare only present when they have a value.scheduling.totalCyclesandscheduling.endDateare always present, withnullwhen they do not apply.paymentUrlis the portal URL shared by all cycles. It is an opaque URL: do not parse it or build it by hand. To open a specific cycle, use thepaymentUrlof each debt inGET /subscriptions/{id}/debts.contact.contactIdalways has thecon_prefix, also when the contact was created inline withcontactData.createdInlineoncontactandproductindicates whether they were created inline when the subscription was created, and it is kept in later reads.
Lifecycle
Each debt generated by the subscription has its own lifecycle (
PENDING, PAID, etc.). Pausing or cancelling the subscription cancels its pending debts. See Pause, resume, and cancel and Debts.Payment order (enforcePaymentOrder)
With enforcePaymentOrder: true, the debtor cannot pay a cycle if there are earlier unpaid cycles. Attempting it returns 422 BUSINESS_RULE_VIOLATION. They must settle them in chronological order.
List subscriptions
GET /subscriptions returns your subscriptions sorted by externalSubscriptionId descending, with the standard pagination (page, limit). Filters:
With no results, the response is
200 with data: []. meta.total and meta.hasMore count only the filtered items.
With the
status filter, meta.total may be approximate: the filter is applied again after the actual status is calculated (for example, an expired ACTIVE subscription comes back as ENDED).Update a subscription
PATCH /subscriptions/{id} modifies amount, description, enforcePaymentOrder, allowOverduePayment, autopay, paymentMethods, and additionalData.
- Only
amountis also applied to the debts already generated, keeping their partial payments and theirallowPartialPayments. Cancelled debts are not modified. The other fields only change the subscription. additionalDatais a flat object with the same rules as on creation.- An empty body or a non-editable field returns
400. Invalid types and values (non-numericamount, non-literal booleans,paymentMethodsoutside the enum,autopay: truetogether withpaymentMethods) return400withdetails[]. companyCodein the body is taken as context, not as a field to modify.- A
CANCELLEDorENDEDsubscription cannot be modified:422 BUSINESS_RULE_VIOLATION. - If the subscription is updated but updating one of its already generated debts fails, the API responds
207with the error envelope:error.codeisPARTIAL_UPDATEanderror.messagesays how many debts were not synced (nodetails). The subscription itself was updated.
Pause, resume, and cancel
Debts cancelled by pausing or cancelling are no longer listed in
GET /subscriptions/{id}/debts or GET /debts, but you can still read them with GET /debts/{id}.
Subscription debts
GET /subscriptions/{id}/debts returns the generated debts, with the standard pagination.
- The
externalRequestIdof each debt is<externalSubscriptionId>-cycle-<n>(withnstarting at0), or<externalSubscriptionId>-resume-<n>-cycle-<m>for those generated on resume. - It does not include cancelled debts (same as
GET /debts) andmeta.totalcounts only the listed ones. - The
paymentUrlof each debt is an opaque URL that opens that cycle directly.
Endpoints
Try it in the API Reference
Explore each endpoint with its interactive playground.

