Swarmz

Terminate a tenant

Permanently delete a tenant and all of its infrastructure. Final and irreversible — a missing workspace is treated as idempotent success.

POSThttps://api.swarmz.net/functions/v1/platform-terminate

Final and irreversible. Maps to WHMCS TerminateAccount. client_status is flipped to terminated first so any in-flight request fails fast, then terminateWorkspace runs the full teardown: every project is deleted — cascading the preview pods, published workers, custom domains, and managed Supabase behind them — and finally the workspace row itself is deleted.

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-terminate \
  -H "Authorization: Bearer sk_live_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6" \
  -H "Content-Type: application/json" \
  -d '{ "tenant_id": "aa7d7fae-0386-4d8a-b977-72f856eac242" }'
const res = await fetch('https://api.swarmz.net/functions/v1/platform-terminate', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer sk_live_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ tenant_id: 'aa7d7fae-0386-4d8a-b977-72f856eac242' }),
});

const data = await res.json();
import requests

res = requests.post(
    "https://api.swarmz.net/functions/v1/platform-terminate",
    headers={"Authorization": "Bearer sk_live_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"},
    json={"tenant_id": "aa7d7fae-0386-4d8a-b977-72f856eac242"},
)
data = res.json()

Response

A fresh termination returns the deletion report:

{
  "ok": true,
  "report": {
    "deletedProjects": [],
    "deletedPods": [],
    "deletedDomains": [],
    "deletedCloud": [],
    "errors": []
  }
}

If the tenant is already terminated — or already gone — the call short-circuits:

{ "ok": true, "already": true }
FieldTypeDescription
okbooleanAlways true on success.
alreadybooleanPresent and true when the tenant was already terminated or the workspace row no longer exists. No teardown ran.
reportobjectThe deletion summary. Absent when already: true.
report.deletedProjectsstring[]Projects that were deleted.
report.deletedPodsstring[]Preview pods that were deleted.
report.deletedDomainsstring[]Custom domains that were removed.
report.deletedCloudstring[]Managed-cloud resources that were deleted.
report.errorsstring[]Per-step failures, if any. The status flip to terminated is authoritative even if a teardown step fails.

Errors

StatuserrorreasonWhen
400missing_fieldstenant_id or external_ref requiredNeither identifier supplied (bearer path)
400missing_fieldsaccount_id required for internal terminateInternal-path call with no account_id
401unauthorizedmissing_bearer / invalid_key / account_disabledSee Authentication
405method_not_allowedAnything other than POST
429rate_limitedper_key / per_ipRate limit hit — see Rate limits
500terminate_failedRPC messageThe status-flip RPC (platform_set_client_status) failed; safe to retry
500internal_errorUnhandled server error; safe to retry

Terminate does not return 404

A missing workspace is treated as idempotent success (already: true), not an error — so terminate never returns 404. After termination, other endpoints return 404 tenant_not_found for that id, because the row is deleted rather than marked terminated. Treat 404 and 410 terminated identically: the service is gone, do not retry.

Idempotency

Terminate is idempotent by state. An already-terminated or already-deleted tenant returns already: true. There is no idempotency key — the absence of an active workspace row is the source of truth. See Idempotency.

Notes

  • Address the tenant by tenant_id from create; external_ref is the fallback.
  • Termination deletes the workspace row, so the tenant_id and external_ref both stop resolving afterward. Per the external_ref convention, never reuse that external_ref for a new service.
  • For a reversible pause instead of permanent deletion, use suspend.
  • The account_id parameter and X-Internal-Mint-Secret path exist for the reseller dashboard's server-to-server calls only; host integrations use the bearer key and never send account_id.

On this page