curl --request POST \
--url https://app.base44.com/api/billing/dynamic-offering/update-subscription \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"new_product_id": "5f0c7a2e-3b91-4d6a-9e0f-2c8b1d4a7e63",
"period_unit": "MONTH"
}
'import requests
url = "https://app.base44.com/api/billing/dynamic-offering/update-subscription"
payload = {
"new_product_id": "5f0c7a2e-3b91-4d6a-9e0f-2c8b1d4a7e63",
"period_unit": "MONTH"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({new_product_id: '5f0c7a2e-3b91-4d6a-9e0f-2c8b1d4a7e63', period_unit: 'MONTH'})
};
fetch('https://app.base44.com/api/billing/dynamic-offering/update-subscription', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.base44.com/api/billing/dynamic-offering/update-subscription",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'new_product_id' => '5f0c7a2e-3b91-4d6a-9e0f-2c8b1d4a7e63',
'period_unit' => 'MONTH'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.base44.com/api/billing/dynamic-offering/update-subscription"
payload := strings.NewReader("{\n \"new_product_id\": \"5f0c7a2e-3b91-4d6a-9e0f-2c8b1d4a7e63\",\n \"period_unit\": \"MONTH\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.base44.com/api/billing/dynamic-offering/update-subscription")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"new_product_id\": \"5f0c7a2e-3b91-4d6a-9e0f-2c8b1d4a7e63\",\n \"period_unit\": \"MONTH\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.base44.com/api/billing/dynamic-offering/update-subscription")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"new_product_id\": \"5f0c7a2e-3b91-4d6a-9e0f-2c8b1d4a7e63\",\n \"period_unit\": \"MONTH\"\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"invoice_id": "2025-0001234"
}{
"detail": [
{
"type": "literal_error",
"loc": [
"body",
"period_unit"
],
"msg": "Input should be 'MONTH' or 'YEAR'",
"input": "WEEK",
"ctx": {
"expected": "'MONTH' or 'YEAR'"
}
}
]
}Change Wix plan
Moves a Wix-billed workspace to another plan. Take new_product_id from wix_product_id in List plans. Only the workspace owner can change the plan.
- Upgrade (a bigger plan): the response has
invoice_id. A declined card returns 402 withdetail.error_typeset topayment_failed, and an unpaid invoice on the plan returns 402 withpast_due. - Downgrade (a smaller plan): takes effect at the end of the billing period, and nothing is charged now. The response has
is_downgradeset totrue. A downgrade keeps the billing period, and only one change can be scheduled at a time. Undo it with Cancel scheduled plan change.
You can switch from monthly to yearly billing, but not from yearly to monthly. A downgrade also turns a plan that’s set to end back to renewing.
To retry an upgrade safely, send an Idempotency-Key header and repeat it on the retry: Wix then charges the order once. Without the header, each request is a new charge attempt, so check Get billing info before you retry.
This works only for workspaces billed through Wix. For a Stripe-billed workspace, use Change plan. Wix billing is still rolling out, so this can return 404 for your account.
The base rate limit is 10 requests per hour per user. See Rate limits for the multiplier your plan gets.
curl --request POST \
--url https://app.base44.com/api/billing/dynamic-offering/update-subscription \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"new_product_id": "5f0c7a2e-3b91-4d6a-9e0f-2c8b1d4a7e63",
"period_unit": "MONTH"
}
'import requests
url = "https://app.base44.com/api/billing/dynamic-offering/update-subscription"
payload = {
"new_product_id": "5f0c7a2e-3b91-4d6a-9e0f-2c8b1d4a7e63",
"period_unit": "MONTH"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({new_product_id: '5f0c7a2e-3b91-4d6a-9e0f-2c8b1d4a7e63', period_unit: 'MONTH'})
};
fetch('https://app.base44.com/api/billing/dynamic-offering/update-subscription', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.base44.com/api/billing/dynamic-offering/update-subscription",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'new_product_id' => '5f0c7a2e-3b91-4d6a-9e0f-2c8b1d4a7e63',
'period_unit' => 'MONTH'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.base44.com/api/billing/dynamic-offering/update-subscription"
payload := strings.NewReader("{\n \"new_product_id\": \"5f0c7a2e-3b91-4d6a-9e0f-2c8b1d4a7e63\",\n \"period_unit\": \"MONTH\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.base44.com/api/billing/dynamic-offering/update-subscription")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"new_product_id\": \"5f0c7a2e-3b91-4d6a-9e0f-2c8b1d4a7e63\",\n \"period_unit\": \"MONTH\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.base44.com/api/billing/dynamic-offering/update-subscription")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"new_product_id\": \"5f0c7a2e-3b91-4d6a-9e0f-2c8b1d4a7e63\",\n \"period_unit\": \"MONTH\"\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"invoice_id": "2025-0001234"
}{
"detail": [
{
"type": "literal_error",
"loc": [
"body",
"period_unit"
],
"msg": "Input should be 'MONTH' or 'YEAR'",
"input": "WEEK",
"ctx": {
"expected": "'MONTH' or 'YEAR'"
}
}
]
}Authorizations
Personal access token, sent as Authorization: Bearer <token>. Each operation lists the permission it needs: read_only or full_access. A full-access token can call every operation. A read-only token can call only the operations that list read_only; anything else returns 403 read_only_credential_route_not_allowed.
Headers
A key you generate for this plan change. Send the same key when you retry it, and a new one for every other change.
8 - 100Body
Wix product of the plan to move to, from wix_product_id in List plans.
1 - 100"5f0c7a2e-3b91-4d6a-9e0f-2c8b1d4a7e63"
Billing period of the new plan: MONTH or YEAR.
MONTH, YEAR "MONTH"
Response
The upgrade was charged, or the downgrade scheduled.
The outcome of a Wix plan change.
Always true. A failure returns an error status instead.
true
true when the change is a downgrade scheduled for the end of the billing period. Not returned on an upgrade.
true
ID of the invoice the upgrade was charged on. Not returned on a downgrade.
"2025-0001234"
Was this page helpful?