Swarmz

Refresh a tenant's billing cycle

Rollover unused credits and reset the monthly allowance at a billing-cycle boundary. Idempotent per (tenant, cycle anchor).

POSThttps://api.swarmz.net/functions/v1/platform-plan-refresh

Performs 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_credits according to the tenant's entitlements.rollover_months (0 = none/drop at reset, 1 = one cycle, 2 = two cycles),
  • resets included_credits to the tenant's entitlements.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"
  }
}
FieldTypeDescription
okbooleanAlways true on success (including a skipped no-op).
resultobjectThe RPC payload, passed through unchanged.
result.successbooleantrue when the RPC ran (whether it applied or skipped).
result.skippedbooleanPresent and true when the cycle was already refreshed; the grant did not run.
result.included_creditsnumberThe monthly grant the tenant was reset to (= entitlements.monthly_credits).
result.rollover_creditsnumberUnused credits carried into the new cycle, bounded by rollover_months.
result.rollover_monthsnumberThe rollover window in effect (0/1/2).
result.expired_previous_rollovernumberRollover credits that aged out and were dropped this cycle.
result.billing_cycle_startstringThe cycle start now recorded on the tenant.

Errors

StatuserrorreasonWhen
400missing_fieldstenant_id or external_ref requiredNeither identifier supplied
400invalid_cycle_anchorcycle_anchor must be an ISO datecycle_anchor was supplied but is not a parseable date
401unauthorizedmissing_bearer / invalid_key / account_disabledSee Authentication
404tenant_not_foundNo tenant matched the identifier under your account
405method_not_allowedAnything other than POST
410terminatedThe tenant has been terminated
429rate_limitedper_key / per_ipRate limit hit — see Rate limits
500refresh_failedRPC messageThe platform_plan_refresh RPC failed server-side; safe to retry
500internal_errorUnhandled server error; safe to retry

A suspended tenant can still be refreshed

Unlike plan and topup, plan-refresh only blocks a terminated tenant. A suspended tenant is still refreshed — the credit reset is independent of the suspend state — so your renewal cron does not need to special-case suspended services.

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, via plan). See Entitlements and Credits.

On this page