Platform Admin · API Reference
✓ 37 tests passing super_admin only
Base URL — all endpoints prefixed with /api/
AuthAuthorization: Bearer {sanctum_token} on every protected route
Envelope{ "success": true|false, "msg": "...", "data": {}, "pagination": {} }
Seed super adminsuperadmin@engineering.test / Test@1234
Sanctum Token Auth user_type: super_admin required
🔑

Authentication

POST /api/login Public

Authenticate with email/password and receive a Sanctum bearer token. Use this token in all subsequent requests. Super admin accounts also have user_type: super_admin in the response.

Request body
JSON
{
  "email": "superadmin@engineering.test",
  "password": "Test@1234"
}
Response 200
JSON
{
  "success": true,
  "msg": "Logged in",
  "data": {
    "token": "1|abcxyz...",
    "user": {
      "id": 1,
      "username": "superadmin",
      "email": "superadmin@engineering.test",
      "display_name": "Super Admin",
      "user_type": "super_admin",
      "business_id": null,
      "is_active": true
    }
  }
}
Errors
422 — Validation failed (missing fields) 401 — These credentials do not match our records 403 — Account deactivated
POST /api/logout Bearer required

Revokes the current Sanctum token. The token becomes invalid immediately.

Request body

None required — token is read from Authorization header.

Response 200
JSON
{
  "success": true,
  "msg": "Logged out",
  "data": null
}
401 — Unauthenticated (no/invalid token)
📊

Dashboard

GET /api/platform/dashboard Tested

Platform KPI summary. Returns tenant counts, plan breakdown (how many tenants on each plan), and the 5 most recently onboarded tenants. No permission slug required beyond super_admin role.

Response 200
JSON
{
  "success": true,
  "msg": "Dashboard",
  "data": {
    "tenant_count": 3,
    "active_tenant_count": 2,
    "suspended_tenant_count": 1,
    "subscription_plan_count": 2,
    "plan_breakdown": [
      {
        "plan_id": 1,
        "plan_name": "Starter Tier",
        "tenant_count": 1,
        "is_active": true
      },
      {
        "plan_id": 2,
        "plan_name": "Pro",
        "tenant_count": 2,
        "is_active": true
      }
    ],
    "recent_tenants": [
      {
        "id": 3,
        "business_name": "BuildCorp Pakistan",
        "owner_name": "Jahanzaib Khan",
        "contact_email": "jk@buildcorp.test",
        "contact_phone": "+92-300-1234567",
        "subscription_plan_id": 2,
        "plan_name": "Pro",
        "subscription_status": "active",
        "subdomain_slug": "buildcorp-pakistan",
        "is_active": true,
        "created_at": "2026-06-20T10:15:00+00:00"
      }
    ],
    "welcome_message": "Platform dashboard"
  }
}
401 — Unauthenticated 403 — Not super_admin
🏢

Tenants (Business Workspaces)

GET /api/platform/tenants Tested

Paginated list of all tenant businesses. Supports search and filters.

Permission required
tenants.view
Query parameters
ParamTypeRequiredDescription
searchstringoptionalSearches business_name, owner_name, contact_email
subscription_plan_idintegeroptionalFilter by plan ID (returns empty if plan doesn't exist)
is_activebooleanoptionaltrue or false — filter active/inactive
subscription_statusstringoptionalactive | suspended | past_due | cancelled
per_pageintegeroptionalRecords per page (default 20)
Response 200
JSON
{
  "success": true,
  "msg": "Tenants",
  "data": [
    {
      "id": 1,
      "business_name": "Pak Engineering Service",
      "owner_name": "Seed Owner",
      "contact_email": "admin@engineering.test",
      "contact_phone": "+92-300-0000000",
      "subscription_plan_id": 2,
      "plan_name": "Pro",
      "subscription_status": "active",
      "subdomain_slug": "pak-engineering-service",
      "is_active": true
    }
  ],
  "pagination": {
    "current_page": 1,
    "per_page": 20,
    "total": 1,
    "last_page": 1
  }
}
401 — Unauthenticated 403 — Missing tenants.view permission
POST /api/platform/tenants Tested

Invite a new tenant (business). Creates the Business record, auto-generates a unique subdomain_slug, and optionally creates an admin user with a one-time temporary password. Writes a tenant_created audit event.

Permission required
tenants.manage
Request body
FieldTypeRequiredValidation
business_namestringrequiredmax:255
owner_namestringrequiredmax:255
contact_emailstringrequiredvalid email, max:255
contact_phonestringoptionalmax:64
subscription_plan_idintegerrequiredmust exist in t_subscription_plans and be active
create_admin_userbooleanoptionaldefault: true — creates admin user account
JSON — request body
{
  "business_name": "BuildCorp Pakistan",
  "owner_name": "Jahanzaib Khan",
  "contact_email": "jk@buildcorp.test",
  "contact_phone": "+92-300-1234567",
  "subscription_plan_id": 2,
  "create_admin_user": true
}
Response 201
JSON
{
  "success": true,
  "msg": "Tenant invited",
  "data": {
    "id": 3,
    "business_name": "BuildCorp Pakistan",
    "owner_name": "Jahanzaib Khan",
    "contact_email": "jk@buildcorp.test",
    "contact_phone": "+92-300-1234567",
    "subscription_plan_id": 2,
    "plan_name": "Pro",
    "subscription_status": "active",
    "subdomain_slug": "buildcorp-pakistan",
    "is_active": true,
    "admin_invite": {
      "user_id": 8,
      "username": "buildcorppakistan_admin",
      "email": "jk@buildcorp.test",
      "temporary_password": "xK9#mP2vQwLn"
    }
  }
}
temporary_password is shown exactly once in this response. Store it securely and forward to the tenant admin. It cannot be retrieved again.
422 — Validation failed 422 — Subscription plan not found or inactive 403 — Not super_admin or missing permission
GET /api/platform/tenants/{id} Tested

Retrieve a single tenant by ID. Includes subscription plan details.

Permission required
tenants.view
Response 200
JSON
{
  "success": true,
  "msg": "Tenants",
  "data": {
    "id": 3,
    "business_name": "BuildCorp Pakistan",
    "owner_name": "Jahanzaib Khan",
    "contact_email": "jk@buildcorp.test",
    "contact_phone": "+92-300-1234567",
    "subscription_plan_id": 2,
    "plan_name": "Pro",
    "subscription_status": "active",
    "subdomain_slug": "buildcorp-pakistan",
    "is_active": true
  }
}
404 — Not found 403 — Missing tenants.view
PUT /api/platform/tenants/{id} Tested

Update any combination of tenant fields. All fields are optional (partial update). Setting is_active: false while tenant was previously active also logs a tenant_suspended audit event in addition to tenant_updated.

Permission required
tenants.manage
Request body (all optional)
FieldTypeValidation
business_namestringmax:255
owner_namestringmax:255
contact_emailstringvalid email, max:255
contact_phonestring|nullmax:64
subscription_plan_idintegermust exist + be active
subscription_statusstringactive | suspended | past_due | cancelled
is_activebooleantrue | false
JSON — suspend a tenant
{
  "is_active": false,
  "subscription_status": "suspended"
}
Response 200
JSON
{
  "success": true,
  "msg": "Updated",
  "data": {
    "id": 3,
    "business_name": "BuildCorp Pakistan",
    "owner_name": "Jahanzaib Khan",
    "contact_email": "jk@buildcorp.test",
    "contact_phone": "+92-300-1234567",
    "subscription_plan_id": 2,
    "plan_name": "Pro",
    "subscription_status": "suspended",
    "subdomain_slug": "buildcorp-pakistan",
    "is_active": false
  }
}
404 — Tenant not found 422 — Validation failed or inactive plan referenced 403 — Missing tenants.manage
DELETE /api/platform/tenants/{id} Tested

Soft suspend only — does not hard-delete the business. Sets is_active=false and subscription_status=suspended. Business data is preserved. Logs tenant_suspended audit event.

Permission required
tenants.manage
Response 200
JSON
{
  "success": true,
  "msg": "Tenant suspended",
  "data": {
    "id": 3,
    "is_active": false
  }
}
404 — Tenant not found 403 — Missing tenants.manage
📋

Subscription Plans

GET /api/platform/subscription-plans Tested

List all subscription plans ordered by monthly_price ascending. Limits appear as -1 in API for unlimited (stored as 0 in DB).

Permission required
subscription_plans.view
Response 200
JSON
{
  "success": true,
  "msg": "Subscription plans",
  "data": [
    {
      "id": 1,
      "name": "Starter Tier",
      "slug": "starter-tier",
      "monthly_price": 49,
      "max_projects": 5,
      "max_locations": 1,
      "max_employees": 10,
      "has_client_portal": false,
      "has_offline_sync": false,
      "is_active": true
    },
    {
      "id": 2,
      "name": "Pro",
      "slug": "pro",
      "monthly_price": 299,
      "max_projects": -1,
      "max_locations": -1,
      "max_employees": -1,
      "has_client_portal": true,
      "has_offline_sync": true,
      "is_active": true
    }
  ],
  "pagination": {
    "current_page": 1,
    "per_page": 20,
    "total": 2,
    "last_page": 1
  }
}
POST /api/platform/subscription-plans Tested

Create a new subscription plan. slug is auto-generated from name if omitted. Pass -1 for any limit field to mean "unlimited".

Permission required
subscription_plans.manage
Request body
FieldTypeRequiredValidation
namestringrequiredmax:128
slugstringoptionalmax:64, must be unique; auto-generated from name if omitted
monthly_pricedecimalrequiredmin:0
max_projectsintegerrequired-1 = unlimited, or positive integer
max_locationsintegerrequired-1 = unlimited, or positive integer
max_employeesintegerrequired-1 = unlimited, or positive integer
has_client_portalbooleanoptionaldefault false
has_offline_syncbooleanoptionaldefault false
is_activebooleanoptionaldefault true
JSON — request body
{
  "name": "Enterprise",
  "monthly_price": 799,
  "max_projects": -1,
  "max_locations": -1,
  "max_employees": -1,
  "has_client_portal": true,
  "has_offline_sync": true,
  "is_active": true
}
Response 201
JSON
{
  "success": true,
  "msg": "Created",
  "data": {
    "id": 3,
    "name": "Enterprise",
    "slug": "enterprise",
    "monthly_price": 799,
    "max_projects": -1,
    "max_locations": -1,
    "max_employees": -1,
    "has_client_portal": true,
    "has_offline_sync": true,
    "is_active": true
  }
}
422 — Validation failed 422 — Slug already in use
GET /api/platform/subscription-plans/{id} Tested

Retrieve a single plan by ID.

Permission required
subscription_plans.view
Response 200
JSON
{
  "success": true,
  "msg": "Subscription plans",
  "data": {
    "id": 2,
    "name": "Pro",
    "slug": "pro",
    "monthly_price": 299,
    "max_projects": -1,
    "max_locations": -1,
    "max_employees": -1,
    "has_client_portal": true,
    "has_offline_sync": true,
    "is_active": true
  }
}
PUT /api/platform/subscription-plans/{id} Tested

Partial update on any plan field. Logs a plan_updated audit event on success.

Permission required
subscription_plans.manage
JSON — upgrade limits to unlimited
{
  "max_projects": -1,
  "max_locations": -1,
  "has_client_portal": true
}
Response 200
JSON
{
  "success": true,
  "msg": "Updated",
  "data": {
    "id": 1,
    "name": "Starter Tier",
    "slug": "starter-tier",
    "monthly_price": 49,
    "max_projects": -1,
    "max_locations": -1,
    "max_employees": 10,
    "has_client_portal": true,
    "has_offline_sync": false,
    "is_active": true
  }
}
422 — Slug already in use by another plan 404 — Plan not found
DELETE /api/platform/subscription-plans/{id} Tested

Hard-delete the plan if no tenants are assigned. If tenants exist, deactivates the plan instead and returns 422 with error: "tenants_assigned" — this is a safe degradation, not a failure.

Permission required
subscription_plans.manage
Response 200 — plan deleted (no tenants)
JSON
{
  "success": true,
  "msg": "Deleted",
  "data": {
    "id": 3
  }
}
Response 422 — tenants assigned (plan deactivated instead)
JSON
{
  "success": false,
  "msg": "Plan has assigned tenants; deactivated instead of deleted",
  "error": "tenants_assigned",
  "data": {
    "id": 2,
    "name": "Pro",
    "slug": "pro",
    "monthly_price": 299,
    "max_projects": -1,
    "max_locations": -1,
    "max_employees": -1,
    "has_client_portal": true,
    "has_offline_sync": true,
    "is_active": false
  }
}
🔔

Audit Events (Platform Notifications)

GET /api/platform/notifications Tested

Paginated audit event log. Events are ordered newest-first. Filter by read status or category. Supports text search across title, body, and event code.

Permission required
platform_notifications.view
Query parameters
ParamTypeDescription
is_readboolean0/false = unread only, 1/true = read only
categorystringtenant_created | tenant_updated | tenant_suspended | plan_created | plan_updated
searchstringSearches title, body, event_code (EVT-xxxxx)
per_pageintegerDefault 20
Response 200
JSON
{
  "success": true,
  "msg": "Platform notifications",
  "data": [
    {
      "id": 1,
      "event_code": "EVT-00001",
      "category": "tenant_created",
      "severity": "info",
      "title": "New Tenant Registration",
      "body": "BuildCorp Pakistan signed up for the Pro tier.",
      "entity_type": "business",
      "entity_id": 3,
      "actor_user_id": 1,
      "actor_name": "superadmin",
      "metadata": {
        "business_name": "BuildCorp Pakistan",
        "plan_name": "Pro"
      },
      "is_read": false,
      "created_at": "2026-06-20T10:15:01+00:00"
    }
  ],
  "pagination": {
    "current_page": 1,
    "per_page": 20,
    "total": 1,
    "last_page": 1
  }
}
GET /api/platform/notifications/{id} Tested

Retrieve a single audit event.

Permission required
platform_notifications.view
Response 200
JSON
{
  "success": true,
  "msg": "Platform notifications",
  "data": {
    "id": 1,
    "event_code": "EVT-00001",
    "category": "tenant_created",
    "severity": "info",
    "title": "New Tenant Registration",
    "body": "BuildCorp Pakistan signed up for the Pro tier.",
    "entity_type": "business",
    "entity_id": 3,
    "actor_user_id": 1,
    "actor_name": "superadmin",
    "metadata": {
      "business_name": "BuildCorp Pakistan",
      "plan_name": "Pro"
    },
    "is_read": false,
    "created_at": "2026-06-20T10:15:01+00:00"
  }
}
404 — Not found422 — Invalid id (≤ 0)
PATCH /api/platform/notifications/{id}/read Tested

Mark a single audit event as read. No request body required.

Permission required
platform_notifications.manage
Response 200
JSON
{
  "success": true,
  "msg": "Marked read",
  "data": {
    "id": 1,
    "is_read": true
  }
}
PATCH /api/platform/notifications/read-all Tested

Mark all unread audit events as read in a single operation.

Permission required
platform_notifications.manage
Response 200
JSON
{
  "success": true,
  "msg": "All marked read",
  "data": {
    "success": true
  }
}
DELETE /api/platform/notifications/{id} Implemented

Permanently delete a single audit event.

Permission required
platform_notifications.manage
Response 200
JSON
{
  "success": true,
  "msg": "Deleted",
  "data": null
}
404 — Not found