Advanced Billing
List Cloud Plans
List the Recurso Cloud plan catalog the calling tenant can be on, mirroring recurso.dev/pricing.
GET
/
v1
/
billing
/
plans
Managed-cloud plan catalog
curl --request GET \
--url https://billing.example.com/v1/billing/plans \
--header 'Authorization: Bearer <token>'import requests
url = "https://billing.example.com/v1/billing/plans"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://billing.example.com/v1/billing/plans', 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://billing.example.com/v1/billing/plans",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://billing.example.com/v1/billing/plans"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://billing.example.com/v1/billing/plans")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://billing.example.com/v1/billing/plans")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"plans": [
{
"tier": "<string>",
"name": "<string>",
"price": "<string>",
"period": "<string>",
"free_note": "<string>",
"features": [
"<string>"
],
"cta": "<string>",
"recommended": true
}
]
}{
"error": {
"code": "validation_failed",
"message": "<string>"
}
}Returns the catalog of plans Recurso itself offers — Self-Hosted, Cloud and
Enterprise — as shown on recurso.dev/pricing. This is Recurso’s own pricing
for your tenant, not the plans you sell to your
customers. The catalog is static data served from the API so the dashboard
and the marketing site never disagree; prices are display strings, not
minor-unit amounts, because Cloud is metered as a percentage of tracked
volume rather than a fixed charge. Pair it with
Get Billing Status to show which tier
the tenant is on.
Errors use the standard envelope — see Errors.
Parameters
This endpoint takes no parameters.Example Request
curl https://api.recurso.dev/v1/billing/plans \
-H "Authorization: Bearer $API_KEY"
Response
The catalog is returned under aplans key (no data envelope).
{
"plans": [
{
"tier": "self_hosted",
"name": "Self-Hosted",
"price": "Free",
"period": "forever",
"free_note": "Unlimited — run it yourself",
"features": [
"Every feature, no paywalled add-ons",
"All gateways + GST/EU/US tax",
"Community support",
"MIT licensed"
],
"cta": "Get started on GitHub",
"recommended": false
},
{
"tier": "cloud",
"name": "Cloud",
"price": "0.4% of volume",
"period": "usage-based",
"free_note": "Free to $10k tracked revenue / mo",
"features": [
"Managed hosting + auto-scaling",
"Daily backups",
"99.9% uptime SLA",
"Email support"
],
"cta": "Start free",
"recommended": true
},
{
"tier": "enterprise",
"name": "Enterprise",
"price": "Custom",
"period": "",
"free_note": "Volume pricing + SOC 2",
"features": [
"Priority support + SLA",
"99.99% uptime",
"SOC 2",
"On-prem option"
],
"cta": "Talk to us",
"recommended": false
}
]
}
Fields
| Field | Type | Description |
|---|---|---|
plans[] | array | One entry per plan, in display order |
plans[].tier | string | Catalog key: self_hosted, cloud or enterprise. This is a display key only — tenants are not yet assigned to paid tiers, so it does not currently appear as plan_tier on Get Billing Status, which today is only trial or free |
plans[].name | string | Display name |
plans[].price | string | Display price, e.g. "Free", "0.4% of volume", "Custom". A string, not a minor-unit amount |
plans[].period | string | Display period qualifier, e.g. "forever", "usage-based"; empty when none applies |
plans[].free_note | string | The free-usage allowance or headline note |
plans[].features[] | array of string | Feature bullets |
plans[].cta | string | Call-to-action label |
plans[].recommended | boolean | Whether the plan is highlighted as the default choice |
Errors
| Status | Code | When | Fix |
|---|---|---|---|
401 | unauthorized | Missing or invalid API key / session | Send Authorization: Bearer $API_KEY |
⌘I
Managed-cloud plan catalog
curl --request GET \
--url https://billing.example.com/v1/billing/plans \
--header 'Authorization: Bearer <token>'import requests
url = "https://billing.example.com/v1/billing/plans"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://billing.example.com/v1/billing/plans', 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://billing.example.com/v1/billing/plans",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://billing.example.com/v1/billing/plans"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://billing.example.com/v1/billing/plans")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://billing.example.com/v1/billing/plans")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"plans": [
{
"tier": "<string>",
"name": "<string>",
"price": "<string>",
"period": "<string>",
"free_note": "<string>",
"features": [
"<string>"
],
"cta": "<string>",
"recommended": true
}
]
}{
"error": {
"code": "validation_failed",
"message": "<string>"
}
}