Skip to content

System Admin

Platform-level endpoints for cross-workspace administration. They are gated by the system_admin flag on directus_users — a boolean introduced with the registration lockdown (migration 20260722A-add-system-admin-flag), backfilled for users holding a Directus admin-access policy. All other callers receive 403 { "error": "forbidden" } (unauthenticated callers receive 401).

The flag is exposed on the current-user payload (Directus GET /users/me, consumed by the frontend and by the SDK's client.me()), so UIs can show or hide the admin surface.

Registration lockdown (OPEN_REGISTRATION_ENABLED)

Self-service sign-up is controlled by the OPEN_REGISTRATION_ENABLED environment variable on directus-api. It defaults to disabled; only the exact string "true" enables it.

While disabled (the default):

FlowBehavior
POST /wallethero-api/register without invitation_token403 { "error": "registration_disabled" }
POST /wallethero-api/register with a valid invitation_tokenWorks — the user is created, auto-verified, and joins the inviting workspace
POST /wallethero-api/auth/sso for an existing user (by SSO id or email)Works — login / account linking is unaffected
POST /wallethero-api/auth/sso for a new user with no pending invitation403 { "error": "registration_disabled" }
POST /wallethero-api/auth/sso for a new user with a pending workspace invitation for their emailWorks — the user is created and joins the inviting workspace (no default workspace)
POST /wallethero-api/workspace/createRestricted to system_admin users — others get 403 { "error": "forbidden" }

The variable is declared in directus-api/env.template, wired through directus-api/docker-compose.yml (OPEN_REGISTRATION_ENABLED: ${OPEN_REGISTRATION_ENABLED:-false}), and in Kubernetes read from the optional directus-config key open_registration_enabled (missing key = disabled).

Onboarding flow

With registration locked down, new-customer onboarding is:

  1. A system admin creates the workspace with POST /system/workspaces, passing the owner's email.
  2. If the owner already has an account, they are immediately the workspace creator and admin member (owner: "added").
  3. If not, an admin invitation email is sent (owner: "invited"); the owner registers through the invitation link (/register with invitation_token) or signs in with Google — both paths are allowed by the lockdown because a pending invitation exists.

List All Workspaces

GET /wallethero-api/system/workspaces

Lists every workspace on the platform with member counts and the owner's email (from user_created, falling back to the first admin-role member).

Auth: Bearer token — system_admin only.

Query Parameters

ParameterTypeDefaultDescription
limitnumber50Page size (1–200)
offsetnumber0Rows to skip

Example Request

bash
curl "https://api.wallethero.app/wallethero-api/system/workspaces?limit=50&offset=0" \
  -H "Authorization: Bearer YOUR_TOKEN"

Response (200)

json
{
  "data": [
    {
      "id": "11111111-1111-1111-1111-111111111111",
      "name": "Acme Coffee Rewards",
      "slug": "acme_coffee",
      "date_created": "2026-07-01T10:30:00Z",
      "member_count": 3,
      "owner_email": "[email protected]"
    }
  ],
  "pagination": { "limit": 50, "offset": 0, "total": 128 }
}

owner_email is null when no owner can be determined.


Create Workspace (with optional owner)

POST /wallethero-api/system/workspaces

Creates a workspace via the standard lifecycle (uniqueness checks, admin junction row, data-model seeding) and optionally hands it to an owner.

Auth: Bearer token — system_admin only.

Request Body

FieldTypeRequiredDescription
namestringYesWorkspace display name (1–100 characters)
slugstringYesURL identifier (^[a-z_]+$)
owner_emailstring (email)NoIntended workspace owner

Owner semantics:

  • owner_email belongs to an existing user → that user is the workspace creator and admin member; the acting system admin is not added. Response owner: "added".
  • owner_email is unknown → the workspace is temporarily created with the acting system admin as creator, and an owner-handoff admin invitation is emailed to the address. The intended email is shown as owner_email while pending. On acceptance, user_created transfers to the invited user and the temporary system-admin membership is removed. Response owner: "invited" plus an invitation object.
  • owner_email omitted → the acting system admin is the creator. Response owner: "none".

Example Request

bash
curl -X POST "https://api.wallethero.app/wallethero-api/system/workspaces" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Acme Coffee Rewards", "slug": "acme_coffee", "owner_email": "[email protected]" }'

Response (200)

json
{
  "workspace": {
    "id": "11111111-1111-1111-1111-111111111111",
    "name": "Acme Coffee Rewards",
    "slug": "acme_coffee",
    "date_created": "2026-07-22T10:30:00Z",
    "user_created": "22222222-2222-2222-2222-222222222222"
  },
  "owner": "invited",
  "invitation": {
    "id": "33333333-3333-3333-3333-333333333333",
    "email": "[email protected]",
    "expires_at": "2026-07-29T10:30:00Z"
  }
}

invitation is present only when owner is "invited".

Errors

StatusBodyCause
400INVALID_PAYLOADDuplicate name/slug, invalid slug format
401UNAUTHENTICATEDMissing/invalid token
403{ "error": "forbidden" }Caller is not a system_admin

SDK

typescript
const { data, pagination } = await client.systemAdmin.listWorkspaces({ limit: 50, offset: 0 });

const result = await client.systemAdmin.createWorkspace({
  name: "Acme Coffee Rewards",
  slug: "acme_coffee",
  owner_email: "[email protected]",
});
// result.owner === "added" | "invited" | "none"

WalletHero Documentation