Skip to main content
A subscription (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 in ACTIVE 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 literal true or false: "true", 1, etc. return 400 with details[].
  • Undeclared fields are ignored.
  • If the schedule (intervalUnit, intervalCount, startDate, totalCycles, endDate, billingDay) is invalid, the API responds 400 and nothing is created: not the subscription, the product, or the inline contact.
See API conventions for the rules shared by all endpoints.

Idempotency

Subscription creation does not use the Idempotency-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.
billingDay is only valid for month or year intervals. The month subfield only applies to year intervals.
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 send totalCycles 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 the scheduling object.
  • description, additionalData, and paymentMethods are only present when they have a value.
  • scheduling.totalCycles and scheduling.endDate are always present, with null when they do not apply.
  • paymentUrl is 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 the paymentUrl of each debt in GET /subscriptions/{id}/debts.
  • contact.contactId always has the con_ prefix, also when the contact was created inline with contactData.
  • createdInline on contact and product indicates 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 amount is also applied to the debts already generated, keeping their partial payments and their allowPartialPayments. Cancelled debts are not modified. The other fields only change the subscription.
  • additionalData is 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-numeric amount, non-literal booleans, paymentMethods outside the enum, autopay: true together with paymentMethods) return 400 with details[].
  • companyCode in the body is taken as context, not as a field to modify.
  • A CANCELLED or ENDED subscription cannot be modified: 422 BUSINESS_RULE_VIOLATION.
  • If the subscription is updated but updating one of its already generated debts fails, the API responds 207 with the error envelope: error.code is PARTIAL_UPDATE and error.message says how many debts were not synced (no details). 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 externalRequestId of each debt is <externalSubscriptionId>-cycle-<n> (with n starting at 0), or <externalSubscriptionId>-resume-<n>-cycle-<m> for those generated on resume.
  • It does not include cancelled debts (same as GET /debts) and meta.total counts only the listed ones.
  • The paymentUrl of 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.