Billing Live
Read the workspace subscription, start a Stripe checkout for a plan or add-on change, open the customer portal, and list past invoices. Workspaces billed through Shopify App Pricing use a separate, webhook-free reconcile path; the Stripe endpoints below refuse to act on a Shopify-billed row. Admin-only.
Current subscription
Returns the subscription row plus resolved entitlements: the effective plan, whether it's locked (a plan the account is no longer eligible to renew into), days left in trial, and the addon set.
Code samples
curl 'https://app.message.com/api/v1/billing/subscription' \
-H 'Authorization: Bearer YOUR_WORKSPACE_JWT'const res = await fetch('https://app.message.com/api/v1/billing/subscription', {
headers: { Authorization: 'Bearer ' + token }
});
const subscription = await res.json();import requests
r = requests.get(
"https://app.message.com/api/v1/billing/subscription",
headers={"Authorization": f"Bearer {token}"},
)
subscription = r.json()require "net/http"
require "json"
uri = URI("https://app.message.com/api/v1/billing/subscription")
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{token}"
http.request(req)
end
subscription = JSON.parse(res.body)<?php
$ctx = stream_context_create([
"http" => ["method" => "GET", "header" => "Authorization: Bearer $token"]
]);
$subscription = json_decode(file_get_contents("https://app.message.com/api/v1/billing/subscription", false, $ctx), true);{
"subscription": {
"id": "uuid",
"plan": "support",
"addons": ["ai"],
"status": "active",
"billingInterval": "monthly",
"billingSource": "stripe",
"currentPeriodStart": "2026-05-01T00:00:00Z",
"currentPeriodEnd": "2026-05-31T23:59:59Z",
"cancelAtPeriodEnd": false,
"createdAt": "2025-11-10T12:00:00Z"
},
"planId": "support",
"locked": false,
"daysLeftInTrial": null,
"addons": ["ai"],
"billingSource": "stripe"
}Invoice history
Returns the workspace's most recent 100 invoice records. There is no separate "upcoming invoice" preview endpoint.
Start checkout / change plan
There is no separate "change plan" action. This one endpoint both starts a plan and changes it: if the workspace already has a live Stripe subscription it's updated in place (prorated); otherwise it returns a hosted Stripe Checkout URL to redirect the admin to. Refused with 409 on a Shopify-billed workspace, which manages its plan through the Shopify listing instead.
{
"planId": "support",
"interval": "monthly",
"aiAddon": true,
"successUrl": "https://app.message.com/settings/billing?checkout=success",
"cancelUrl": "https://app.message.com/settings/billing"
}planId is one of chat, support, business. Enterprise has no Stripe price and is not a checkout target; it's sales-assisted.
Toggle add-ons
Sets the desired full add-on set for the workspace (idempotent, not a delta). Today the only toggleable key is ai ($5/mo). Body: { "addons": ["ai"] }.
Customer portal
Returns a Stripe billing-portal URL for the workspace. Lets admins update the card on file, download invoices, and view tax IDs without leaving Stripe's hosted portal.
Cancel
Schedules cancellation at period end (or, on an active trial, ends it immediately). Also refused on a Shopify-billed workspace.
Errors
| Code | When |
|---|---|
403 admin_required | Agent role is not admin. |
400 invalid_body | Payload failed schema validation. |
409 | Checkout or cancel attempted on a Shopify-billed workspace. |
Common pitfalls
- Looking for a GET payment-method endpoint. There isn't one. Card details live entirely in Stripe; send admins to the customer portal to view or change them.
- Hard-coding plan IDs. Use
chat,support, orbusiness, not Stripe price IDs. Prices are looked up by interval server-side. - Assuming Stripe endpoints work for every workspace. A Shopify-billed workspace manages its subscription through the Shopify App Pricing listing; checkout and cancel both refuse to act on it.