curl --request POST \
--url https://app.base44.com/api/billing/topup/confirm-saved-payment \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"payment_method_id": "pm_1QxYz2AbCdEf3GhIjKlMnOpQ",
"message_credits": 100,
"integration_credits": 1000,
"idempotency_key": "topup-2026-10-25-7f3c9a1e"
}
'import requests
url = "https://app.base44.com/api/billing/topup/confirm-saved-payment"
payload = {
"payment_method_id": "pm_1QxYz2AbCdEf3GhIjKlMnOpQ",
"message_credits": 100,
"integration_credits": 1000,
"idempotency_key": "topup-2026-10-25-7f3c9a1e"
}
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({
payment_method_id: 'pm_1QxYz2AbCdEf3GhIjKlMnOpQ',
message_credits: 100,
integration_credits: 1000,
idempotency_key: 'topup-2026-10-25-7f3c9a1e'
})
};
fetch('https://app.base44.com/api/billing/topup/confirm-saved-payment', 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/topup/confirm-saved-payment",
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([
'payment_method_id' => 'pm_1QxYz2AbCdEf3GhIjKlMnOpQ',
'message_credits' => 100,
'integration_credits' => 1000,
'idempotency_key' => 'topup-2026-10-25-7f3c9a1e'
]),
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/topup/confirm-saved-payment"
payload := strings.NewReader("{\n \"payment_method_id\": \"pm_1QxYz2AbCdEf3GhIjKlMnOpQ\",\n \"message_credits\": 100,\n \"integration_credits\": 1000,\n \"idempotency_key\": \"topup-2026-10-25-7f3c9a1e\"\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/topup/confirm-saved-payment")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"payment_method_id\": \"pm_1QxYz2AbCdEf3GhIjKlMnOpQ\",\n \"message_credits\": 100,\n \"integration_credits\": 1000,\n \"idempotency_key\": \"topup-2026-10-25-7f3c9a1e\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.base44.com/api/billing/topup/confirm-saved-payment")
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 \"payment_method_id\": \"pm_1QxYz2AbCdEf3GhIjKlMnOpQ\",\n \"message_credits\": 100,\n \"integration_credits\": 1000,\n \"idempotency_key\": \"topup-2026-10-25-7f3c9a1e\"\n}"
response = http.request(request)
puts response.read_body{
"status": "charge_accepted",
"invoice_id": "in_1QxYz2AbCdEf3GhIjKlMnOpQ"
}{
"detail": [
{
"type": "string_too_short",
"loc": [
"body",
"idempotency_key"
],
"msg": "String should have at least 8 characters",
"input": "abc",
"ctx": {
"min_length": 8
}
}
]
}Buy top-up with saved card
Buys top-up credits for your workspace by charging a saved payment method, with no checkout page. Get the amounts and prices from List top-up packs. Send at least one of message_credits (a multiple of 50, up to 400) or integration_credits (a multiple of 1,000, up to 8,000), so one purchase buys at most 8 packs of each type.
Get payment_method_id from Get billing info. On a workspace billed through Stripe, use default_card_payment_method_id, the card that pays for the plan, or the id of another card in payment_methods. default_card_payment_method_id is null when the plan isn’t paid by card, and always null on a workspace billed through Wix, so use an id from payment_methods there.
Send a new key (8 to 100 characters) with every purchase, in the Idempotency-Key header or the idempotency_key field. If you send both and they differ, you get 422 idempotency_key_mismatch. On a workspace billed through Stripe, repeating a request with the same key within 24 hours doesn’t charge the card a second time. The repeat doesn’t return the first result, though: once the first request reached the charge step, the repeat fails, and so does reusing a key with different amounts. A key is only remembered for 24 hours, so reusing it later can start a new purchase. On a workspace billed through Wix, don’t rely on the key to prevent a second charge. Before you retry a request whose response you didn’t get, check the workspace’s invoices in Get billing info.
A charge_accepted status means the payment went through. Base44 adds the credits once it processes the payment confirmation, so they can take a few moments to show up. A fallback_required status means the card couldn’t be charged without the card owner, for example because it was declined or the bank asked them to authenticate. Nothing is charged, and the card owner can pay with Create top-up checkout. On a workspace billed through Wix, a declined payment returns a 402 instead, and checkout isn’t available there.
Only the workspace owner can buy top-ups, and the workspace needs a paid plan other than Enterprise that isn’t past due. A workspace with an open payment dispute can’t buy top-ups, and neither can one whose plan is paid through the App Store or Google Play. A workspace billed through Wix can buy them only where Base44 has turned on Wix top-ups. Purchases billed through Stripe in shekels have VAT added. This endpoint is limited to 5 requests an hour per user, and requests it rejects count toward the limit. Some workspaces have a different limit. A read-only token is refused, and workspace API keys aren’t accepted.
curl --request POST \
--url https://app.base44.com/api/billing/topup/confirm-saved-payment \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"payment_method_id": "pm_1QxYz2AbCdEf3GhIjKlMnOpQ",
"message_credits": 100,
"integration_credits": 1000,
"idempotency_key": "topup-2026-10-25-7f3c9a1e"
}
'import requests
url = "https://app.base44.com/api/billing/topup/confirm-saved-payment"
payload = {
"payment_method_id": "pm_1QxYz2AbCdEf3GhIjKlMnOpQ",
"message_credits": 100,
"integration_credits": 1000,
"idempotency_key": "topup-2026-10-25-7f3c9a1e"
}
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({
payment_method_id: 'pm_1QxYz2AbCdEf3GhIjKlMnOpQ',
message_credits: 100,
integration_credits: 1000,
idempotency_key: 'topup-2026-10-25-7f3c9a1e'
})
};
fetch('https://app.base44.com/api/billing/topup/confirm-saved-payment', 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/topup/confirm-saved-payment",
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([
'payment_method_id' => 'pm_1QxYz2AbCdEf3GhIjKlMnOpQ',
'message_credits' => 100,
'integration_credits' => 1000,
'idempotency_key' => 'topup-2026-10-25-7f3c9a1e'
]),
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/topup/confirm-saved-payment"
payload := strings.NewReader("{\n \"payment_method_id\": \"pm_1QxYz2AbCdEf3GhIjKlMnOpQ\",\n \"message_credits\": 100,\n \"integration_credits\": 1000,\n \"idempotency_key\": \"topup-2026-10-25-7f3c9a1e\"\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/topup/confirm-saved-payment")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"payment_method_id\": \"pm_1QxYz2AbCdEf3GhIjKlMnOpQ\",\n \"message_credits\": 100,\n \"integration_credits\": 1000,\n \"idempotency_key\": \"topup-2026-10-25-7f3c9a1e\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.base44.com/api/billing/topup/confirm-saved-payment")
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 \"payment_method_id\": \"pm_1QxYz2AbCdEf3GhIjKlMnOpQ\",\n \"message_credits\": 100,\n \"integration_credits\": 1000,\n \"idempotency_key\": \"topup-2026-10-25-7f3c9a1e\"\n}"
response = http.request(request)
puts response.read_body{
"status": "charge_accepted",
"invoice_id": "in_1QxYz2AbCdEf3GhIjKlMnOpQ"
}{
"detail": [
{
"type": "string_too_short",
"loc": [
"body",
"idempotency_key"
],
"msg": "String should have at least 8 characters",
"input": "abc",
"ctx": {
"min_length": 8
}
}
]
}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 purchase, sent instead of or with the idempotency_key field.
8 - 100Body
Request model for confirming a top-up purchase with a saved payment method.
ID of the saved payment method to charge, from default_card_payment_method_id or payment_methods[].id in Get billing info.
1 - 200"pm_1QxYz2AbCdEf3GhIjKlMnOpQ"
Message credits to buy: a multiple of 50, up to 400. Defaults to 0.
100
Integration credits to buy: a multiple of 1,000, up to 8,000. Defaults to 0.
1000
A key you generate for this purchase. Send a new one for every purchase. Required unless you send the Idempotency-Key header instead.
8 - 100"topup-2026-10-25-7f3c9a1e"
Response
The card was charged, or the card owner has to pay through checkout instead.
charge_accepted when the payment went through. The credits are added shortly after. fallback_required when the card couldn't be charged without the card owner. Nothing is charged, and the card owner can pay with Create top-up checkout.
charge_accepted, fallback_required "charge_accepted"
ID of the invoice for this purchase. Present only when status is charge_accepted.
"in_1QxYz2AbCdEf3GhIjKlMnOpQ"
Why the card couldn't be charged. Present only when status is fallback_required. payment_confirmation_failed when the card was declined or the bank asked the card owner to authenticate. Otherwise it's the invoice's status, such as open.
"payment_confirmation_failed"
Was this page helpful?