Skip to main content
POST /v1/provisioning/clients creates a complete client tenant in a single call: an organization, its workspace, the owner user and membership, and (optionally) an initial tenant API key and an owner invite link. It is the recommended, operator-facing path for onboarding a new client. The underlying mechanism is the onboard_client() SQL function described in Client Provisioning.

Authentication

This endpoint does not use a tenant Bearer JWT or a tenant API key. It authenticates with a dedicated platform provisioning key, sent as a Bearer token:
Authorization: Bearer clp_admin_...
The server stores only the SHA-256 hash of each valid provisioning key, configured in the CAUSELOOP_PROVISION_KEY_HASHES environment variable (comma-separated, so keys can be rotated without downtime). Generate a key and its hash with:
python scripts/gen_provision_key.py
If CAUSELOOP_PROVISION_KEY_HASHES is not configured, the endpoint returns 503 Service Unavailable. Provisioning keys are platform-level credentials — treat them like root. They are separate from, and far more powerful than, tenant API keys.

Endpoint

POST /v1/provisioning/clients
Authorization: Bearer clp_admin_...
Idempotency-Key: <uuid>        # optional but recommended
Content-Type: application/json

Request body

FieldTypeRequiredNotes
organization.namestringYesDisplay name of the organization.
organization.slugstringYesURL-safe, globally unique. Must match ^[a-z0-9][a-z0-9-]*$.
organization.planstringNoOne of free, starter, growth, enterprise. Defaults to free.
organization.seatsintegerNoSeat allotment for the organization.
organization.timezonestringNoIANA timezone (e.g. America/New_York).
workspace.namestringNoWorkspace label. Defaults to the organization name.
owner.emailstringYesOwner’s email. Reused if a user with this email already exists.
owner.namestringNoOwner’s display name. Defaults to the local part of the email.
issue_api_keybooleanNoWhether to mint an initial tenant API key. Defaults to true.
send_owner_invitebooleanNoWhether to create an owner invite link. Defaults to true.

Idempotency

Pass an Idempotency-Key header (a UUID) to make retries safe. A retry with the same key replays the original response byte-for-byte — including the one-time api_key.secret — so a dropped connection never loses the key. See Idempotency for the general mechanism. Without an Idempotency-Key, re-calling with the same slug and owner is still safe: it returns the existing tenant with created: false and api_key: null (the secret is never re-issued).

Example

curl -X POST https://api.causeloop.ai/v1/provisioning/clients \
  -H "Authorization: Bearer $CAUSELOOP_PROVISION_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "organization": {"name": "Acme Corp", "slug": "acme", "plan": "growth", "seats": 25, "timezone": "America/New_York"},
    "owner": {"email": "owner@acme.com", "name": "Jane Doe"}
  }'

Response

On success the endpoint returns the created (or existing) tenant. The api_key.secret is present only on first creation (or on an idempotent replay) and is shown once — store it immediately.
{
  "created": true,
  "organization": {"id": "org_01abc...", "slug": "acme", "name": "Acme Corp", "plan": "growth"},
  "workspace": {"id": "ws_01abc...", "name": "Acme Corp"},
  "owner": {
    "user_id": "usr_01abc...",
    "membership_id": "mem_01abc...",
    "email": "owner@acme.com",
    "role": "owner"
  },
  "api_key": {
    "id": "key_01abc...",
    "secret": "clp_sk_...",
    "scopes": ["issues:read", "issues:write", "..."],
    "note": "Shown once. Store securely; it cannot be retrieved later."
  },
  "owner_invite": {
    "id": "inv_01abc...",
    "url": "https://app.causeloop.ai/invite/...",
    "expires_at": "2026-06-21T10:00:00Z"
  }
}
api_key.secret is returned only once. Causeloop stores only its SHA-256 hash and cannot recover it. If the secret is lost, rotate it through the tenant API key endpoints rather than re-provisioning — see Settings → API keys.

Status codes

StatusMeaning
201 CreatedA new tenant was provisioned. created: true; api_key.secret present (if issue_api_key).
200 OKIdempotent existing tenant — the slug + owner already exist. created: false, api_key: null.
401 UnauthorizedMissing or invalid provisioning key.
409 ConflictThe slug exists with a different owner, or an Idempotency-Key was reused with a different request body.
422 Unprocessable EntityRequest body failed validation (e.g. invalid slug pattern or unknown plan).
503 Service UnavailableNo provisioning key is configured (CAUSELOOP_PROVISION_KEY_HASHES unset).