Refresh a tenant's billing cycle
Rollover unused credits and reset the monthly allowance at a billing-cycle boundary. Idempotent per (tenant, cycle anchor).
https://api.swarmz.net/functions/v1/platform-plan-refreshPerforms a host-driven monthly reset on a tenant at a billing-cycle boundary. Called from your renewal or cycle hook (for example, WHMCS InvoicePaid or a renewal cron). It drives the platform_plan_refresh RPC, which:
- rolls over unused
included_creditsaccording to the tenant'sentitlements.rollover_months(0= none/drop at reset,1= one cycle,2= two cycles), - resets
included_creditsto the tenant'sentitlements.monthly_credits, and - resets the daily-free and monthly-free-cap counters.
This is deliberately separate from plan: a renewal cron can reset credits without any risk of touching the plan configuration. Address the tenant by tenant_id — the id returned by create — or fall back to your external_ref. Supply exactly one identifier.
Parameters
Prop
Type
Request
curl -X POST https://api.swarmz.net/functions/v1/platform-plan-refresh \
-H "Authorization: Bearer sk_live_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6" \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "aa7d7fae-0386-4d8a-b977-72f856eac242",
"cycle_anchor": "2026-06-01T00:00:00.000Z"
}'const res = await fetch('https://api.swarmz.net/functions/v1/platform-plan-refresh', {
method: 'POST',
headers: {
Authorization: 'Bearer sk_live_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6',
'Content-Type': 'application/json',
},
body: JSON.stringify({
tenant_id: 'aa7d7fae-0386-4d8a-b977-72f856eac242',
cycle_anchor: '2026-06-01T00:00:00.000Z',
}),
});
const data = await res.json();import requests
res = requests.post(
"https://api.swarmz.net/functions/v1/platform-plan-refresh",
headers={"Authorization": "Bearer sk_live_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"},
json={
"tenant_id": "aa7d7fae-0386-4d8a-b977-72f856eac242",
"cycle_anchor": "2026-06-01T00:00:00.000Z",
},
)
data = res.json()Response
The edge function returns { ok, result } where result is the verbatim payload of the platform_plan_refresh RPC. A fresh refresh applies the reset and reports the new balances and cycle start:
{
"ok": true,
"result": {
"success": true,
"included_credits": 500,
"rollover_credits": 120,
"rollover_months": 1,
"expired_previous_rollover": 0,
"billing_cycle_start": "2026-06-01T00:00:00.000Z"
}
}If the same (tenant, cycle_anchor) has already been refreshed (the tenant's billing_cycle_start is already >= cycle_anchor), the RPC short-circuits and nothing is granted:
{
"ok": true,
"result": {
"success": true,
"skipped": true,
"reason": "already_refreshed_for_cycle",
"billing_cycle_start": "2026-06-01T00:00:00.000Z"
}
}| Field | Type | Description |
|---|---|---|
ok | boolean | Always true on success (including a skipped no-op). |
result | object | The RPC payload, passed through unchanged. |
result.success | boolean | true when the RPC ran (whether it applied or skipped). |
result.skipped | boolean | Present and true when the cycle was already refreshed; the grant did not run. |
result.included_credits | number | The monthly grant the tenant was reset to (= entitlements.monthly_credits). |
result.rollover_credits | number | Unused credits carried into the new cycle, bounded by rollover_months. |
result.rollover_months | number | The rollover window in effect (0/1/2). |
result.expired_previous_rollover | number | Rollover credits that aged out and were dropped this cycle. |
result.billing_cycle_start | string | The cycle start now recorded on the tenant. |
Errors
| Status | error | reason | When |
|---|---|---|---|
| 400 | missing_fields | tenant_id or external_ref required | Neither identifier supplied |
| 400 | invalid_cycle_anchor | cycle_anchor must be an ISO date | cycle_anchor was supplied but is not a parseable date |
| 401 | unauthorized | missing_bearer / invalid_key / account_disabled | See Authentication |
| 404 | tenant_not_found | — | No tenant matched the identifier under your account |
| 405 | method_not_allowed | — | Anything other than POST |
| 410 | terminated | — | The tenant has been terminated |
| 429 | rate_limited | per_key / per_ip | Rate limit hit — see Rate limits |
| 500 | refresh_failed | RPC message | The platform_plan_refresh RPC failed server-side; safe to retry |
| 500 | internal_error | — | Unhandled server error; safe to retry |
Idempotency
Refresh is idempotent on (tenant, cycle_anchor). The idempotency is keyed on cycle_anchor, not a separate header: a second call with the same anchor short-circuits, returns skipped: true, and never double-grants credits or applies rollover twice for the same cycle. See Idempotency.
Notes
The host owns the billing cycle
Pass the cycle_anchor that matches the service's next-due-date in your billing
system. A renewal cron can then safely replay this call — duplicate hits with
the same anchor short-circuit, and the first successful refresh per cycle is the
one that lands. Omit cycle_anchor only for an ad-hoc manual reset using the
current time.
- Rollover and the monthly grant are driven by the tenant's stored entitlements (
monthly_credits,rollover_months) — set those via the assigned plan (or, legacy, viaplan). See Entitlements and Credits.
Change a tenant's plan
Move a tenant onto a different named plan, or patch its raw entitlements. Maps to WHMCS ChangePackage. Addressed by tenant_id.
List your plans
Enumerate the named plans defined for your platform account. Each plan carries a stable code you map to a WHMCS product and later pass to create or plan.