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.
https://api.swarmz.net/functions/v1/platform-createProvisions 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
}| Field | Type | Description |
|---|---|---|
ok | boolean | Always true on success. |
tenant_id | string (UUID) | The provisioned workspace id. This is the tenant_id you pass to every later endpoint. Store it. |
dashboard_url | string | Ready-to-use dashboard link for the customer: <origin>/dashboard?workspace=<tenant_id>. |
plan | object | Present 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_pending | boolean | Present 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
| Status | error | reason | When |
|---|---|---|---|
| 400 | missing_fields | external_ref required | external_ref absent or not a string |
| 400 | missing_fields | whu.email required | whu.email absent or not a string |
| 400 | invalid_email | — | Email failed the shape check |
| 401 | unauthorized | missing_bearer / invalid_key / account_disabled / account_missing | See Authentication |
| 404 | account_not_found | — | Defensive — your account row was not found |
| 405 | method_not_allowed | — | Anything other than POST |
| 409 | account_inactive | the account status | Your platform account is not active (e.g. draft) |
| 422 | plan_not_found | the plan_code | plan_code did not resolve to a plan under your account |
| 422 | plan_assign_failed | the RPC reason (e.g. plan_account_mismatch) | The plan resolved but could not be applied to the workspace |
| 429 | rate_limited | per_key / per_ip | Rate limit hit — see Rate limits |
| 500 | provision_failed | RPC message or no_workspace_id | Provisioning failed server-side; safe to retry |
| 500 | plan_lookup_failed | RPC message | Resolving the named plan failed server-side; safe to retry |
| 500 | plan_assign_failed | RPC message | Applying the named plan failed server-side; safe to retry |
| 500 | internal_error | — | Unhandled 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_idis the canonical address for the tenant. Persist it and use it on every later call; keepexternal_refas 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_codepath the workspace is created first (with any rawentitlements, 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. entitlementssanitization is forgiving: numeric knobs accept a finite value≥ 0ornull(= unlimited); everything else for that knob is dropped silently rather than failing the call. See Entitlements.initial_promptis 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.
WHMCS module
Resell Swarmz from WHMCS with no code. Install the module, set your API key once, create a server, and every WHMCS service lifecycle event provisions, signs in, suspends, and tears down a tenant for you.
Initial prompt
Capture the first build prompt on your own storefront and have the customer's workspace open mid-build on their first login. Works through the WHMCS module's Prompt Box or a single field on platform-create.