message.comDevelopers

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

GET/api/v1/billing/subscriptionAuth: Bearer

Returns the subscription row plus resolved entitlements: the effective plan, whether access is locked after trial expiry, days left in trial, and the addon set.

Code samples

cURL
curl 'https://app.message.com/api/v1/billing/subscription' \
  -H 'Authorization: Bearer YOUR_WORKSPACE_JWT'
JavaScript
const res = await fetch('https://app.message.com/api/v1/billing/subscription', {
  headers: { Authorization: 'Bearer ' + token }
});
const subscription = await res.json();
Python
import requests
r = requests.get(
    "https://app.message.com/api/v1/billing/subscription",
    headers={"Authorization": f"Bearer {token}"},
)
subscription = r.json()
Ruby
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
<?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);
200 OK
{
  "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

GET/api/v1/billing/invoicesAuth: Bearer

Returns the workspace's most recent 100 invoice records. There is no separate "upcoming invoice" preview endpoint.

Start checkout / change plan

POST/api/v1/billing/checkoutAuth: Bearer

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 Message.com checkout-page URL where the administrator reviews and pays inside the dashboard. Reopening the same unpaid plan reuses the saved checkout. Refused with 409 on a Shopify-billed workspace, which manages its plan through the Shopify listing instead.

Body
{
  "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.

Read an existing payment

GET/api/v1/billing/checkout/:idAuth: Bearer

Administrator-only and workspace-scoped. Returns the embedded checkout configuration plus paid, credited and status. A completed Stripe payment is not treated as activated until its subscription is recorded. This read never creates or confirms a payment. Client secrets must not be logged or shared.

Toggle add-ons

POST/api/v1/billing/addonsAuth: Bearer

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"] }.

Saved payment methods

GET/api/v1/billing/cardsAuth: Bearer

Returns a cards array with id, brand, last4, expMonth, expYear and isDefault. Full card numbers and CVCs are never returned.

POST/api/v1/billing/cards/setupAuth: Bearer

Send a new UUID in requestId for each intentional card setup. Retries reuse that same UUID. The response includes clientSecret and publishableKey for secure Stripe card fields inside Message.com. This saves a payment method; it does not purchase a plan or add credits. The endpoint allows ten requests per hour.

POST/api/v1/billing/cards/defaultAuth: Bearer

Send paymentMethodId from the workspace’s saved-card list. A card outside this workspace is rejected. Administrators can manage cards in Settings → Billing → Payment method without leaving Message.com.

Customer portal

POST/api/v1/billing/portalAuth: Bearer

Compatibility endpoint returning a Stripe billing-portal URL. The current dashboard has its own payment-method and invoice pages; new integrations should use those pages or the dedicated endpoints above.

Cancel

POST/api/v1/billing/cancelAuth: Bearer

Schedules cancellation at period end (or, on an active trial, ends it immediately). Also refused on a Shopify-billed workspace.

Errors

CodeWhen
403 admin_requiredAgent role is not admin.
400 invalid_bodyPayload failed schema validation.
409Checkout or cancel attempted on a Shopify-billed workspace.

Common pitfalls

  • Handling raw card data. Use secure payment fields and the saved-card endpoints. Never send full card numbers or CVCs to Message.com API routes.
  • Hard-coding plan IDs. Use chat, support, or business, 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.