Swarmz

Create a tenant

Provision a fully-isolated Swarmz workspace for one of your customers. Maps to WHMCS CreateAccount and is idempotent on your external reference.

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

Provisions a new tenant — an isolated Swarmz workspace owned by your platform account — for one of your customers (a web-host user, or WHU). Returns the tenant's tenant_id and a dashboard_url to drop the customer into their dashboard.

This is the only endpoint that does not take a tenant_id, because the tenant does not exist yet. The id it returns is the tenant_id you use on every later call (plan, topup, sso, suspend, …). Set a stable external_ref here so create is safely retryable and so you have a fallback lookup handle if you ever lose the id.

Set a plan with plan_code, not raw entitlements

The supported way to put a tenant on a plan is plan_code — the stable code of one of your named plans. The raw entitlements object is legacy and is no longer sent by the WHMCS module; prefer plan_code. See Entitlements for what a plan's individual knobs mean.

Parameters

Prop

Type

Request

curl -X POST https://api.swarmz.net/functions/v1/platform-create \
  -H "Authorization: Bearer sk_live_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6" \
  -H "Content-Type: application/json" \
  -d '{
    "external_ref": "whmcs:1234",
    "whu": { "email": "alice@example.com", "name": "Alice Martin" },
    "plan_code": "pro-monthly",
    "initial_prompt": "A booking site for my yoga studio with a class schedule and Stripe payments"
  }'
const res = await fetch('https://api.swarmz.net/functions/v1/platform-create', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer sk_live_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    external_ref: 'whmcs:1234',
    whu: { email: 'alice@example.com', name: 'Alice Martin' },
    plan_code: 'pro-monthly',
    initial_prompt:
      'A booking site for my yoga studio with a class schedule and Stripe payments',
  }),
});

const data = await res.json();
// Persist data.tenant_id against your service record.
import requests

res = requests.post(
    "https://api.swarmz.net/functions/v1/platform-create",
    headers={"Authorization": "Bearer sk_live_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"},
    json={
        "external_ref": "whmcs:1234",
        "whu": {"email": "alice@example.com", "name": "Alice Martin"},
        "plan_code": "pro-monthly",
        "initial_prompt": "A booking site for my yoga studio with a class schedule and Stripe payments",
    },
)
data = res.json()
# Persist data["tenant_id"] against your service record.

Response

{
  "ok": true,
  "tenant_id": "aa7d7fae-0386-4d8a-b977-72f856eac242",
  "dashboard_url": "https://swarmz.net/dashboard?workspace=aa7d7fae-0386-4d8a-b977-72f856eac242",
  "plan": { "success": true, "entitlements": { "...": "..." } },
  "initial_prompt_pending": true
}
FieldTypeDescription
okbooleanAlways true on success.
tenant_idstring (UUID)The provisioned workspace id. This is the tenant_id you pass to every later endpoint. Store it.
dashboard_urlstringReady-to-use dashboard link for the customer: <origin>/dashboard?workspace=<tenant_id>.
planobjectPresent only when plan_code was sent and resolved. The verbatim result of platform_assign_plan{ success: true, entitlements: { … } } describing the caps that were applied. Omitted entirely on the no-plan path.
initial_prompt_pendingbooleanPresent only when initial_prompt was sent and stored. true = the prompt is parked and will auto-start the customer's first project on their first entry. Omitted when no prompt was sent, or when a retried create found one already pending/consumed. See Initial prompt.

Where dashboard_url points

The dashboard_url host defaults to the apex https://swarmz.net. It switches to your custom domain only once that domain is fully verified — i.e. your account's custom_domain_status is active. Until then (and if you have no custom domain) the host is the apex. There is no per-slug <your-slug>.swarmz.net host serving the app, so the id-bearing apex URL is the only correct landing.

Errors

StatuserrorreasonWhen
400missing_fieldsexternal_ref requiredexternal_ref absent or not a string
400missing_fieldswhu.email requiredwhu.email absent or not a string
400invalid_emailEmail failed the shape check
401unauthorizedmissing_bearer / invalid_key / account_disabled / account_missingSee Authentication
404account_not_foundDefensive — your account row was not found
405method_not_allowedAnything other than POST
409account_inactivethe account statusYour platform account is not active (e.g. draft)
422plan_not_foundthe plan_codeplan_code did not resolve to a plan under your account
422plan_assign_failedthe RPC reason (e.g. plan_account_mismatch)The plan resolved but could not be applied to the workspace
429rate_limitedper_key / per_ipRate limit hit — see Rate limits
500provision_failedRPC message or no_workspace_idProvisioning failed server-side; safe to retry
500plan_lookup_failedRPC messageResolving the named plan failed server-side; safe to retry
500plan_assign_failedRPC messageApplying the named plan failed server-side; safe to retry
500internal_errorUnhandled server error; safe to retry

Idempotency

Retries are safe — but a retry never re-applies the plan or entitlements

platform-create is idempotent on (account, external_ref). A retried call returns the same tenant_id and dashboard_url, and never double-provisions. The provisioning RPC short-circuits to the existing workspace, so a repeat call with a different plan_code or entitlements returns the existing tenant unchanged — to change a plan after create, use plan.

See Idempotency for the account-wide model and The external_ref convention for how the key is locked.

Notes

  • The id in tenant_id is the canonical address for the tenant. Persist it and use it on every later call; keep external_ref as your stable fallback handle.
  • Once you create with external_ref: "whmcs:1234", that value is locked to this tenant forever — never reuse it for a different service, even after termination.
  • On the plan_code path the workspace is created first (with any raw entitlements, normally none), then the plan's caps overwrite them. The plan is resolved against your account only, so you can never assign another reseller's plan.
  • entitlements sanitization is forgiving: numeric knobs accept a finite value ≥ 0 or null (= unlimited); everything else for that knob is dropped silently rather than failing the call. See Entitlements.
  • initial_prompt is best-effort by design: a storage miss never fails the provision, and a retried create can neither overwrite a still-pending prompt nor re-arm one the customer already consumed. Full lifecycle in Initial prompt.

On this page