Skip to main content
POST
Calling this endpoint with an empty body (or no body at all) cancels the subscription right away, changing its status to CANCELED. This is unchanged and still final: once immediate, it cannot be reverted. You can cancel from CREATED, ACTIVE, TRIALING, PAST_DUE and PAUSED. COMPLETED and CANCELED are terminal, so canceling from either returns 400 INVALID_STATE — including a second cancel on a subscription that is already canceled. See Subscription Status.

Refund on immediate cancel

Allowlisted organizations onlyRefund quoting is gated per organization, independently of scheduled cancellation. If your calls return 400 "Refund quote is not enabled for this organization.", contact your Yuno representative to have it enabled.
Send a refund mode alongside an immediate cancel (an absent schedule) to have Yuno compute a refund quote for the current billing cycle. Yuno only computes the quote — it never moves money. refund.execution on the response is always "MERCHANT": you execute the refund yourself against your own payment rail, using the quoted amount as input.
Cancel with a prorated refund
refund cannot be combined with schedule — sending both, or sending refund together with "schedule": null, is rejected with 400 before anything is written. refund: NONE, or omitting refund entirely, still works normally alongside schedule.

The refund basis

Every refund mode is computed against the same basis: the most recently captured payment on the subscription — never the subscription’s own list price, and never an estimate. If the current cycle has no captured payment at all (a decline, a payment still pending, or a gap), PRORATED, FULL, and CUSTOM all return 400 REFUND_NOT_AVAILABLE — except a genuine $0 phase (a trial or promotional phase with nothing charged), which returns 200 with amount: 0.00. There’s nothing to refund there, but that’s not an error. A PAST_DUE subscription has no captured payment for the cycle in progress, so the same rule applies: refund: NONE (or an absent refund) still cancels normally, but any other mode returns 400 REFUND_NOT_AVAILABLE with current_status: "PAST_DUE".

PRORATED

Refunds the unused days of the current cycle, using calendar-date arithmetic — never hours or minutes:
  • period_start — the date the last captured payment was charged.
  • period_end — the date the current cycle naturally ends, capped at availability.finish_at when the subscription has one and it falls earlier.
  • unused_days = period_days − used_days, where used_days counts the day you cancel on as a full used day — canceling at any time on day 10 of a 30-day cycle still counts 10 used days.
  • amount = charged_amount × unused_days / period_days, rounded HALF_UP at the currency’s minor units.
For example, a 30-day cycle charged $30.00, canceled on day 10: 10 days used, 20 days unused, amount = 30.00 × 20/30 = 20.00.
Cancel with a prorated refund — response
A subscription billed by the hour or minute has no meaningful notion of a “day,” so PRORATED returns 400 "proration is not available for sub-daily cadences" there. FULL and CUSTOM are unaffected — both still work on a sub-daily cadence.

FULL

Refunds the entire basis.charged_amount of the current cycle — amount always equals basis.charged_amount exactly, and unused_days equals period_days (the whole cycle is treated as unused).

CUSTOM

A CUSTOM refund takes its amount from one of two places, in this order:
  1. refund_amount on the cancel request, if you send one.
  2. Otherwise the refund_rule configured on the subscription.
If neither exists, the cancellation is rejected with 400 BAD_REQUEST — "refund: CUSTOM requires refund_amount or a configured refund_rule." CUSTOM never falls back to a computed amount. refund_rule is set on Create or Update and described on the Subscription object.
Cancel G — the request's refund_amount wins over the configured refund_rule
Cancel H — no refund_amount sent, the configured refund_rule wins
AvailabilityA configured refund_rule is available to organizations enabled for it; contact Yuno. For an organization that is not yet enabled, CUSTOM behaves exactly as it does today: refund_amount on the request is required, and a refund_rule can never be configured for that organization in the first place.
Send refund_amount alongside "refund": "CUSTOM" to refund a specific amount instead of a computed or rule-based one:
Cancel with a custom refund amount
amount in the response is your refund_amount echoed back exactly — not recomputed or rounded again. refund_amount.value must be greater than 0 and must not exceed the current cycle’s charged amount, and refund_amount.currency must match the subscription’s own currency.

Reading the refund object

refund is null when refund was NONE or omitted. Otherwise:

Where refund appears

  • The cancel 200 response always carries the refund key — null when no quote was computed, the object otherwise.
  • GET /v1/subscriptions/{id} and GET /v1/subscriptions?merchant_reference=... carry the last computed refund too, once one exists — canceling with a refund quote persists it, so it keeps showing up on every later read of that subscription through either of these.
  • GET /v1/subscriptions (the paginated list) does not carry refund on any entry, whether or not that subscription was canceled with a quote — the list item is a smaller summary shape than the full subscription object, and refund isn’t one of the fields it includes.
  • The subscription.cancel webhook carries refund only when a quote was actually computed (PRORATED/FULL/CUSTOM) — it’s absent, not null, for a NONE/absent-refund cancellation.

Scheduling a cancellation instead

Allowlisted organizations onlyScheduled cancellation is gated per organization. If your calls return 400 "Scheduled cancellation is not enabled for this organization.", contact your Yuno representative to have it enabled.
Send a schedule object to defer the cancellation instead of applying it now. The subscription stays in its current status — billing continues normally — until the schedule fires.
Schedule after 3 more billing cycles
Schedule for a specific date
schedule takes exactly one of: Sending both or neither returns 400. A PIX_AUTOMATIC subscription billed with frequency.execution: MERCHANT cannot schedule a cancellation — schedule it as at or after_cycles on a different payment method type, or cancel immediately instead.

Behavior while retries are running

past_due_behavior is echoed on cancel_scheduled, pending product confirmationThe engine currently stamps whatever past_due_behavior resolved for the schedule (explicit value, or the EXHAUST_RETRIES default) onto the cancel_scheduled.past_due_behavior field in every response. Treat this echo as provisional until product confirms it as the intended external contract — it may change.
past_due_behavior controls what a scheduled cancellation does if the subscription is PAST_DUE when the schedule would otherwise fire:
Schedule and skip retries if PAST_DUE when it fires
Independently of past_due_behavior, canceling a PAST_DUE subscription — whether immediately or because a schedule became due — never errors and always stops that cycle’s retries. See Subscription Status and Retries.
Refund handling is only available on an immediate cancel — see Refund on immediate cancel. It does not ship with scheduled cancellation: a schedule request cannot also carry a refund mode.

Undoing a scheduled cancellation

Send "schedule": null to clear a pending schedule. The subscription is unaffected otherwise — no status change, no charge.
Undo a scheduled cancellation
Sending "schedule": null when nothing is scheduled is a no-op: 200, cancel_scheduled stays null, and no webhook is sent. When a schedule is cleared, subscription.cancel_schedule_canceled fires instead.

Reading the result

The response carries the same Subscription shape as every other subscription endpoint. Four fields are relevant here:
  • cancel_scheduled — null when no cancellation is pending, otherwise the schedule’s mode, after_cycles/at, requested_at and past_due_behavior.
  • retries.stopped, retries.attempts_made, retries.outcome, retries.next_retry_at — populated whenever the subscription is PAST_DUE or has a cancel_scheduled pending; omitted otherwise.
  • unpaid_cycle — present when the current billing cycle is PAST_DUE and has not settled.
  • refund — null (or absent on non-cancel reads) when no refund quote was computed, otherwise the quoted amount and its basis. See Refund on immediate cancel.
Persistence of retries and unpaid_cycle after cancellation is pending product confirmationBoth fields are computed from the subscription’s live state at request time. Whether a GET /v1/subscriptions/{id} issued after the subscription reaches CANCELED continues to surface the retry snapshot / unpaid cycle from the cycle that was in flight, or omits them once terminal, is not yet confirmed as a stable contract — verify against a live response before depending on it.
See the Subscription object for the full field reference.

Scheduling a cancellation on a paused subscription

A paused subscription can still be scheduled for cancellation. schedule.after_cycles is rejected while the subscription is PAUSED, because a pause freezes the billing clock and there is no cycle boundary to count from:
400 — after_cycles on a paused subscription
schedule.at has no such restriction — it needs no cycle clock, so it is accepted normally on a PAUSED subscription. See Pause Subscription.

Webhooks

  • subscription.cancel_scheduled — sent when a cancellation is scheduled via this endpoint.
  • subscription.cancel_schedule_canceled — sent when a pending scheduled cancellation is undone via "schedule": null.
An immediate cancellation (empty body, or a schedule becoming due) still emits subscription.cancel as before — scheduling emits its own events only for the scheduling and undoing actions themselves. subscription.cancel’s payload carries refund too, following the same rule as the cancel response itself: present only when a quote was actually computed (PRORATED/FULL/CUSTOM), absent for a NONE/absent-refund cancellation.

Errors

Authorizations

public-api-key
string
header
default:<Your public-api-key>
required
private-secret-key
string
header
default:<Your private-secret-key>
required

Path Parameters

subscription_id
string
required

The unique identifier of a subscription.

Body

application/json
refund
string

NONE (default), PRORATED, FULL, or CUSTOM. Computes a refund quote for the current billing cycle on an immediate cancel. Cannot be combined with schedule.

Example:

"PRORATED"

refund_amount
object

Required, and only allowed, when refund is CUSTOM. Must not exceed the current cycle's charged amount.

schedule
object | null

Response

200

id
string
Example:

"7304911d-5df9-429e-8488-ad41abea1a4c"

name
string
Example:

"sub_001"

description
string | null
Example:

"streaming service"

account_id
string
Example:

"2404911d-5df9-429e-8488-ad41abea1a4b"

merchant_reference
string
Example:

"001_marzo_23"

status
string
Example:

"CANCELED"

subscription_plan_id
string
Example:

"1904911d-5df9-429e-8488-ad41abea1a4d"

amount
object
frequency
object
billing_cycles
object
customer_payer
object
payment_method
object

The payment method the subscription charges. A single object, not a list.

availability
object
metadata
object[] | null
payments
string[]

List of payment IDs created by the subscription. Omitted from the response when the subscription has no payments.

retries
object
unpaid_cycle
object

Present only when the current billing cycle is PAST_DUE and has not settled.

cancel_scheduled
object | null

null when no cancellation is pending.

refund
object | null

null when refund was NONE or omitted. Present on the cancel response and on every later read once computed.

created_at
string
Example:

"2023-12-16T20:46:54.786342Z"

updated_at
string
Example:

"2023-12-16T21:00:54.786342Z"

country
string

The subscription's country (ISO 3166-1 alpha-2).

Example:

"US"

soft_descriptor
string | null

Soft descriptor.

Example:

"ACME SUBSCRIPTION"

current_period_start
string | null

Start of the billing period the subscription is currently in, one cadence before current_period_end. Inside a trial or any other plan phase it follows that phase's cadence. A terminal subscription (CANCELED or COMPLETED) keeps its final window rather than going null. Null only for a subscription with no recurring cadence. Always returned together with current_period_end.

Example:

"2023-01-16T20:00:00.912480Z"

current_period_end
string | null

End of the billing period the subscription is currently in. Equal to billing_cycles.next_at. Always returned together with current_period_start.

Example:

"2023-02-16T20:00:00.786342Z"

billing_date
object | null

Only present on subscriptions billed on a fixed date instead of a frequency; mutually exclusive with frequency.

trial_period
object

Trial period.

additional_data
object | null

The optional order enrichment sent at creation, echoed back. See The Subscription Object for its full shape.

initial_payment_validation
boolean

Whether the subscription waited for its first payment to succeed before activating. false by default.

Example:

false

reason
string | null

Free-form reason for the subscription's current status, when one was recorded; null otherwise.

Example:

"merchant request"

renewal_notification_days
integer | null

How many days before the next billing date the subscription.close_to_renewal webhook is sent. null means no renewal notification is sent.

Example:

3

subscription_agreement_id
string | null

Subscription agreement id.

Example:

"sa_6af2dfcd-44c0-4f16-a331-8bed3ed9c9fa"

cancellation_time
string | null

When the subscription was canceled. Only set once it reaches CANCELED; null otherwise.

Example:

"2023-12-16T20:46:54.786342Z"

cancellation_source
string | null

What triggered the cancellation: MERCHANT, SYSTEM, PLAN_CHANGE or RETRIES_EXHAUSTED. Only set once the subscription reaches CANCELED; null otherwise. Treat as open-ended — handle unrecognized values gracefully.

Example:

"MERCHANT"

ending_time
string | null

When the subscription completed or expired. Only set once it reaches COMPLETED; null otherwise.

Example:

"2024-12-16T20:46:54.786342Z"

plan_id
string

Only present when the subscription is linked to a plan.

Example:

"00000000-0000-4000-8000-000000000001"

plan_assigned_at
string

Only present on plan-linked subscriptions. The moment the plan was attached — at creation from a plan, migration onto a plan, or a plan change.

Example:

"2023-12-16T20:46:54.786342Z"

previous_subscription_id
string | null

Only present when this subscription was created by a plan change (switching an existing subscription to a different plan).

phases
object[]

The linked plan's phase definitions, as the plan declares them — including the terminal REGULAR phase. Only present on plan-linked subscriptions whose plan has phases. This is the plan's own ladder, not this subscriber's snapshot: for what was pinned for this subscription at creation, use billing_phases.

current_phase
string

Only present on plan-linked subscriptions.

Example:

"REGULAR"

current_phase_end
string | null

When the phase named by current_phase hands over to the next one — for a subscription in its trial, when it switches to the regular price. End of the last billing period the phase covers, so on the final period of a trial it equals current_period_end. Null for a subscription with no plan and for one that already left the phase ladder.

Example:

"2023-03-16T20:00:00.786342Z"

billing_phases
object[]

Only present on plan-linked subscriptions whose plan has phases. Contains the leading TRIAL phases only, snapshotted at creation — the terminal REGULAR phase is never included, since its price isn't pinned and instead resolves from the plan at every billing. current_phase: "REGULAR" is how you know the ladder is exhausted.

pending_plan_change
object | null

The plan change scheduled for the next billing cycle, or null when none is pending. Schedule one with the plan_change block on Update Subscription.

refund_rule
object

The stored refund rule used to resolve a CUSTOM cancel refund when the cancel request sends no refund_amount of its own. Present only when one is configured; omitted, not null, otherwise. Available to organizations enabled for it; contact Yuno.

pause_until
string

When the subscription is scheduled to resume automatically. Present only on a subscription currently paused with a duration (pause_cycles or pause_until sent at pause); absent, not null, on every other read.

Example:

"2026-10-15T15:54:22.780166Z"