Api

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.monthly is in cents, and null means the tier is priced per contract — pair it with contactSales and render the ui.custom label rather than a number.
  • A null application limit means "uncapped", never zero. An absent traffic capacity is unknown; custom offers do not inherit platform traffic tiers. annual.enabled is false while annual terms are unpublished, and every price.annual is null to 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

OperationSDKREST API
Read the plan catalog, optionally localized.billing.listPlans(input?)GET /api/billing/plans
Handler authentication
Read current billing state.billing.getState()GET /api/billing/state
billing:read
Verify a checkout and its credit delivery for this organization.billing.getCheckout(input)GET /api/billing/checkout
billing:read
Read compute usage, edge traffic, and bandwidth limits.billing.getResources()GET /api/billing/resources
billing:read
Read subscription and renewal state.billing.getSubscription()GET /api/billing/subscription
billing:read
Create a subscription checkout or subscription change.billing.createSubscription(input)POST /api/billing/subscription
billing:write
Stop subscription renewal at the end of the paid period.billing.cancelSubscription()POST /api/billing/cancel
billing:admin
Resume a scheduled subscription renewal.billing.resumeSubscription()POST /api/billing/resume
billing:admin
Create a credit top-up checkout.billing.createTopup(input)POST /api/billing/topup
billing:write
List available credit packs.billing.listTopupPacks()GET /api/billing/topup-packs
billing:read
Create a billing portal session.billing.createPortal()POST /api/billing/portal
billing:admin
Read billable usage for the requested period.billing.getUsage(input?)GET /api/billing/usage
billing:read
Read detailed allowance allocation and consumption.billing.listAllowanceDetail()GET /api/billing/allowances
billing:read

HTTP endpoints

OperationSDKREST API
Receive authenticated Synquic billing events.HTTP onlyPOST /api/billing/synquic-webhook
Handler authentication
Retired Stripe webhook; returns 410 without changing billing.HTTP onlyPOST /api/billing/webhook/stripe
Handler authentication

On this page