/api/platform/ for all endpoints on this pageAuth —
Authorization: Bearer {sanctum_token} · user_type must be platform_adminGuard —
rejectUnlessSuperAdmin() on every endpoint — additional permission checks per operation (see below)Scope — No
business_id filter — Platform Admin sees all tenants globallyEnvelope —
{ "success": true, "msg": "...", "data": {...} } — paginated responses include "pagination": {...}
Dashboard
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.
{ "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" } }
Tenants
Paginated list of all business tenants on the platform. Optionally filter by plan, active status, or subscription status.
| Param | Type | Description |
|---|---|---|
| subscription_plan_id | int | Filter by plan ID. If plan does not exist, returns empty result. |
| is_active | bool | true / false — filter by active/suspended |
| subscription_status | string | active | suspended | past_due | cancelled |
| per_page | int | Default 20 |
{ "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 } }
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.
| Field | Required | Notes |
|---|---|---|
| business_name | Yes | string, max 255. Used to auto-generate subdomain_slug via Str::slug() |
| owner_name | Yes | string, max 255 |
| contact_email | Yes | email, max 255. Used as admin user email if create_admin_user=true |
| contact_phone | No | string, max 64 |
| subscription_plan_id | Yes | int. Plan must exist AND is_active=true — returns 422 otherwise |
| create_admin_user | No | boolean, default true. When true: creates admin User with user_type=admin, random 12-char password |
{ "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 }
{ "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" } } }
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.Retrieve a single tenant with their subscription plan detail.
{ "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 } } }
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.
| Field | Type | Notes |
|---|---|---|
| business_name | string | max 255 |
| owner_name | string | max 255 |
| contact_email | max 255 | |
| contact_phone | string | max 64; nullable |
| subscription_plan_id | int | Must exist and be is_active=true |
| subscription_status | string | active | suspended | past_due | cancelled |
| is_active | boolean | Setting false triggers tenantSuspended() audit event |
{ "success": true, "msg": "Updated", "data": { "id": 3, "is_active": true, "subscription_status": "active" } }
Soft-suspend a tenant. Does not hard-delete any records. Sets is_active=false and subscription_status='suspended'. Fires PlatformAuditLogger.tenantSuspended().
PUT /api/platform/tenants/{id} with is_active=true.{ "success": true, "msg": "Tenant suspended", "data": { "id": 3, "is_active": false } }
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.
{ "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.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.
| Field | Required | Notes |
|---|---|---|
| name | Yes | string, max 128 |
| monthly_price | Yes | numeric, min 0. Use 0 for free plans. |
| max_projects | Yes | int, min -1. -1 = unlimited |
| max_locations | Yes | int, min -1. -1 = unlimited |
| max_employees | Yes | int, min -1. -1 = unlimited |
| slug | No | string, max 64. Auto-generated from name if omitted. Must be unique. |
| has_client_portal | No | boolean, default false |
| is_active | No | boolean, default true |
{ "name": "Professional", "monthly_price": 15000, "max_projects": 25, "max_locations": 5, "max_employees": 50, "has_client_portal": true, "is_active": true }
{ "success": true, "msg": "Created", "data": { "id": 2, "name": "Professional", "slug": "professional", "monthly_price": 15000, "max_projects": 25, "has_client_portal": true, "is_active": true } }
Retrieve a single subscription plan by ID.
{ "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 } }
Update plan fields. All fields optional. Slug uniqueness is validated excluding the plan being updated. Side effect: PlatformAuditLogger.planUpdated() fires.
| Field | Type | Notes |
|---|---|---|
| name | string | max 128 |
| slug | string | max 64; must be unique (excluding self) |
| monthly_price | numeric | min 0 |
| max_projects | int | min -1 (-1 = unlimited) |
| max_locations | int | min -1 (-1 = unlimited) |
| max_employees | int | min -1 (-1 = unlimited) |
| has_client_portal | boolean | |
| is_active | boolean | Setting to false prevents new tenant assignments |
{ "success": true, "msg": "Updated", "data": { "id": 2, "name": "Professional", "max_projects": 30, "is_active": true } }
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.
{ "success": true, "msg": "Deleted", "data": { "id": 4 } }
{ "success": false, "msg": "Plan has assigned tenants; deactivated instead of deleted", "error": "tenants_assigned", "data": { "id": 2, "name": "Professional", "is_active": false } }
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.
{ "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" }] }
Mark all unread platform audit events as read. Clears the bell badge count.
{ "success": true, "msg": "All marked read", "data": null }
Audit Logs
Paginated list of PlatformAuditEvent records. Full audit trail with filter and search support. Each event includes the actor who triggered it.
| Param | Type | Description |
|---|---|---|
| category | string | e.g. tenant, plan |
| severity | string | e.g. info, warning, action_taken |
| entity_type | string | e.g. business, subscription_plan |
| is_read | string | 0 / false for unread; 1 / true for read. Omit for all. |
| search | string | Searches title, body, event_code, category, and numeric id |
| per_page | int | Default 15 |
{ "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_code | category | Trigger |
|---|---|---|
| TENANT_CREATED | tenant | New business onboarded via POST /tenants |
| TENANT_UPDATED | tenant | Any field changed via PUT /tenants/{id} |
| TENANT_SUSPENDED | tenant | is_active set to false or DELETE /tenants/{id} |
| PLAN_CREATED | plan | New subscription plan created |
| PLAN_UPDATED | plan | Plan updated or deactivated |
Retrieve a single audit event with full actor detail.
{ "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" } }
Mark all unread audit events as read in one operation.
{ "success": true, "msg": "All marked read", "data": null }
Mark a single audit event as read.
{ "success": true, "msg": "Marked read", "data": { "id": 47, "is_read": true } }
Hard-delete a single audit event. Prefer marking as read over deleting for compliance purposes.
{ "success": true, "msg": "Deleted", "data": null }