curl --request POST \
--url https://api.simpl.gg/v1/billing/downgrade \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{
"plan": "payg"
}'import requests
url = "https://api.simpl.gg/v1/billing/downgrade"
payload = { "plan": "payg" }
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({plan: 'payg'})
};
fetch('https://api.simpl.gg/v1/billing/downgrade', 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://api.simpl.gg/v1/billing/downgrade",
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([
'plan' => 'payg'
]),
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://api.simpl.gg/v1/billing/downgrade"
payload := strings.NewReader("{\n \"plan\": \"payg\"\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://api.simpl.gg/v1/billing/downgrade")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"plan\": \"payg\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.simpl.gg/v1/billing/downgrade")
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 \"plan\": \"payg\"\n}"
response = http.request(request)
puts response.read_body{
"plan": "free",
"limits": {
"max_concurrent_servers": 1,
"max_tier": "1x-2g",
"max_server_ttl": 1800,
"regions": [
"iad"
],
"max_builds": 5,
"queues_enabled": true,
"lobbies_enabled": true,
"matchmaking_enabled": true,
"payment_method_required": true
},
"current_period_start": "2023-11-07T05:31:56Z",
"current_period_end": "2023-11-07T05:31:56Z",
"pending_plan": "free",
"payment_method": {
"brand": "visa",
"last_four": "4242",
"exp_month": 12,
"exp_year": 2030
}
}Downgrade the plan
Schedules a move to a lower plan. Unlike an upgrade, a downgrade does not take effect immediately.
Pro to PAYG carries a grace period. Queues, lobbies and matchmaking
keep working until current_period_end, then switch off automatically.
The account is not cut off mid-cycle for capability it has already paid
for, and a live game does not lose matchmaking the moment someone clicks
downgrade. pending_plan reports what is coming; upgrading back to the
current plan before the cycle ends cancels it.
Downgrading to free while usage exceeds free limits is accepted, but
once it takes effect the account cannot start servers beyond one
concurrent, tier 1x-1g, a 30 minute TTL, or outside iad. Servers
already running are not killed by the change.
Downgrading to the plan already active returns PLAN_ALREADY_ACTIVE
(8004).
curl --request POST \
--url https://api.simpl.gg/v1/billing/downgrade \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{
"plan": "payg"
}'import requests
url = "https://api.simpl.gg/v1/billing/downgrade"
payload = { "plan": "payg" }
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({plan: 'payg'})
};
fetch('https://api.simpl.gg/v1/billing/downgrade', 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://api.simpl.gg/v1/billing/downgrade",
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([
'plan' => 'payg'
]),
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://api.simpl.gg/v1/billing/downgrade"
payload := strings.NewReader("{\n \"plan\": \"payg\"\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://api.simpl.gg/v1/billing/downgrade")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"plan\": \"payg\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.simpl.gg/v1/billing/downgrade")
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 \"plan\": \"payg\"\n}"
response = http.request(request)
puts response.read_body{
"plan": "free",
"limits": {
"max_concurrent_servers": 1,
"max_tier": "1x-2g",
"max_server_ttl": 1800,
"regions": [
"iad"
],
"max_builds": 5,
"queues_enabled": true,
"lobbies_enabled": true,
"matchmaking_enabled": true,
"payment_method_required": true
},
"current_period_start": "2023-11-07T05:31:56Z",
"current_period_end": "2023-11-07T05:31:56Z",
"pending_plan": "free",
"payment_method": {
"brand": "visa",
"last_four": "4242",
"exp_month": 12,
"exp_year": 2030
}
}Authorizations
Developer session token from POST /v1/auth/login or
POST /v1/auth/register. Valid 7 days.
Authenticates the developer control plane. It also reaches the server
and build endpoints, which a dashboard has to drive and which no
session can otherwise call: an API key's value is returned once, at
mint, so nothing can read back a key for an existing project. On those
endpoints a session names its project with project_id, or is
resolved from the resource it addresses, and is always checked against
the account that owns it.
It reaches no player-facing game endpoint.
Body
Billing plan for the account. Plan gates capability, not just price.
| free | payg | pro | |
|---|---|---|---|
| price | $0 | usage only | $10/mo + usage |
| server credit | none | none | $5/mo |
| max concurrent servers | 1 | unlimited | unlimited |
| max tier | 1x-1g | all 12 | all 12 |
| max server ttl | 30 min | unlimited | unlimited |
| regions | iad only | all 16 | all 16 |
| max builds | 5 | unlimited | unlimited |
| queues | no | no | yes |
| lobbies | no | no | yes |
| matchmaking | no | no | yes |
| payment method | not required | required | required |
free, payg, pro "free"
Response
The plan, with pending_plan set to the scheduled target.
The account's current plan, what it allows, and where the billing cycle
sits. Plan changes go through POST /v1/billing/upgrade and
POST /v1/billing/downgrade.
Billing plan for the account. Plan gates capability, not just price.
| free | payg | pro | |
|---|---|---|---|
| price | $0 | usage only | $10/mo + usage |
| server credit | none | none | $5/mo |
| max concurrent servers | 1 | unlimited | unlimited |
| max tier | 1x-1g | all 12 | all 12 |
| max server ttl | 30 min | unlimited | unlimited |
| regions | iad only | all 16 | all 16 |
| max builds | 5 | unlimited | unlimited |
| queues | no | no | yes |
| lobbies | no | no | yes |
| matchmaking | no | no | yes |
| payment method | not required | required | required |
free, payg, pro "free"
The capability ceiling applied by the account's current plan.
Show child attributes
Show child attributes
Set when a downgrade is scheduled. The account keeps its current
plan's capability until current_period_end, then moves to this plan
automatically.
free, payg, pro "free"
The card on file, as reported by Stripe. Simpl never stores or sees a card number.
Show child attributes
Show child attributes