Billing
Read plans and manage subscriptions, credits, and usage.
Hosted-cloud only, switched off in Synq
These routes are for hosted-cloud plans, subscriptions, credits and payments. The hosted cloud is switched off in Synq, so the dashboard never offers them and a Synq install has nothing to connect to or pay for. They are documented here because the routes still exist in the code.
Billing covers plans, subscriptions, credits, and usage. Plan listing is public on every installation. Synq defines its prices, namespace credits, limits and checkout copy. Synquic collects the payment and applies the saved offer on payment and renewal. Every organization maps to one immutable provider namespace resolved by Synq. Account operations require configured Cloud billing. Self-hosted HTTP routes can proxy a connected user's Cloud billing account; fixed SDK scopes require a direct compatible Cloud API connection. See Cloud compatibility.
List plans
const catalog = await ship.billing.listPlans();
console.log(catalog.plans);curl "$SYNQ_URL/api/billing/plans"Public on every deploy mode. The response is the resolved pricing catalog: one entry per tier in display order, with prices in the currency's minor units (cents), the numeric limits enforcement reads, and finished localized feature strings.
Pass ?locale=<lang> — or send an Accept-Language header — to pick the
language. An unsupported tag falls back to its base language, then to English;
the locale that was actually used comes back in the response.
The envelope is { data: { provider: "synquic", locale, annual, ui, plans } }. annual.enabled reports
whether annual terms are published; read the actual yearly price from each plan.
ui carries the short labels a pricing UI needs — the /mo suffix, the
"Custom" and "Free" words, the "Most popular" badge — already translated, because
a marketing site may have no dictionary of its own.
Each entry in plans looks like:
Prop
Type
Two conventions matter when you render this:
price.monthlyis in cents, andnullmeans the tier is priced per contract — pair it withcontactSalesand render theui.customlabel rather than a number.- A
nullapplication limit means "uncapped", never zero. An absent traffic capacity is unknown; custom offers do not inherit platform traffic tiers.annual.enabledisfalsewhile annual terms are unpublished, and everyprice.annualisnullto match — keep annual affordances hidden while it is.
Prices, credits, packs and limits come from Synq's server catalog in
@repo/core. The API, dashboard and pricing page read the same
published catalog. Monthly paid plans are $10 / $39 / $99; annual checkout is
hidden until explicit yearly terms are published. Linked local installations
read the SaaS public catalog without sending local credentials.
New checkouts send a generic Synquic offer. It contains the server-selected
price, credits, grace and VM caps; versioned metadata records the Synq plan,
application limits, organization and immutable namespace. Customers cannot
submit their own price, offer or namespace. Existing Synquic catalog subscriptions
remain readable, while all new purchases use Synq's catalog.
The payment funds the reseller's Synquic wallet. It does not change the reseller account's platform subscription. Each customer subscription belongs to its namespace. Renewals use the saved offer and application limits, so catalog edits do not change existing customers' terms. Grace defaults to zero and can be configured in the catalog. Credits are independent of USD price and are not a promise of fixed CPU minutes.
Workspace count is a namespace allowance; CPU, RAM and disk caps apply to each
VM. Synq submits billing.resourceLimits directly from its catalog. VM size
fields default to null, which inherits Synquic capacity; a reseller may declare
stricter caps. Synquic resolves account, platform and saved customer limits and
enforces them on VM operations. Synq does not calculate CPU/RAM budgets,
fetch account quota to fit an offer, or rewrite saved purchase terms on renewal.
One Compose project must fit one VM; services can span multiple projects.
Subscription and usage
const state = await ship.billing.getState();
console.log(state);curl "$SYNQ_URL/api/billing/state" \
-H "Authorization: Bearer $SYNQ_TOKEN"GET /state is the snapshot the dashboard's Billing screen renders in one call:
the active tier, the period you're in, the credit balance, and how much of the
plan's capacity is in use. GET /usage returns the metered series behind the
usage chart. GET /subscription returns the entitlement tier/status/period and
a subscription object, or null if never subscribed. The object includes
tier, status, interval (monthly or annual), currentPeriod,
cancelAtPeriodEnd, and canceledAt. A scheduled cancellation can still have
an active entitlement until the paid period ends.
POST /subscription accepts { planTierId, interval, idempotencyKey? } and returns
{ checkoutUrl }. Use a stable request key when retrying the same purchase.
Synquic replaces only this organization's subscription after payment and starts
a new cycle at the full selected price, without automatic proration. A browser
return URL does not grant access: signed Synquic events and fresh entitlement
reads confirm the current plan.
POST /cancel stops renewal at period end and returns { cancelAt, subscription }.
POST /resume undoes a scheduled cancellation and returns { subscription }.
Both are safe to repeat. Cancellation preserves the paid period; it does not
delete resources. An ended subscription requires a new checkout.
await ship.billing.cancelSubscription();
const { subscription } = await ship.billing.getSubscription();
console.log(subscription?.cancelAtPeriodEnd);
await ship.billing.resumeSubscription();Verify a checkout
const checkout = await ship.billing.getCheckout({ checkoutId: 'cs_...' });GET /api/billing/checkout?checkoutId=cs_... requires billing:read. Synq
resolves the current organization's namespace before asking Synquic about that
specific session. Another organization's checkout cannot be read. The response
contains id, kind, status, paymentStatus, fulfilled,
fulfillmentStatus and creditsGranted (milli-credits), without reseller wallet
or payment-provider identifiers.
The success URL includes session_id. A return URL or a previously active
subscription is not proof of the new payment. The dashboard confirms only a
paid checkout whose fulfillment completed with a positive net credit grant;
subscription purchases also require the expected tier and interval to be active.
Unfinished delivery stays pending, while refunds, disputes and failures are
shown explicitly. Webhook events and fresh provider reads remain authoritative.
Resource usage
const resources = await ship.billing.getResources();
if (resources.compute.status === "available") {
console.log(resources.compute.cpuHours, resources.compute.memoryGbHours);
}
if (resources.edge.status === "available") {
console.log(resources.edge.bandwidthGb, resources.edge.limits.bandwidthGb);
console.log(resources.edge.requests);
}curl "$SYNQ_URL/api/billing/resources" \
-H "Authorization: Bearer $SYNQ_TOKEN"Requires billing:read. Synq resolves the authenticated organization's
namespace; callers do not supply a namespace or another customer's account.
The REST response wraps the resource snapshot in { data }.
compute reports vCPU-hours, memory GB-hours, disk I/O GB, and network GB across
builds and running apps in the billing period. These measurements consume the
shared Cloud allowance; they are not separate included runtime-hour quotas.
edge reports request counts, inbound and outbound bandwidth, total bandwidth,
and the monthly bandwidth limit in limits.bandwidthGb. Each group includes
its own period, since edge allowances reset monthly even on annual plans.
Read each group's status before displaying its measurements. An unavailable
provider returns "unavailable" and null measurements for that group while
the other group can remain available. A null measurement does not mean zero
usage or unlimited resources. measuredAt identifies the snapshot time.
Top-ups and the portal
GET /topup-packs lists the one-shot credit packs from the same catalog;
POST /topup opens Checkout for one. Credits bought this way sit on top of the
plan's allowance; only the unused purchased remainder survives renewal.
Top-up eligibility in checkout and billing state requires an active or trialing
paid subscription and a current Synquic entitlement of active or credit_exhausted. An expired or
past-due entitlement cannot qualify through a stale active subscription record;
an unavailable entitlement read blocks the purchase.
A top-up cannot increase resource/project/build limits or activate a subscription.
Synq never resets or grants credits itself.
POST /portal returns { portalUrl } for this organization's invoices, payment
methods, and cancellation. It requires billing:admin, like cancel/resume,
because the hosted portal can stop renewal. It never exposes the API-key owner's
billing portal. Shared legacy billing must be separated before management or
purchases can proceed.
Portal/cancel/resume remain available when new purchases are disabled. The
capabilities object in billing state reports which actions are available.
Organization deletion still requires support to coordinate provider billing
closure and resource cleanup.
Operations
Resource methods
| Operation | SDK | REST API |
|---|---|---|
| Read the plan catalog, optionally localized. | billing.listPlans(input?) | GET /api/billing/plansHandler authentication |
| Read current billing state. | billing.getState() | GET /api/billing/statebilling:read |
| Verify a checkout and its credit delivery for this organization. | billing.getCheckout(input) | GET /api/billing/checkoutbilling:read |
| Read compute usage, edge traffic, and bandwidth limits. | billing.getResources() | GET /api/billing/resourcesbilling:read |
| Read subscription and renewal state. | billing.getSubscription() | GET /api/billing/subscriptionbilling:read |
| Create a subscription checkout or subscription change. | billing.createSubscription(input) | POST /api/billing/subscriptionbilling:write |
| Stop subscription renewal at the end of the paid period. | billing.cancelSubscription() | POST /api/billing/cancelbilling:admin |
| Resume a scheduled subscription renewal. | billing.resumeSubscription() | POST /api/billing/resumebilling:admin |
| Create a credit top-up checkout. | billing.createTopup(input) | POST /api/billing/topupbilling:write |
| List available credit packs. | billing.listTopupPacks() | GET /api/billing/topup-packsbilling:read |
| Create a billing portal session. | billing.createPortal() | POST /api/billing/portalbilling:admin |
| Read billable usage for the requested period. | billing.getUsage(input?) | GET /api/billing/usagebilling:read |
| Read detailed allowance allocation and consumption. | billing.listAllowanceDetail() | GET /api/billing/allowancesbilling:read |
HTTP endpoints
| Operation | SDK | REST API |
|---|---|---|
| Receive authenticated Synquic billing events. | HTTP only | POST /api/billing/synquic-webhookHandler authentication |
| Retired Stripe webhook; returns 410 without changing billing. | HTTP only | POST /api/billing/webhook/stripeHandler authentication |