Subscriptions
The Subscription API Resource
The subscription model contains all the information about recurring billing relationships with customers.
Properties
| Name | Type | Description |
|---|---|---|
id | string | Unique identifier for the subscription (starts with subscription_). |
resource | string | The resource type. Always subscription. |
customerId | string | ID of the customer who owns this subscription. |
subscriptionPlanId | string | ID of the subscription plan this subscription is based on (starts with subscription_plan_). |
testmode | boolean | Whether this resource is in test mode. |
name | string | The name for the subscription (from the plan). |
description | string | The description for the subscription. |
billingAddress | object | The customer billing address for the subscription. Includes fullName, companyName, taxId, streetAndNumber, streetAdditional, city, region, postalCode, and country. |
basePrice | Money | The base price per billing cycle before taxes. A Money object with value (decimal string) and currency (ISO 4217 code). |
quantity | integer | The quantity for the subscription (e.g., number of seats). |
interval | string | The billing interval. Can be day, week, month, or year. |
intervalCount | integer | The interval count, e.g., "3" for charging every 3 months. |
status | string | The status for the subscription. Can be created, trial, active, canceled, on_grace_period, or paused. |
cancellationReason | string | null | Why the subscription was canceled. Can be payment_failure (payment recovery was exhausted after failed renewals), merchant_request (the merchant canceled the subscription), or customer_request (the customer canceled from the self-service portal). Null unless a cancellation has been requested. |
mandate | object | null | The payment method (mandate) on file. Contains method (e.g. card, sepa_debit, paypal, bacs_debit) and maskedIdentifier (e.g. the last 4 digits). Null if no mandate is set. |
startedAt | string | null | When the subscription started (ISO 8601 format). |
endedAt | string | null | When the subscription actually ended (ISO 8601 format). Stays null during a cancellation grace period — it is only set once the subscription has fully ended. |
canceledAt | string | null | When the subscription was canceled (ISO 8601 format). Set while in on_grace_period, and cleared again if the subscription is resumed. Null if not canceled. |
renewedAt | string | null | When the subscription was last renewed (ISO 8601 format). |
renewedUntil | string | null | Current billing period end date (ISO 8601 format). |
nextRenewalAt | string | null | When the next renewal will be attempted (ISO 8601 format). Null if subscription is canceled or ended. |
trialUntil | string | null | When the trial period ends (ISO 8601 format). Null if not in trial or trial has ended. |
scheduledUpdate | object | null | The target values for a plan/price/quantity change scheduled to take effect at the subscription's next billing cycle, set by an update with applyImmediately: false. The rest of the resource reflects the subscription's current state; scheduledUpdate is what it will become. Contains subscriptionPlanId, name, description, basePrice, quantity, interval, intervalCount, and effectiveAt (the next renewal date when the change applies, or null if there is no scheduled renewal). Null when nothing is pending. This is the authoritative way to reconcile a pending change — always present on the resource, so you don't need to rely on the subscription.update_scheduled webhook. Cleared when the change is applied at renewal, and when it is discarded because the subscription was canceled before that renewal. |
links | object | HATEOAS links to related resources. Contains self and customer links. |
List all subscriptions
GET /v1/subscriptions
This endpoint allows you to retrieve a paginated list of all subscriptions across your account.
Optional attributes
| Name | Type | Description |
|---|---|---|
limit | integer | The number of subscriptions to return (default: 10, max: 100). |
startingAfter | string | A cursor for use in pagination. Returns results after this subscription ID. |
endingBefore | string | A cursor for use in pagination. Returns results before this subscription ID. |
curl -G https://api.vatly.com/v1/subscriptions \
-H "Authorization: Bearer live_your_api_key_here" \
-d limit=10
$vatly = new \Vatly\API\VatlyApiClient();
$vatly->setApiKey('live_your_api_key_here');
$subscriptions = $vatly->subscriptions->page();
{
"data": [
{
"id": "subscription_Lp3mNvBxKw7RjTgYcZaE",
"resource": "subscription",
"customerId": "customer_Lp3mNvBxKw7RjTgYcZaE",
"testmode": false,
"name": "Pro Monthly",
"description": "Full access to all Pro features",
"billingAddress": {
"fullName": "John Doe",
"companyName": "Acme Corp",
"taxId": null,
"streetAndNumber": "123 Main Street",
"streetAdditional": null,
"city": "Berlin",
"region": null,
"postalCode": "10115",
"country": "DE"
},
"basePrice": {
"value": "29.00",
"currency": "EUR"
},
"quantity": 1,
"interval": "month",
"intervalCount": 1,
"status": "active",
"cancellationReason": null,
"startedAt": "2024-01-15T10:30:00Z",
"endedAt": null,
"canceledAt": null,
"renewedAt": "2024-02-15T10:30:00Z",
"renewedUntil": "2024-03-15T10:30:00Z",
"nextRenewalAt": "2024-03-15T10:30:00Z",
"trialUntil": null,
"scheduledUpdate": null,
"links": {
"self": {
"href": "https://api.vatly.com/v1/subscriptions/subscription_Lp3mNvBxKw7RjTgYcZaE",
"type": "application/json"
},
"customer": {
"href": "https://api.vatly.com/v1/customers/customer_Lp3mNvBxKw7RjTgYcZaE",
"type": "application/json"
}
}
},
{
"id": "subscription_Wt5mNvBxKw7YcZaEjLhR",
"resource": "subscription",
"customerId": "customer_Mn6xBtPvKw2RjTgYcZaE",
"testmode": false,
"name": "Enterprise Yearly",
"description": "Enterprise features with priority support",
"billingAddress": {
"fullName": "Jane Smith",
"companyName": "TechCorp Ltd",
"taxId": "GB123456789",
"streetAndNumber": "456 Tech Lane",
"streetAdditional": null,
"city": "London",
"region": null,
"postalCode": "EC1A 1BB",
"country": "GB"
},
"basePrice": {
"value": "990.00",
"currency": "EUR"
},
"quantity": 5,
"interval": "year",
"intervalCount": 1,
"status": "active",
"cancellationReason": null,
"startedAt": "2024-01-01T00:00:00Z",
"endedAt": null,
"canceledAt": null,
"renewedAt": "2024-01-01T00:00:00Z",
"renewedUntil": "2025-01-01T00:00:00Z",
"nextRenewalAt": "2025-01-01T00:00:00Z",
"trialUntil": null,
"scheduledUpdate": null,
"links": {
"self": {
"href": "https://api.vatly.com/v1/subscriptions/subscription_Wt5mNvBxKw7YcZaEjLhR",
"type": "application/json"
},
"customer": {
"href": "https://api.vatly.com/v1/customers/customer_Mn6xBtPvKw2RjTgYcZaE",
"type": "application/json"
}
}
}
],
"count": 2,
"links": {
"self": {
"href": "https://api.vatly.com/v1/subscriptions",
"type": "application/json"
},
"next": null,
"prev": null
}
}
Get a subscription
GET /v1/subscriptions/:id
This endpoint allows you to retrieve a specific subscription by its ID.
Parameters
| Name | Type | Description |
|---|---|---|
subscriptionId | string | The unique identifier of the subscription. |
curl https://api.vatly.com/v1/subscriptions/subscription_Lp3mNvBxKw7RjTgYcZaE \
-H "Authorization: Bearer live_your_api_key_here"
$vatly = new \Vatly\API\VatlyApiClient();
$vatly->setApiKey('live_your_api_key_here');
$subscription = $vatly->subscriptions->get('subscription_Lp3mNvBxKw7RjTgYcZaE');
{
"id": "subscription_Lp3mNvBxKw7RjTgYcZaE",
"resource": "subscription",
"customerId": "customer_Lp3mNvBxKw7RjTgYcZaE",
"testmode": false,
"name": "Pro Monthly",
"description": "Full access to all Pro features",
"billingAddress": {
"fullName": "John Doe",
"companyName": "Acme Corp",
"taxId": null,
"streetAndNumber": "123 Main Street",
"streetAdditional": null,
"city": "Berlin",
"region": null,
"postalCode": "10115",
"country": "DE"
},
"basePrice": {
"value": "29.00",
"currency": "EUR"
},
"quantity": 1,
"interval": "month",
"intervalCount": 1,
"status": "active",
"cancellationReason": null,
"startedAt": "2024-01-15T10:30:00Z",
"endedAt": null,
"canceledAt": null,
"renewedAt": "2024-02-15T10:30:00Z",
"renewedUntil": "2024-03-15T10:30:00Z",
"nextRenewalAt": "2024-03-15T10:30:00Z",
"trialUntil": null,
"scheduledUpdate": null,
"links": {
"self": {
"href": "https://api.vatly.com/v1/subscriptions/subscription_Lp3mNvBxKw7RjTgYcZaE",
"type": "application/json"
},
"customer": {
"href": "https://api.vatly.com/v1/customers/customer_Lp3mNvBxKw7RjTgYcZaE",
"type": "application/json"
}
}
}
List customer subscriptions
GET /v1/customers/:customerId/subscriptions
This endpoint allows you to retrieve a paginated list of all subscriptions for a specific customer.
Parameters
| Name | Type | Description |
|---|---|---|
customerId | string | The unique identifier of the customer. |
limit | integer | The number of subscriptions to return (default: 10, max: 100). |
startingAfter | string | A cursor for use in pagination. Returns results after this subscription ID. |
endingBefore | string | A cursor for use in pagination. Returns results before this subscription ID. |
curl -G https://api.vatly.com/v1/customers/customer_Lp3mNvBxKw7RjTgYcZaE/subscriptions \
-H "Authorization: Bearer live_your_api_key_here" \
-d limit=10
$vatly = new \Vatly\API\VatlyApiClient();
$vatly->setApiKey('live_your_api_key_here');
$subscriptions = $vatly->customers->subscriptions('customer_Lp3mNvBxKw7RjTgYcZaE')->page();
{
"data": [
{
"id": "subscription_Lp3mNvBxKw7RjTgYcZaE",
"resource": "subscription",
"customerId": "customer_Lp3mNvBxKw7RjTgYcZaE",
"testmode": false,
"name": "Pro Monthly",
"description": "Full access to all Pro features",
"billingAddress": {
"fullName": "John Doe",
"companyName": "Acme Corp",
"taxId": null,
"streetAndNumber": "123 Main Street",
"streetAdditional": null,
"city": "Berlin",
"region": null,
"postalCode": "10115",
"country": "DE"
},
"basePrice": {
"value": "29.00",
"currency": "EUR"
},
"quantity": 1,
"interval": "month",
"intervalCount": 1,
"status": "active",
"cancellationReason": null,
"startedAt": "2024-01-15T10:30:00Z",
"endedAt": null,
"canceledAt": null,
"renewedAt": "2024-02-15T10:30:00Z",
"renewedUntil": "2024-03-15T10:30:00Z",
"nextRenewalAt": "2024-03-15T10:30:00Z",
"trialUntil": null,
"scheduledUpdate": null,
"links": {
"self": {
"href": "https://api.vatly.com/v1/subscriptions/subscription_Lp3mNvBxKw7RjTgYcZaE",
"type": "application/json"
},
"customer": {
"href": "https://api.vatly.com/v1/customers/customer_Lp3mNvBxKw7RjTgYcZaE",
"type": "application/json"
}
}
}
],
"count": 1,
"links": {
"self": {
"href": "https://api.vatly.com/v1/customers/customer_Lp3mNvBxKw7RjTgYcZaE/subscriptions",
"type": "application/json"
},
"next": null,
"prev": null
}
}
Get a customer subscription
GET /v1/customers/:customerId/subscriptions/:subscriptionId
This endpoint allows you to retrieve a specific subscription for a specific customer.
Parameters
| Name | Type | Description |
|---|---|---|
customerId | string | The unique identifier of the customer. |
subscriptionId | string | The unique identifier of the subscription. |
curl https://api.vatly.com/v1/customers/customer_Lp3mNvBxKw7RjTgYcZaE/subscriptions/subscription_Lp3mNvBxKw7RjTgYcZaE \
-H "Authorization: Bearer live_your_api_key_here"
$vatly = new \Vatly\API\VatlyApiClient();
$vatly->setApiKey('live_your_api_key_here');
$subscription = $vatly->customers->subscriptions('customer_Lp3mNvBxKw7RjTgYcZaE')->get('subscription_Lp3mNvBxKw7RjTgYcZaE');
{
"id": "subscription_Lp3mNvBxKw7RjTgYcZaE",
"resource": "subscription",
"customerId": "customer_Lp3mNvBxKw7RjTgYcZaE",
"testmode": false,
"name": "Pro Monthly",
"description": "Full access to all Pro features",
"billingAddress": {
"fullName": "John Doe",
"companyName": "Acme Corp",
"taxId": null,
"streetAndNumber": "123 Main Street",
"streetAdditional": null,
"city": "Berlin",
"region": null,
"postalCode": "10115",
"country": "DE"
},
"basePrice": {
"value": "29.00",
"currency": "EUR"
},
"quantity": 1,
"interval": "month",
"intervalCount": 1,
"status": "active",
"cancellationReason": null,
"startedAt": "2024-01-15T10:30:00Z",
"endedAt": null,
"canceledAt": null,
"renewedAt": "2024-02-15T10:30:00Z",
"renewedUntil": "2024-03-15T10:30:00Z",
"nextRenewalAt": "2024-03-15T10:30:00Z",
"trialUntil": null,
"scheduledUpdate": null,
"links": {
"self": {
"href": "https://api.vatly.com/v1/subscriptions/subscription_Lp3mNvBxKw7RjTgYcZaE",
"type": "application/json"
},
"customer": {
"href": "https://api.vatly.com/v1/customers/customer_Lp3mNvBxKw7RjTgYcZaE",
"type": "application/json"
}
}
}
Update a subscription
PATCH /v1/subscriptions/:id
This endpoint allows you to update a subscription. You can change the plan, quantity, or recurring price, and control proration and timing.
Optional attributes
At least one of subscriptionPlanId, quantity, or price must be provided.
| Name | Type | Description |
|---|---|---|
subscriptionPlanId | string | The ID of the subscription plan to update to (starts with subscription_plan_). Must match the testmode of the current subscription. |
quantity | integer | The new total quantity for the subscription (e.g. number of seats). This sets the quantity to the given value — it is not added to the current quantity. Must be at least 1. |
price | object | Set a new recurring price while keeping the current plan. A Money object with value (decimal string) and currency (EUR or USD); the currency must match the subscription's own currency. Sent alongside subscriptionPlanId, it overrides the new plan's default price. |
prorate | boolean | Whether to prorate charges for the partial billing period. If true, the customer is credited for unused time on the old plan and charged for remaining time on the new plan. Default: true. |
applyImmediately | boolean | Whether to apply changes immediately or at the end of the current billing period. When true, changes take effect immediately (with proration if enabled). When false, the change is scheduled for the end of the current period and surfaced on the subscription's scheduledUpdate. Default: false. |
invoiceImmediately | boolean | Whether to generate and charge an invoice immediately for proration. Only applies when applyImmediately and prorate are both true. When false (the default), the proration delta is parked on the current cycle and billed as a line on the next renewal invoice — the customer gets one invoice and one payment covering both. If the subscription is canceled before that renewal, the parked amount is waived, so a customer is never charged at the moment they cancel. Set this to true on yearly and other long-interval plans, where the deferred delta may not be billed for many months and is waived entirely if the customer cancels in between. Default: false. |
anchor | date | Set the billing anchor to a specific calendar date (YYYY-MM-DD, interpreted in UTC); the billing cycle is recalculated around it. Cannot be combined with resetAnchor. |
resetAnchor | boolean | Reset the billing anchor to "now" — the moment the update is processed becomes the new cycle anchor and future renewals align to it. Cannot be combined with anchor. Default: false. |
trialUntil | string | Extend or set a trial period until this date (ISO 8601 format). Cannot be combined with anchor. |
Scheduling and proration behavior:
- With
applyImmediately: false(the default), the change is scheduled for the next billing cycle and returned on the subscription'sscheduledUpdate. Scheduled updates are applied at renewal — they are no longer silently discarded — and are cleared only if the subscription is canceled before that renewal. - A subscription whose
statusison_grace_periodhas no next billing period. Scheduling an update for it returns422; resume the subscription first, or setapplyImmediately: trueto apply the change to the remaining period. - An immediate update on an
on_grace_periodsubscription can change the plan, price, or quantity for the remaining period, but not when that period ends —anchor,resetAnchor, and switching to a plan with a different interval each return422, because each would move the already-communicated end date. - When
invoiceImmediatelyisfalse, the proration delta is parked on the current cycle and billed on the next renewal invoice. If the subscription is canceled before that renewal, the parked charge is waived.
curl -X PATCH https://api.vatly.com/v1/subscriptions/subscription_Lp3mNvBxKw7RjTgYcZaE \
-H "Authorization: Bearer live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"subscriptionPlanId": "subscription_plan_Wt5mNvBxKw7YcZaEjLhR",
"prorate": true,
"applyImmediately": true
}'
curl -X PATCH https://api.vatly.com/v1/subscriptions/subscription_Lp3mNvBxKw7RjTgYcZaE \
-H "Authorization: Bearer live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"price": { "value": "29.00", "currency": "EUR" },
"applyImmediately": true
}'
$vatly = new \Vatly\API\VatlyApiClient();
$vatly->setApiKey('live_your_api_key_here');
$subscription = $vatly->subscriptions->update('subscription_Lp3mNvBxKw7RjTgYcZaE', [
'subscriptionPlanId' => 'subscription_plan_Wt5mNvBxKw7YcZaEjLhR',
'prorate' => true,
'applyImmediately' => true,
]);
{
"id": "subscription_Lp3mNvBxKw7RjTgYcZaE",
"resource": "subscription",
"customerId": "customer_Lp3mNvBxKw7RjTgYcZaE",
"testmode": false,
"name": "Pro Yearly",
"description": "Full access to all Pro features, billed yearly",
"billingAddress": {
"fullName": "John Doe",
"companyName": "Acme Corp",
"taxId": null,
"streetAndNumber": "123 Main Street",
"streetAdditional": null,
"city": "Berlin",
"region": null,
"postalCode": "10115",
"country": "DE"
},
"basePrice": {
"value": "290.00",
"currency": "EUR"
},
"quantity": 1,
"interval": "year",
"intervalCount": 1,
"status": "active",
"cancellationReason": null,
"startedAt": "2024-01-15T10:30:00Z",
"endedAt": null,
"canceledAt": null,
"renewedAt": "2024-02-15T10:30:00Z",
"renewedUntil": "2025-02-15T10:30:00Z",
"nextRenewalAt": "2025-02-15T10:30:00Z",
"trialUntil": null,
"scheduledUpdate": null,
"links": {
"self": {
"href": "https://api.vatly.com/v1/subscriptions/subscription_Lp3mNvBxKw7RjTgYcZaE",
"type": "application/json"
},
"customer": {
"href": "https://api.vatly.com/v1/customers/customer_Lp3mNvBxKw7RjTgYcZaE",
"type": "application/json"
}
}
}
Create billing update link
POST /v1/subscriptions/:id/billing-update-link
Creates a signed link that the customer can use to update the billing details for this subscription — billing address, VAT number, and company name — via a hosted flow.
Required attributes
| Name | Type | Description |
|---|---|---|
redirectUrlSuccess | string | URL to redirect after successful billing update. |
redirectUrlCanceled | string | URL to redirect if customer cancels the update. |
Optional attributes
| Name | Type | Description |
|---|---|---|
billingAddress | object | Pre-fill billing address fields. Customer can modify these values in the hosted form. |
curl -X POST https://api.vatly.com/v1/subscriptions/subscription_Lp3mNvBxKw7RjTgYcZaE/billing-update-link \
-H "Authorization: Bearer live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"redirectUrlSuccess": "https://example.com/billing-updated",
"redirectUrlCanceled": "https://example.com/account/billing",
"billingAddress": {
"companyName": "Acme Corp",
"taxId": "DE123456789"
}
}'
$vatly = new \Vatly\API\VatlyApiClient();
$vatly->setApiKey('live_your_api_key_here');
$response = $vatly->subscriptions->createBillingUpdateLink('subscription_Lp3mNvBxKw7RjTgYcZaE', [
'redirectUrlSuccess' => 'https://example.com/billing-updated',
'redirectUrlCanceled' => 'https://example.com/account/billing',
'billingAddress' => [
'companyName' => 'Acme Corp',
'taxId' => 'DE123456789',
],
]);
// Redirect the customer to the hosted billing update page
header('Location: ' . $response->href, true, 303);
{
"href": "https://vatly.com/subscriptions/subscription_Lp3mNvBxKw7RjTgYcZaE/billing?token=xyz...",
"type": "text/html"
}
Cancel a subscription
DELETE /v1/subscriptions/:id
This endpoint allows you to cancel a subscription. By default, the subscription will remain active until the end of the current billing period (grace period), after which it will be fully canceled. Set immediately=true to cancel immediately.
During the grace period the subscription's status is on_grace_period and canceledAt is set, while endedAt stays null. A grace-period cancellation can still be reversed with Resume a subscription until the period lapses. A subscription canceled with immediately=true (status canceled) cannot be reactivated — create a new subscription instead.
Optional query parameters
| Name | Type | Description |
|---|---|---|
immediately | boolean | Cancel immediately instead of at period end. Default: false. |
curl -X DELETE https://api.vatly.com/v1/subscriptions/subscription_Lp3mNvBxKw7RjTgYcZaE \
-H "Authorization: Bearer live_your_api_key_here"
curl -X DELETE "https://api.vatly.com/v1/subscriptions/subscription_Lp3mNvBxKw7RjTgYcZaE?immediately=true" \
-H "Authorization: Bearer live_your_api_key_here"
$vatly = new \Vatly\API\VatlyApiClient();
$vatly->setApiKey('live_your_api_key_here');
$vatly->subscriptions->cancel('subscription_Lp3mNvBxKw7RjTgYcZaE');
Returns 204 No Content on success.
Resume a subscription
POST /v1/subscriptions/:id/resume
Resumes a subscription that was canceled with a grace period, while it is still within that period.
When this works:
- The subscription's
statusison_grace_period. endedAtstaysnullthroughout the grace period — resumability is keyed offstatus, notendedAt.
Result:
- Status returns to
active. - The existing billing cycle and renewal schedule are preserved (no new charge fires immediately) and the original payment mandate remains in effect.
canceledAtis cleared and asubscription.resumedwebhook is delivered.- The refreshed subscription is returned in the response body.
When this does not work (returns 422):
- The subscription was canceled immediately (
statusiscanceled). - The grace period has already lapsed (
statusis nowcanceledandendedAtis set). - The subscription is already active (the no-op is rejected for clarity, not silently ignored).
In all of those cases, create a new subscription instead.
Parameters
| Name | Type | Description |
|---|---|---|
id | string | The unique identifier of the subscription. |
curl -X POST https://api.vatly.com/v1/subscriptions/subscription_Lp3mNvBxKw7RjTgYcZaE/resume \
-H "Authorization: Bearer live_your_api_key_here"
$vatly = new \Vatly\API\VatlyApiClient();
$vatly->setApiKey('live_your_api_key_here');
$subscription = $vatly->subscriptions->resume('subscription_Lp3mNvBxKw7RjTgYcZaE');
{
"id": "subscription_Lp3mNvBxKw7RjTgYcZaE",
"resource": "subscription",
"customerId": "customer_Lp3mNvBxKw7RjTgYcZaE",
"subscriptionPlanId": "subscription_plan_Rk5pQrSvWm8NjLhYbUcP",
"testmode": false,
"name": "Pro Monthly",
"status": "active",
"cancellationReason": null,
"basePrice": {
"value": "29.00",
"currency": "EUR"
},
"quantity": 1,
"interval": "month",
"intervalCount": 1,
"startedAt": "2024-01-15T10:30:00Z",
"endedAt": null,
"canceledAt": null,
"renewedAt": "2024-02-15T10:30:00Z",
"renewedUntil": "2024-03-15T10:30:00Z",
"nextRenewalAt": "2024-03-15T10:30:00Z",
"trialUntil": null,
"scheduledUpdate": null,
"links": {
"self": {
"href": "https://api.vatly.com/v1/subscriptions/subscription_Lp3mNvBxKw7RjTgYcZaE",
"type": "application/json"
},
"customer": {
"href": "https://api.vatly.com/v1/customers/customer_Lp3mNvBxKw7RjTgYcZaE",
"type": "application/json"
}
}
}