Platform Admin · API Reference
⚡ Super Admin Only Platform-wide scope
Base URL/api/platform/ for all endpoints on this page
AuthAuthorization: Bearer {sanctum_token} · user_type must be platform_admin
GuardrejectUnlessSuperAdmin() on every endpoint — additional permission checks per operation (see below)
ScopeNo business_id filter — Platform Admin sees all tenants globally
Envelope{ "success": true, "msg": "...", "data": {...} } — paginated responses include "pagination": {...}
📊

Dashboard

GET /api/platform/dashboard Super Admin

Platform-wide KPI summary. Returns global tenant counts, active subscription plan breakdown, and the 5 most recently joined businesses. No business_id filter — data spans all tenants.

Auth guard
rejectUnlessSuperAdmin()
No additional permission required
Response 200
JSON
{
  "success": true,
  "msg": "Dashboard",
  "data": {
    "tenant_count": 12,
    "active_tenant_count": 10,
    "suspended_tenant_count": 2,
    "subscription_plan_count": 3,
    "plan_breakdown": [
      { "plan_id": 1, "plan_name": "Starter", "tenant_count": 4, "is_active": true },
      { "plan_id": 2, "plan_name": "Professional", "tenant_count": 6, "is_active": true },
      { "plan_id": 3, "plan_name": "Enterprise", "tenant_count": 2, "is_active": true }
    ],
    "recent_tenants": [
      {
        "id": 12, "business_name": "Al-Noor Engineering",
        "subscription_plan": { "name": "Professional" },
        "is_active": true, "created_at": "2024-03-15T10:23:44+05:00"
      }
    ],
    "welcome_message": "Platform dashboard"
  }
}
Errors
401 Unauthenticated403 Not platform_admin
🏚

Tenants

GET/api/platform/tenants

Paginated list of all business tenants on the platform. Optionally filter by plan, active status, or subscription status.

Permission required
tenants.view
Query parameters
ParamTypeDescription
subscription_plan_idintFilter by plan ID. If plan does not exist, returns empty result.
is_activebooltrue / false — filter by active/suspended
subscription_statusstringactive | suspended | past_due | cancelled
per_pageintDefault 20
Response 200
JSON
{
  "success": true, "msg": "Tenants",
  "data": [{
    "id": 3, "business_name": "Al-Noor Engineering",
    "owner_name": "Mr. Bilal Ahmed",
    "contact_email": "bilal@alnoor.pk",
    "contact_phone": "+92-300-1234567",
    "subdomain_slug": "al-noor-engineering",
    "subscription_plan_id": 2,
    "subscription_status": "active",
    "is_active": true,
    "subscription_plan": { "id": 2, "name": "Professional", "monthly_price": 15000 }
  }],
  "pagination": { "total": 12, "per_page": 20, "current_page": 1 }
}
Errors
401 Unauthenticated403 Missing tenants.view
POST/api/platform/tenants

Invite a new business tenant. Creates the Business record, auto-generates a subdomain_slug, and optionally creates a business admin user with a temporary password. All steps run inside a DB transaction.

Permission required
tenants.manage
Request body
FieldRequiredNotes
business_nameYesstring, max 255. Used to auto-generate subdomain_slug via Str::slug()
owner_nameYesstring, max 255
contact_emailYesemail, max 255. Used as admin user email if create_admin_user=true
contact_phoneNostring, max 64
subscription_plan_idYesint. Plan must exist AND is_active=true — returns 422 otherwise
create_admin_userNoboolean, default true. When true: creates admin User with user_type=admin, random 12-char password
Request JSON
{
  "business_name": "Al-Noor Engineering",
  "owner_name": "Mr. Bilal Ahmed",
  "contact_email": "bilal@alnoor.pk",
  "contact_phone": "+92-300-1234567",
  "subscription_plan_id": 2,
  "create_admin_user": true
}
Response 201
JSON
{
  "success": true, "msg": "Tenant invited",
  "data": {
    "id": 13,
    "business_name": "Al-Noor Engineering",
    "subdomain_slug": "al-noor-engineering",
    "subscription_status": "active",
    "is_active": true,
    "subscription_plan": { "id": 2, "name": "Professional", "monthly_price": 15000 },
    "admin_invite": {
      "user_id": 42,
      "username": "alnoorengineering_admin",
      "email": "bilal@alnoor.pk",
      "temporary_password": "Kx7@mPqZ3nWr"
    }
  }
}
Share the temporary_password securely — it is only returned once in this response and is not stored in plain text. The business admin must change it on first login.
Errors
401 Unauthenticated403 Missing tenants.manage422 Invalid plan (inactive or missing)422 Validation failed
GET/api/platform/tenants/{id}

Retrieve a single tenant with their subscription plan detail.

Permission required
tenants.view
Response 200
JSON
{
  "success": true, "msg": "Tenants",
  "data": {
    "id": 3, "business_name": "Al-Noor Engineering",
    "owner_name": "Mr. Bilal Ahmed",
    "contact_email": "bilal@alnoor.pk",
    "subdomain_slug": "al-noor-engineering",
    "subscription_plan_id": 2,
    "subscription_status": "active",
    "is_active": true,
    "subscription_plan": { "id": 2, "name": "Professional", "monthly_price": 15000, "max_projects": 25 }
  }
}
Errors
404 Not found403 Missing tenants.view
PUT/api/platform/tenants/{id}

Update tenant fields. All fields are optional (PATCH semantics). Changed fields are diffed and logged to the platform audit log. Setting is_active=false also fires a TENANT_SUSPENDED audit event.

Permission required
tenants.manage
Request body (all optional)
FieldTypeNotes
business_namestringmax 255
owner_namestringmax 255
contact_emailemailmax 255
contact_phonestringmax 64; nullable
subscription_plan_idintMust exist and be is_active=true
subscription_statusstringactive | suspended | past_due | cancelled
is_activebooleanSetting false triggers tenantSuspended() audit event
Response 200
JSON
{ "success": true, "msg": "Updated", "data": { "id": 3, "is_active": true, "subscription_status": "active" } }
Errors
422 Invalid plan422 Validation failed404 Not found403 Missing tenants.manage
DELETE/api/platform/tenants/{id}

Soft-suspend a tenant. Does not hard-delete any records. Sets is_active=false and subscription_status='suspended'. Fires PlatformAuditLogger.tenantSuspended().

Permission required
tenants.manage
Soft deactivation only. No business data, projects, users, or documents are deleted. The business can be reactivated by sending PUT /api/platform/tenants/{id} with is_active=true.
Response 200
JSON
{ "success": true, "msg": "Tenant suspended", "data": { "id": 3, "is_active": false } }
Errors
404 Not found403 Missing tenants.manage
📋

Subscription Plans

GET/api/platform/subscription-plans

List all subscription plans including inactive ones. Ordered by monthly_price ascending. Use plan_breakdown on the dashboard for per-plan tenant counts.

Permission required
subscription_plans.view
Response 200
JSON
{
  "success": true, "msg": "Subscription plans",
  "data": [
    { "id": 1, "name": "Starter", "slug": "starter", "monthly_price": 5000, "max_projects": 5, "max_locations": 3, "max_employees": 10, "has_client_portal": false, "is_active": true },
    { "id": 2, "name": "Professional", "slug": "professional", "monthly_price": 15000, "max_projects": 25, "max_locations": 5, "max_employees": 50, "has_client_portal": true, "is_active": true },
    { "id": 3, "name": "Enterprise", "slug": "enterprise", "monthly_price": 35000, "max_projects": -1, "max_locations": -1, "max_employees": -1, "has_client_portal": true, "is_active": true }
  ]
}
max_projects, max_locations, max_employees of -1 mean unlimited. Display as "Unlimited" in the UI.
Errors
403 Missing subscription_plans.view
POST/api/platform/subscription-plans

Create a new subscription plan. If slug is omitted, it is auto-generated from the plan name using Str::slug() with a uniqueness suffix if needed.

Permission required
subscription_plans.manage
Request body
FieldRequiredNotes
nameYesstring, max 128
monthly_priceYesnumeric, min 0. Use 0 for free plans.
max_projectsYesint, min -1. -1 = unlimited
max_locationsYesint, min -1. -1 = unlimited
max_employeesYesint, min -1. -1 = unlimited
slugNostring, max 64. Auto-generated from name if omitted. Must be unique.
has_client_portalNoboolean, default false
is_activeNoboolean, default true
Request JSON
{
  "name": "Professional",
  "monthly_price": 15000,
  "max_projects": 25,
  "max_locations": 5,
  "max_employees": 50,
  "has_client_portal": true,
  "is_active": true
}
Response 201
JSON
{ "success": true, "msg": "Created", "data": { "id": 2, "name": "Professional", "slug": "professional", "monthly_price": 15000, "max_projects": 25, "has_client_portal": true, "is_active": true } }
Errors
422 Validation failed422 Slug already in use403 Missing subscription_plans.manage
GET/api/platform/subscription-plans/{id}

Retrieve a single subscription plan by ID.

Permission required
subscription_plans.view
Response 200
JSON
{ "success": true, "msg": "Subscription plans",
  "data": { "id": 2, "name": "Professional", "slug": "professional", "monthly_price": 15000,
    "max_projects": 25, "max_locations": 5, "max_employees": 50, "has_client_portal": true, "is_active": true }
}
Errors
404 Not found403 Missing subscription_plans.view
PUT/api/platform/subscription-plans/{id}

Update plan fields. All fields optional. Slug uniqueness is validated excluding the plan being updated. Side effect: PlatformAuditLogger.planUpdated() fires.

Permission required
subscription_plans.manage
Request body (all optional)
FieldTypeNotes
namestringmax 128
slugstringmax 64; must be unique (excluding self)
monthly_pricenumericmin 0
max_projectsintmin -1 (-1 = unlimited)
max_locationsintmin -1 (-1 = unlimited)
max_employeesintmin -1 (-1 = unlimited)
has_client_portalboolean 
is_activebooleanSetting to false prevents new tenant assignments
Response 200
JSON
{ "success": true, "msg": "Updated", "data": { "id": 2, "name": "Professional", "max_projects": 30, "is_active": true } }
Errors
422 Slug already in use422 Validation failed404 Not found
DELETE/api/platform/subscription-plans/{id}

Delete a plan. If any tenants are still assigned to the plan, it cannot be hard-deleted — the plan is deactivated instead and a 422 is returned with the deactivated plan. If no tenants are assigned, the plan is permanently deleted.

Permission required
subscription_plans.manage
Tenants assigned → deactivation only. Reassign all tenants to another plan first if you need to hard-delete. The 422 response includes the deactivated plan object so you can confirm the change.
Response 200 (no tenants assigned — hard deleted)
JSON
{ "success": true, "msg": "Deleted", "data": { "id": 4 } }
Response 422 (tenants assigned — deactivated instead)
JSON
{
  "success": false,
  "msg": "Plan has assigned tenants; deactivated instead of deleted",
  "error": "tenants_assigned",
  "data": { "id": 2, "name": "Professional", "is_active": false }
}
Errors
404 Not found403 Missing subscription_plans.manage
🔔

Notifications

GET/api/platform/notifications

Bell-dropdown endpoint. Returns the 20 most recent unread PlatformAuditEvent records. No pagination key is returned. Used for the notification bell badge only — use the Audit Logs endpoints for full history.

Permission required
platform_notifications.view
No query parameters
Response 200
JSON
{
  "success": true, "msg": "Notifications",
  "data": [{
    "id": 47,
    "title": "Tenant Created",
    "body": "Al-Noor Engineering onboarded on Professional plan",
    "event_code": "TENANT_CREATED",
    "category": "tenant",
    "is_read": false,
    "actor": { "id": 1, "display_name": "Platform Admin", "email": "admin@platform.com" },
    "created_at": "2024-03-15T10:23:44+05:00"
  }]
}
Errors
403 Missing platform_notifications.view
PATCH/api/platform/notifications/read-all

Mark all unread platform audit events as read. Clears the bell badge count.

Permission required
platform_notifications.manage
No request body required
Response 200
JSON
{ "success": true, "msg": "All marked read", "data": null }
Errors
403 Missing platform_notifications.manage
📝

Audit Logs

GET/api/platform/audit-logs

Paginated list of PlatformAuditEvent records. Full audit trail with filter and search support. Each event includes the actor who triggered it.

Permission required
platform_notifications.view
Query parameters
ParamTypeDescription
categorystringe.g. tenant, plan
severitystringe.g. info, warning, action_taken
entity_typestringe.g. business, subscription_plan
is_readstring0 / false for unread; 1 / true for read. Omit for all.
searchstringSearches title, body, event_code, category, and numeric id
per_pageintDefault 15
Response 200
JSON
{
  "success": true, "msg": "Audit logs",
  "data": [{
    "id": 47,
    "title": "Tenant Created",
    "body": "Al-Noor Engineering onboarded on Professional plan",
    "event_code": "TENANT_CREATED",
    "category": "tenant",
    "severity": "info",
    "entity_type": "business",
    "is_read": false,
    "actor": { "id": 1, "display_name": "Platform Admin", "email": "admin@platform.com" },
    "created_at": "2024-03-15T10:23:44+05:00"
  }],
  "pagination": { "total": 84, "per_page": 15, "current_page": 1, "last_page": 6 }
}
Event codes reference
event_codecategoryTrigger
TENANT_CREATEDtenantNew business onboarded via POST /tenants
TENANT_UPDATEDtenantAny field changed via PUT /tenants/{id}
TENANT_SUSPENDEDtenantis_active set to false or DELETE /tenants/{id}
PLAN_CREATEDplanNew subscription plan created
PLAN_UPDATEDplanPlan updated or deactivated
Errors
403 Missing platform_notifications.view
GET/api/platform/audit-logs/{id}

Retrieve a single audit event with full actor detail.

Permission required
platform_notifications.view
Response 200
JSON
{
  "success": true, "msg": "Audit log",
  "data": {
    "id": 47, "title": "Tenant Created",
    "body": "Al-Noor Engineering onboarded on Professional plan",
    "event_code": "TENANT_CREATED", "category": "tenant",
    "severity": "info", "entity_type": "business",
    "is_read": false,
    "actor": { "id": 1, "display_name": "Platform Admin", "email": "admin@platform.com" },
    "created_at": "2024-03-15T10:23:44+05:00"
  }
}
Errors
404 Not found422 Invalid id (id ≤ 0)403 Missing view permission
PATCH/api/platform/audit-logs/read-all

Mark all unread audit events as read in one operation.

Permission required
platform_notifications.manage
No request body required
Response 200
JSON
{ "success": true, "msg": "All marked read", "data": null }
Errors
403 Missing platform_notifications.manage
PATCH/api/platform/audit-logs/{id}/read

Mark a single audit event as read.

Permission required
platform_notifications.manage
No request body required
Response 200
JSON
{ "success": true, "msg": "Marked read", "data": { "id": 47, "is_read": true } }
Errors
404 Not found403 Missing platform_notifications.manage
DELETE/api/platform/audit-logs/{id}

Hard-delete a single audit event. Prefer marking as read over deleting for compliance purposes.

Permission required
platform_notifications.manage
Permanent deletion. Audit events provide compliance traceability. Only delete records that are confirmed noise or duplicates.
Response 200
JSON
{ "success": true, "msg": "Deleted", "data": null }
Errors
404 Not found403 Missing platform_notifications.manage