curl --request POST \
--url https://api.mixpeek.com/v1/organizations/billing/portal \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"return_url": "https://studio.mixpeek.com/billing"
}
'import requests
url = "https://api.mixpeek.com/v1/organizations/billing/portal"
payload = { "return_url": "https://studio.mixpeek.com/billing" }
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({return_url: 'https://studio.mixpeek.com/billing'})
};
fetch('https://api.mixpeek.com/v1/organizations/billing/portal', 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.mixpeek.com/v1/organizations/billing/portal",
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([
'return_url' => 'https://studio.mixpeek.com/billing'
]),
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.mixpeek.com/v1/organizations/billing/portal"
payload := strings.NewReader("{\n \"return_url\": \"https://studio.mixpeek.com/billing\"\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.mixpeek.com/v1/organizations/billing/portal")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"return_url\": \"https://studio.mixpeek.com/billing\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.mixpeek.com/v1/organizations/billing/portal")
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 \"return_url\": \"https://studio.mixpeek.com/billing\"\n}"
response = http.request(request)
puts response.read_body{
"portal_url": "https://billing.stripe.com/p/session/live_abc123",
"session_id": "bps_live_abc123"
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}Open Billing Portal
Open the Stripe customer billing portal.
This is the correct destination for a “Manage subscription” action: the portal lets an existing customer update their card, view/download invoices, and change or cancel their plan. It is NOT the same as POST /subscribe — re-running /subscribe for an existing subscriber starts a brand-new checkout instead of managing the current one.
Requires an existing Stripe customer. Orgs that have never started billing have nothing to manage, so they get an actionable 400 pointing at /subscribe.
curl --request POST \
--url https://api.mixpeek.com/v1/organizations/billing/portal \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"return_url": "https://studio.mixpeek.com/billing"
}
'import requests
url = "https://api.mixpeek.com/v1/organizations/billing/portal"
payload = { "return_url": "https://studio.mixpeek.com/billing" }
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({return_url: 'https://studio.mixpeek.com/billing'})
};
fetch('https://api.mixpeek.com/v1/organizations/billing/portal', 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.mixpeek.com/v1/organizations/billing/portal",
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([
'return_url' => 'https://studio.mixpeek.com/billing'
]),
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.mixpeek.com/v1/organizations/billing/portal"
payload := strings.NewReader("{\n \"return_url\": \"https://studio.mixpeek.com/billing\"\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.mixpeek.com/v1/organizations/billing/portal")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"return_url\": \"https://studio.mixpeek.com/billing\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.mixpeek.com/v1/organizations/billing/portal")
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 \"return_url\": \"https://studio.mixpeek.com/billing\"\n}"
response = http.request(request)
puts response.read_body{
"portal_url": "https://billing.stripe.com/p/session/live_abc123",
"session_id": "bps_live_abc123"
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}Authorizations
Mixpeek API key, sent as Authorization: Bearer mxp_sk_.... Create one in Studio under Settings → API Keys, or with an admin key via POST /v1/organizations/users/{user_email}/api-keys. A missing header returns 403; an invalid or revoked key returns 401.
Body
Request to open the Stripe customer billing portal.
URL Stripe redirects back to when the customer leaves the portal
Response
Successful Response
Response with the Stripe-hosted billing portal URL.
The portal lets an existing customer update their card, view invoices, and change or cancel their subscription — distinct from /subscribe, which starts a NEW checkout (re-running checkout for an existing subscriber is wrong).
Was this page helpful?

