Buy an add-on, or move between tiers of one. Buying and moving to a pricier tier apply now; a cheaper tier waits for the period end.
const url = 'https://api.siteqwality.com/billing/addons';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"pack_id":"2489E9AD-2EE2-8E00-8EC9-32D5F69181C0","billing_interval":"example","idempotency_key":"2489E9AD-2EE2-8E00-8EC9-32D5F69181C0"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://api.siteqwality.com/billing/addons \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "pack_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "billing_interval": "example", "idempotency_key": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0" }'Authorizations
Section titled “Authorizations ”Request Body required
Section titled “Request Body required ”object
"month" or "year".
Client-generated, stable across retries of ONE confirm attempt.
This is what makes a double-click safe: the same value twice is one
Stripe mutation. A client that omits it gets no protection, which is
why it is required rather than optional here (unlike change-plan,
which has to keep accepting bodies from already-cached bundles).
Example generated
{ "pack_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "billing_interval": "example", "idempotency_key": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0"}Responses
Section titled “ Responses ”Add-on bought or upgraded (now), cheaper tier scheduled for period end, or payment required
object
Retained for symmetry with change-plan, whose older clients read it.
The same statuses as change-plan: "updated" when the change is
live, "requires_payment" when the customer must confirm a payment
first, "scheduled" for a cheaper tier (applied at effective_at),
and "scheduled_change_cancelled" when re-picking the current tier
cancelled a scheduled change.
For scheduled: when the change takes effect.
Example generated
{ "success": true, "status": "example", "client_secret": "example", "stripe_subscription_id": "example", "effective_at": "2026-04-15T12:00:00Z"}Invalid interval, unknown pack, pack is not an add-on, or already on that tier
Missing or invalid authentication
Caller is not a super admin
Account has no Stripe customer
Internal server error