Billing Live
Read-mostly endpoints for the workspace subscription, upcoming invoice, payment method, and invoice history. Subscription changes happen through the dashboard (which talks to Stripe directly). Admin-only.
Current subscription
Returns the active subscription with plan, seats, channel set, included engagements, and current period.
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);{
"id": "uuid",
"plan": "pro",
"seats": 5,
"channels": ["chat", "ticket", "call"],
"engagementsIncluded": 1000,
"currentPeriod": {
"start": "2026-05-01T00:00:00Z",
"end": "2026-05-31T23:59:59Z"
},
"status": "active",
"cancelAtPeriodEnd": false,
"createdAt": "2025-11-10T12:00:00Z"
}Upcoming invoice
Returns the next invoice that will be issued at period end, broken down by line item. Updates live as seats are added, phone numbers are claimed, or engagements accrue.
{
"amountDueCents": 17500,
"currency": "USD",
"lineItems": [
{ "description": "Pro plan x 5 seats", "amountCents": 14500 },
{ "description": "Phone numbers x 2", "amountCents": 2000 },
{ "description": "AI engagement overage x 10", "amountCents": 1000 }
],
"billingDate": "2026-06-01T00:00:00Z"
}Invoice history
Cursor-paginated list of past invoices. Each item includes the Stripe invoice URL and the PDF link.
Payment method
Returns the current default payment method (card brand + last four). The full PCI surface lives in Stripe; we never see your card number. To update, use:
This returns a Stripe SetupIntent client secret. Pass it to Stripe Elements in your dashboard to collect a fresh payment method.
Customer portal
Returns a Stripe billing-portal URL for the workspace owner. Lets admins update card, download invoices, and view tax IDs without leaving message.com's domain (portal opens in a new tab).
Change plan
Switch between plans (Starter, Pro, Enterprise). Upgrade prorates immediately; downgrade applies at period end. Requires plan and seats in the body.
Downgrading below the active agent count is refused with 422 seats_below_active. Suspend or remove agents first.
Cancel
Schedules cancellation at period end. Workspace stays usable until then, then enters a 30-day read-only grace period before hard-deletion.
Errors
| Code | When |
|---|---|
403 forbidden | Agent role is not admin. |
402 payment_required | Latest invoice failed and recovery window has elapsed. |
422 seats_below_active | Downgrade attempted with too few seats for current team. |
Common pitfalls
- Polling
/upcomingfrom every dashboard load. Cache for at least 60 seconds. The value changes only when seats, numbers, or engagements change. - Hard-coding plan IDs. Use the
planstring (starter,pro,enterprise), not Stripe price IDs. We re-map prices over time. - Storing card numbers anywhere on your side. We never see them; you never should either. Use the Stripe Elements path with the SetupIntent.